Browse Source

Forbid canceling orders during the inventory reduction state

Fix #221
pull/222/head
gdlcf88 4 years ago
parent
commit
9fd1b679e7
  1. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderManager.cs
  2. 6
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs
  3. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs
  4. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/PaymentCanceledEventHandler.cs
  5. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs
  6. 42
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs
  7. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs
  8. 38
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderManager.cs

@ -8,6 +8,6 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
Task<Order> CompleteAsync(Order order); Task<Order> CompleteAsync(Order order);
Task<Order> CancelAsync(Order order, string cancellationReason); Task<Order> CancelAsync(Order order, string cancellationReason, bool forceCancel = false);
} }
} }

6
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs

@ -226,5 +226,11 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
StaffRemark = staffRemark; StaffRemark = staffRemark;
} }
public bool IsInInventoryDeductionStage()
{
return !ReducedInventoryAfterPlacingTime.HasValue ||
PaidTime.HasValue && !ReducedInventoryAfterPaymentTime.HasValue;
}
} }
} }

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderManager.cs

@ -57,14 +57,14 @@ namespace EasyAbp.EShop.Orders.Orders
} }
[UnitOfWork] [UnitOfWork]
public virtual async Task<Order> CancelAsync(Order order, string cancellationReason) public virtual async Task<Order> CancelAsync(Order order, string cancellationReason, bool forceCancel = false)
{ {
if (order.CanceledTime.HasValue) if (order.CanceledTime.HasValue)
{ {
throw new OrderIsInWrongStageException(order.Id); throw new OrderIsInWrongStageException(order.Id);
} }
if (order.IsInPayment()) if (!forceCancel && (order.IsInPayment() || order.IsInInventoryDeductionStage()))
{ {
throw new OrderIsInWrongStageException(order.Id); throw new OrderIsInWrongStageException(order.Id);
} }

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/PaymentCanceledEventHandler.cs

@ -40,6 +40,7 @@ namespace EasyAbp.EShop.Orders.Orders
order.SetPaymentId(null); order.SetPaymentId(null);
// OrderAutoCancelOnUpdatedHandler may auto cancel the unpaid order.
await _orderRepository.UpdateAsync(order, true); await _orderRepository.UpdateAsync(order, true);
} }
} }

11
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs

@ -1,10 +1,7 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Payments.Refunds; using EasyAbp.EShop.Payments.Refunds;
using EasyAbp.EShop.Products.Products; using EasyAbp.EShop.Products.Products;
using EasyAbp.PaymentService.Refunds;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -49,7 +46,8 @@ namespace EasyAbp.EShop.Orders.Orders
if (!eventData.IsSuccess) if (!eventData.IsSuccess)
{ {
await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason); await _orderManager.CancelAsync(
order, OrdersConsts.InventoryReductionFailedAutoCancellationReason, true);
return; return;
} }
@ -76,7 +74,8 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
var refundOrderEto = CreateRefundOrderEto(order); var refundOrderEto = CreateRefundOrderEto(order);
await _orderManager.CancelAsync(order, OrdersConsts.InventoryReductionFailedAutoCancellationReason); await _orderManager.CancelAsync(
order, OrdersConsts.InventoryReductionFailedAutoCancellationReason, true);
await RefundOrderAsync(refundOrderEto); await RefundOrderAsync(refundOrderEto);

42
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs

@ -64,9 +64,7 @@ namespace EasyAbp.EShop.Orders.Orders
"Key", "Key",
0.3m 0.3m
)); ));
Order1.SetPaymentId(OrderTestData.Payment1Id);
Order1.SetPaidTime(DateTime.Now);
orderRepository.GetAsync(OrderTestData.Order1Id).Returns(Task.FromResult(Order1)); orderRepository.GetAsync(OrderTestData.Order1Id).Returns(Task.FromResult(Order1));
services.AddTransient(_ => orderRepository); services.AddTransient(_ => orderRepository);
@ -117,7 +115,10 @@ namespace EasyAbp.EShop.Orders.Orders
} }
} }
}); });
Order1.SetPaymentId(OrderTestData.Payment1Id);
Order1.SetPaidTime(DateTime.Now);
Order1.RefundAmount.ShouldBe(0.3m); Order1.RefundAmount.ShouldBe(0.3m);
var orderLine1 = Order1.OrderLines.Single(x => x.Id == OrderTestData.OrderLine1Id); var orderLine1 = Order1.OrderLines.Single(x => x.Id == OrderTestData.OrderLine1Id);
@ -133,6 +134,9 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
var handler = ServiceProvider.GetRequiredService<RefundCompletedEventHandler>(); var handler = ServiceProvider.GetRequiredService<RefundCompletedEventHandler>();
Order1.SetPaymentId(OrderTestData.Payment1Id);
Order1.SetPaidTime(DateTime.Now);
await Should.ThrowAsync<InvalidRefundAmountException>(async () => await Should.ThrowAsync<InvalidRefundAmountException>(async () =>
{ {
await handler.HandleEventAsync(new EShopRefundCompletedEto await handler.HandleEventAsync(new EShopRefundCompletedEto
@ -174,6 +178,9 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
var handler = ServiceProvider.GetRequiredService<RefundCompletedEventHandler>(); var handler = ServiceProvider.GetRequiredService<RefundCompletedEventHandler>();
Order1.SetPaymentId(OrderTestData.Payment1Id);
Order1.SetPaidTime(DateTime.Now);
await Should.ThrowAsync<InvalidRefundQuantityException>(async () => await Should.ThrowAsync<InvalidRefundQuantityException>(async () =>
{ {
await handler.HandleEventAsync(new EShopRefundCompletedEto await handler.HandleEventAsync(new EShopRefundCompletedEto
@ -209,5 +216,32 @@ namespace EasyAbp.EShop.Orders.Orders
}); });
}); });
} }
[Fact]
public async Task Should_Forbid_Canceling_Order_During_Payment_State()
{
var orderManager = ServiceProvider.GetRequiredService<IOrderManager>();
var order = await _orderRepository.GetAsync(OrderTestData.Order1Id);
order.SetPaymentId(Guid.NewGuid());
order.SetPaidTime(null);
await Should.ThrowAsync<OrderIsInWrongStageException>(() => orderManager.CancelAsync(order, "my-reason"));
}
[Fact]
public async Task Should_Forbid_Canceling_Order_During_Inventory_Reduction_State()
{
var orderManager = ServiceProvider.GetRequiredService<IOrderManager>();
var order = await _orderRepository.GetAsync(OrderTestData.Order1Id);
order.SetReducedInventoryAfterPlacingTime(null);
await Should.ThrowAsync<OrderIsInWrongStageException>(() => orderManager.CancelAsync(order, "my-reason"));
order.SetReducedInventoryAfterPlacingTime(DateTime.Now);
order.SetPaymentId(Guid.NewGuid());
order.SetPaidTime(DateTime.Now);
order.SetReducedInventoryAfterPlacingTime(null);
await Should.ThrowAsync<OrderIsInWrongStageException>(() => orderManager.CancelAsync(order, "my-reason"));
}
} }
} }

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/InventoryRollbackOrderCanceledEventHandler.cs

@ -28,7 +28,7 @@ namespace EasyAbp.EShop.Products.Products
[UnitOfWork(true)] [UnitOfWork(true)]
public virtual async Task HandleEventAsync(OrderCanceledEto eventData) public virtual async Task HandleEventAsync(OrderCanceledEto eventData)
{ {
if (eventData.Order.PaidTime.HasValue) if (eventData.Order.PaidTime.HasValue || !eventData.Order.ReducedInventoryAfterPlacingTime.HasValue)
{ {
return; return;
} }

38
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/InventoryRollbackTests.cs

@ -25,40 +25,62 @@ namespace EasyAbp.EShop.Products.Products
[Fact] [Fact]
public async Task Should_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Not_Paid() public async Task Should_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Not_Paid()
{ {
await TestAsync(InventoryStrategy.ReduceAfterPlacing, false, true); await TestAsync(InventoryStrategy.ReduceAfterPlacing, false, true, true);
} }
[Fact] [Fact]
public async Task Should_Not_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Paid() public async Task Should_Not_Roll_Back_ReduceAfterPlacing_Inventory_If_Order_Is_Paid()
{ {
await TestAsync(InventoryStrategy.ReduceAfterPlacing, true, false); await TestAsync(InventoryStrategy.ReduceAfterPlacing, true, true, false);
} }
[Fact] [Fact]
public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Not_Paid() public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Not_Paid()
{ {
await TestAsync(InventoryStrategy.ReduceAfterPayment, false, false); await TestAsync(InventoryStrategy.ReduceAfterPayment, false, true, false);
} }
[Fact] [Fact]
public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Paid() public async Task Should_Not_Roll_Back_ReduceAfterPayment_Inventory_If_Order_Is_Paid()
{ {
await TestAsync(InventoryStrategy.ReduceAfterPayment, true, false); await TestAsync(InventoryStrategy.ReduceAfterPayment, true, true, false);
} }
[Fact] [Fact]
public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Not_Paid() public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Not_Paid()
{ {
await TestAsync(InventoryStrategy.NoNeed, false, false); await TestAsync(InventoryStrategy.NoNeed, false, true, false);
} }
[Fact] [Fact]
public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Paid() public async Task Should_Not_Roll_Back_NoNeed_Inventory_If_Order_Is_Paid()
{ {
await TestAsync(InventoryStrategy.NoNeed, true, false); await TestAsync(InventoryStrategy.NoNeed, true, true, false);
} }
protected async Task TestAsync(InventoryStrategy inventoryStrategy, bool orderPaid, bool expectRollback) [Fact]
public async Task Should_Roll_Back_FlashSales_Inventory_If_Order_Is_Not_Paid()
{
await TestAsync(InventoryStrategy.FlashSales, false, true, true);
}
[Fact]
public async Task Should_Not_Roll_Back_FlashSales_Inventory_If_Order_Is_Paid()
{
await TestAsync(InventoryStrategy.FlashSales, true, true, false);
}
[Fact]
public async Task Should_Not_Roll_Back_Inventory_If_ReducedInventoryAfterPlacingTime_Is_Null()
{
await TestAsync(InventoryStrategy.NoNeed, true, false, false);
await TestAsync(InventoryStrategy.ReduceAfterPlacing, true, false, false);
await TestAsync(InventoryStrategy.ReduceAfterPayment, true, false, false);
await TestAsync(InventoryStrategy.FlashSales, true, false, false);
}
protected async Task TestAsync(InventoryStrategy inventoryStrategy, bool orderPaid,
bool hasReducedInventoryAfterPlacingTime, bool expectRollback)
{ {
var product = await ProductRepository.GetAsync(ProductsTestData.Product1Id); var product = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
var productSku = product.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id); var productSku = product.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
@ -98,7 +120,7 @@ namespace EasyAbp.EShop.Products.Products
CompletionTime = null, CompletionTime = null,
CanceledTime = null, CanceledTime = null,
CancellationReason = null, CancellationReason = null,
ReducedInventoryAfterPlacingTime = null, ReducedInventoryAfterPlacingTime = hasReducedInventoryAfterPlacingTime ? DateTime.Now : null,
ReducedInventoryAfterPaymentTime = null, ReducedInventoryAfterPaymentTime = null,
PaymentExpiration = null, PaymentExpiration = null,
OrderLines = new List<OrderLineEto> OrderLines = new List<OrderLineEto>

Loading…
Cancel
Save