From 7b0b21027cbd836922e03febaa7a5be0d86e26c8 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Sat, 9 Apr 2022 20:10:24 +0800 Subject: [PATCH 1/2] Auto create ProductInventory entities --- .../DefaultProductInventoryProvider.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs index cf2cb961..e22a1881 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs @@ -5,6 +5,8 @@ using System.Threading.Tasks; using EasyAbp.EShop.Products.ProductInventories; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; namespace EasyAbp.EShop.Products.Products @@ -12,15 +14,21 @@ namespace EasyAbp.EShop.Products.Products public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency { // Todo: should use IProductInventoryStore. + private readonly IGuidGenerator _guidGenerator; + private readonly ICurrentTenant _currentTenant; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IDistributedEventBus _distributedEventBus; private readonly IProductInventoryRepository _productInventoryRepository; public DefaultProductInventoryProvider( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, IUnitOfWorkManager unitOfWorkManager, IDistributedEventBus distributedEventBus, IProductInventoryRepository productInventoryRepository) { + _guidGenerator = guidGenerator; + _currentTenant = currentTenant; _unitOfWorkManager = unitOfWorkManager; _distributedEventBus = distributedEventBus; _productInventoryRepository = productInventoryRepository; @@ -49,7 +57,7 @@ namespace EasyAbp.EShop.Products.Products [UnitOfWork(true)] public virtual async Task TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool decreaseSold) { - var productInventory = await _productInventoryRepository.GetAsync(x => x.ProductSkuId == productSku.Id); + var productInventory = await GetOrCreateProductInventoryAsync(product.Id, productSku.Id); return await TryIncreaseInventoryAsync(product, productInventory, quantity, decreaseSold); } @@ -57,11 +65,29 @@ namespace EasyAbp.EShop.Products.Products [UnitOfWork(true)] public virtual async Task TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold) { - var productInventory = await _productInventoryRepository.GetAsync(x => x.ProductSkuId == productSku.Id); + var productInventory = await GetOrCreateProductInventoryAsync(product.Id, productSku.Id); return await TryReduceInventoryAsync(product, productInventory, quantity, increaseSold); } + [UnitOfWork] + protected virtual async Task GetOrCreateProductInventoryAsync(Guid productId, Guid productSkuId) + { + var productInventory = + await _productInventoryRepository.FindAsync(x => + x.ProductId == productId && x.ProductSkuId == productSkuId); + + if (productInventory is null) + { + productInventory = new ProductInventory(_guidGenerator.Create(), _currentTenant.Id, productId, + productSkuId, 0, 0); + + await _productInventoryRepository.InsertAsync(productInventory, true); + } + + return productInventory; + } + [UnitOfWork(true)] public virtual async Task TryIncreaseInventoryAsync(Product product, ProductInventory productInventory, int quantity, bool decreaseSold) { From 6cbb4866248468c9620d05f05be15db18fe66864 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Sat, 9 Apr 2022 20:19:06 +0800 Subject: [PATCH 2/2] Inventory auto rollback --- .../Products/EShopProductsDomainModule.cs | 1 + ...entoryRollbackOrderCanceledEventHandler.cs | 47 ++++++ .../Products/InventoryRollbackTests.cs | 150 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs create mode 100644 modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs 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); + } + } +}