Browse Source

Merge pull request #210 from EasyAbp/refactor-flash-sales

Refactor the flash sales plugin module
pull/211/head
Super 4 years ago
committed by GitHub
parent
commit
0a9ede1b47
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      docs/plugins/flash-sales/README.md
  2. 33
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs
  3. 10
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSaleOrderResultDto.cs
  4. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/OrderFlashSalePlanInput.cs
  5. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanAppService.cs
  6. 14
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs
  7. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs
  8. 71
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs
  9. 96
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandler.cs
  10. 175
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs
  11. 124
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandler.cs
  12. 59
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCache.cs
  13. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCacheItem.cs
  14. 45
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs
  15. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleCurrentResultCache.cs
  16. 4
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/Options/FlashSalesOptions.cs
  17. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs
  18. 8
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs
  19. 12
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs
  20. 26
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEto.cs
  21. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs
  22. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs
  23. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json
  24. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json
  25. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json
  26. 37
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs
  27. 13
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs
  28. 17
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResult.cs
  29. 9
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs
  30. 6
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultController.cs
  31. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/ViewModels/ViewFlashSaleResultViewModel.cs
  32. 5
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/index.js
  33. 4
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs
  34. 8
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs
  35. 8
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs
  36. 23
      plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs
  37. 4
      plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs
  38. 3
      plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs
  39. 165
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandlerTest.cs
  40. 257
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandlerTests.cs
  41. 119
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs
  42. 151
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandlerTests.cs
  43. 9
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs
  44. 4
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs
  45. 85
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs
  46. 12
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs

4
docs/plugins/flash-sales/README.md

@ -41,6 +41,6 @@ A flash-sales plugin for EShop.
### Customers
1. Use `/api/e-shop/plugins/flash-sales/flash-sale-plan/{planId}/pre-order` (POST) to pre-order. It will return an expiration time if your pre-order request succeeds. You should re-invoke this API to refresh your request before the expiration time.
1. Use `/api/e-shop/plugins/flash-sales/flash-sale-plan/{planId}/pre-order` (POST) to pre-order. It will return an expiration time if your pre-order request succeeds. Don't forget to re-invoke this API to refresh your request before the expiration time.
2. When the flash sale starts, use `/api/e-shop/plugins/flash-sales/flash-sale-plan/{planId}/order` (POST) to create your order. If you are fast enough, it will occupy the inventory and create an order for you in the background.
3. If you are told that you have succeeded, continuous use `/api/e-shop/plugins/flash-sales/flash-sale-result/{resultId}` (GET) to query the order creation result until it succeeds or fails.
3. If you are told that you have succeeded, continuous use `/api/e-shop/plugins/flash-sales/flash-sale-result/current/{planId}` (GET) to query the order creation result until it succeeds or fails.

33
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs

@ -10,6 +10,7 @@ using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
@ -61,16 +62,20 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken))
{
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto()
await DistributedEventBus.PublishAsync(new FlashSaleOrderCreationResultEto
{
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
PlanId = eventData.Plan.Id,
OrderId = null,
UserId = eventData.UserId,
StoreId = eventData.StoreId,
PendingResultId = eventData.PendingResultId,
StoreId = eventData.Plan.StoreId,
ResultId = eventData.ResultId,
Success = false,
Reason = FlashSaleResultFailedReason.InvalidHashToken
Reason = FlashSaleResultFailedReason.InvalidHashToken,
ProductInventoryProviderName = product.InventoryProviderName,
ProductId = eventData.Plan.ProductId,
ProductSkuId = eventData.Plan.ProductSkuId,
AllowToTryAgain = true
});
return;
}
@ -85,16 +90,20 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
await OrderRepository.InsertAsync(order, autoSave: true);
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto()
await DistributedEventBus.PublishAsync(new FlashSaleOrderCreationResultEto
{
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
PlanId = eventData.Plan.Id,
OrderId = order.Id,
UserId = eventData.UserId,
StoreId = eventData.StoreId,
PendingResultId = eventData.PendingResultId,
StoreId = eventData.Plan.StoreId,
ResultId = eventData.ResultId,
Success = true,
Reason = null
Reason = null,
ProductInventoryProviderName = product.InventoryProviderName,
ProductId = eventData.Plan.ProductId,
ProductSkuId = eventData.Plan.ProductSkuId,
AllowToTryAgain = false
});
}
@ -126,7 +135,7 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
{
var input = new CreateOrderDto()
{
StoreId = eventData.StoreId,
StoreId = eventData.Plan.StoreId,
CustomerRemark = eventData.CustomerRemark,
OrderLines = new List<CreateOrderLineDto>()
{
@ -139,6 +148,8 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
}
};
eventData.MapExtraPropertiesTo(input);
return Task.FromResult(input);
}

10
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/FlashSaleOrderResultDto.cs

@ -1,4 +1,4 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
@ -7,5 +7,9 @@ public class FlashSaleOrderResultDto : ExtensibleEntityDto
{
public bool IsSuccess { get; set; }
public Guid? FlashSaleResultId { get; set; }
}
[CanBeNull]
public string ErrorCode { get; set; }
[CanBeNull]
public string ErrorMessage { get; set; }
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/CreateOrderInput.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/Dtos/OrderFlashSalePlanInput.cs

@ -2,7 +2,7 @@
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
public class CreateOrderInput : ExtensibleEntityDto
public class OrderFlashSalePlanInput : ExtensibleEntityDto
{
public string CustomerRemark { get; set; }
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/IFlashSalePlanAppService.cs

@ -15,5 +15,5 @@ public interface IFlashSalePlanAppService :
{
Task<FlashSalePlanPreOrderDto> PreOrderAsync(Guid id);
Task<FlashSaleOrderResultDto> OrderAsync(Guid id, CreateOrderInput input);
Task<FlashSaleOrderResultDto> OrderAsync(Guid id, OrderFlashSalePlanInput flashSalePlanInput);
}

14
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs

@ -6,15 +6,17 @@ namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
public class FlashSaleResultDto : ExtensibleFullAuditedEntityDto<Guid>, IMultiStore
{
public virtual Guid StoreId { get; set; }
public Guid StoreId { get; set; }
public virtual Guid PlanId { get; set; }
public Guid PlanId { get; set; }
public virtual FlashSaleResultStatus Status { get; set; }
public FlashSaleResultStatus Status { get; set; }
public virtual string Reason { get; set; }
public string Reason { get; set; }
public virtual Guid UserId { get; set; }
public Guid UserId { get; set; }
public virtual Guid? OrderId { get; set; }
public Guid? OrderId { get; set; }
public DateTime ReducedInventoryTime { get; set; }
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using Volo.Abp.Application.Services;
@ -10,5 +11,5 @@ public interface IFlashSaleResultAppService :
Guid,
FlashSaleResultGetListInput>
{
Task<FlashSaleResultDto> GetCurrentAsync(Guid planId);
}

71
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs

@ -1,71 +0,0 @@
using System;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Products.Products;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class CreateFlashSaleOrderFailedEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency
{
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
protected IDistributedCache DistributedCache { get; }
protected IFlashSalePlanRepository FlashSalePlanRepository { get; }
protected IProductAppService ProductAppService { get; }
protected ILogger<CreateFlashSaleOrderFailedEventHandler > Logger { get; }
public CreateFlashSaleOrderFailedEventHandler (
IFlashSaleInventoryManager flashSaleInventoryManager,
IDistributedCache distributedCache,
IFlashSalePlanRepository flashSalePlanRepository,
IProductAppService productAppService,
ILogger<CreateFlashSaleOrderFailedEventHandler > logger)
{
FlashSaleInventoryManager = flashSaleInventoryManager;
DistributedCache = distributedCache;
FlashSalePlanRepository = flashSalePlanRepository;
ProductAppService = productAppService;
Logger = logger;
}
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData)
{
if (eventData.Success)
{
return;
}
if (eventData.Reason != FlashSaleResultFailedReason.InvalidHashToken)
{
return;
}
var plan = await FlashSalePlanRepository.GetAsync(eventData.PlanId);
var product = await ProductAppService.GetAsync(plan.ProductId);
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync(
plan.TenantId, product.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true
))
{
Logger.LogWarning("Try roll back inventory failed.");
return;
}
await RemoveUserFlashSaleResultCacheAsync(plan, eventData.UserId);
}
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(FlashSalePlan plan, Guid userId)
{
return Task.FromResult(string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, userId));
}
protected virtual async Task RemoveUserFlashSaleResultCacheAsync(FlashSalePlan plan, Guid userId)
{
await DistributedCache.RemoveAsync(await GetUserFlashSaleResultCacheKeyAsync(plan, userId));
}
}

96
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandler.cs

@ -0,0 +1,96 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.Eshop.Products.Products;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class FlashSaleOrderCreationResultEventHandler : IDistributedEventHandler<FlashSaleOrderCreationResultEto>,
ITransientDependency
{
protected ILogger<FlashSaleOrderCreationResultEventHandler> Logger { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IObjectMapper ObjectMapper { get; }
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; }
protected IFlashSaleResultRepository FlashSaleResultRepository { get; }
public FlashSaleOrderCreationResultEventHandler(
ILogger<FlashSaleOrderCreationResultEventHandler> logger,
IFlashSaleInventoryManager flashSaleInventoryManager,
IUnitOfWorkManager unitOfWorkManager,
IObjectMapper objectMapper,
IFlashSaleCurrentResultCache flashSaleCurrentResultCache,
IFlashSaleResultRepository flashSaleResultRepository)
{
Logger = logger;
FlashSaleInventoryManager = flashSaleInventoryManager;
UnitOfWorkManager = unitOfWorkManager;
ObjectMapper = objectMapper;
FlashSaleCurrentResultCache = flashSaleCurrentResultCache;
FlashSaleResultRepository = flashSaleResultRepository;
}
[UnitOfWork]
public virtual async Task HandleEventAsync(FlashSaleOrderCreationResultEto eventData)
{
var flashSaleResult = await FlashSaleResultRepository.GetAsync(eventData.ResultId);
if (eventData.Success)
{
flashSaleResult.MarkAsSuccessful(eventData.OrderId!.Value);
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true);
}
else
{
flashSaleResult.MarkAsFailed(eventData.Reason);
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true);
}
UnitOfWorkManager.Current.OnCompleted(async () =>
{
if (eventData.Success)
{
await ResetFlashSaleCurrentResultCacheAsync(flashSaleResult);
}
else
{
// try to roll back the inventory.
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync(
eventData.TenantId, eventData.ProductInventoryProviderName, eventData.StoreId,
eventData.ProductId, eventData.ProductSkuId))
{
Logger.LogWarning("Failed to roll back the flash sale inventory.");
return; // avoid to remove cache if the rollback failed.
}
// remove the cache so the user can try to order again.
if (eventData.AllowToTryAgain)
{
await FlashSaleCurrentResultCache.RemoveAsync(flashSaleResult.PlanId, flashSaleResult.UserId);
}
else
{
await ResetFlashSaleCurrentResultCacheAsync(flashSaleResult);
}
}
});
}
protected virtual async Task ResetFlashSaleCurrentResultCacheAsync(FlashSaleResult flashSaleResult)
{
await FlashSaleCurrentResultCache.SetAsync(flashSaleResult.PlanId, flashSaleResult.UserId,
new FlashSaleCurrentResultCacheItem
{
TenantId = flashSaleResult.TenantId,
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(flashSaleResult)
});
}
}

175
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs

@ -4,21 +4,27 @@ using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.Options;
using EasyAbp.EShop.Plugins.FlashSales.Permissions;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Stores.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
@ -34,14 +40,6 @@ public class FlashSalePlanAppService :
/// </summary>
public const string PreOrderCacheKeyFormat = "eshopflashsales_{0}_{1}";
/// <summary>
/// The <see cref="GetUserFlashSaleResultCacheKeyAsync(FlashSalePlanCacheItem,Guid)"/> cache key format.
/// <para>{0}: Tenant ID</para>
/// <para>{1}: FlashSalePlan ID</para>
/// <para>{2}: User ID</para>
/// </summary>
public const string UserFlashSaleResultCacheKeyFormat = "eshopflashsales-result_{0}_{1}_{2}";
protected override string CrossStorePolicyName { get; set; } = FlashSalesPermissions.FlashSalePlan.CrossStore;
protected override string GetPolicyName { get; set; } = null;
protected override string GetListPolicyName { get; set; } = null;
@ -70,7 +68,7 @@ public class FlashSalePlanAppService :
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
protected IDistributedCache DistributedCache { get; }
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; }
protected FlashSalesOptions Options { get; }
@ -85,8 +83,8 @@ public class FlashSalePlanAppService :
IAbpDistributedLock distributedLock,
IFlashSalePlanHasher flashSalePlanHasher,
IFlashSaleInventoryManager flashSaleInventoryManager,
IDistributedCache distributedCache,
IOptionsMonitor<FlashSalesOptions> optionsMonitor)
IFlashSaleCurrentResultCache flashSaleCurrentResultCache,
IOptions<FlashSalesOptions> options)
: base(flashSalePlanRepository)
{
FlashSalePlanRepository = flashSalePlanRepository;
@ -99,8 +97,8 @@ public class FlashSalePlanAppService :
DistributedLock = distributedLock;
FlashSalePlanHasher = flashSalePlanHasher;
FlashSaleInventoryManager = flashSaleInventoryManager;
DistributedCache = distributedCache;
Options = optionsMonitor.CurrentValue;
FlashSaleCurrentResultCache = flashSaleCurrentResultCache;
Options = options.Value;
}
public override async Task<FlashSalePlanDto> GetAsync(Guid id)
@ -206,6 +204,7 @@ public class FlashSalePlanAppService :
}
[Authorize]
[UnitOfWork(IsDisabled = true)]
public virtual async Task<FlashSalePlanPreOrderDto> PreOrderAsync(Guid id)
{
await CheckPolicyAsync(PreOrderPolicyName);
@ -222,25 +221,27 @@ public class FlashSalePlanAppService :
return new FlashSalePlanPreOrderDto { ExpiresTime = Clock.Normalize(expiresTime.LocalDateTime), ExpiresInSeconds = Options.PreOrderExpires.TotalSeconds };
}
[DisableAuditing]
[UnitOfWork(IsDisabled = true)]
[Authorize]
public virtual async Task<FlashSaleOrderResultDto> OrderAsync(Guid id, CreateOrderInput input)
public virtual async Task<FlashSaleOrderResultDto> OrderAsync(Guid id, OrderFlashSalePlanInput flashSalePlanInput)
{
var preOrderCache = await GetPreOrderCacheAsync(id);
if (preOrderCache == null)
{
throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired);
return CreateFailureResultDto(FlashSalesErrorCodes.PreOrderExpired);
}
var plan = await GetFlashSalePlanCacheAsync(id);
var now = Clock.Now;
if (plan.BeginTime > now)
{
throw new BusinessException(FlashSalesErrorCodes.FlashSaleNotStarted);
return CreateFailureResultDto(FlashSalesErrorCodes.FlashSaleNotStarted);
}
if (now >= plan.EndTime)
{
throw new BusinessException(FlashSalesErrorCodes.FlashSaleIsOver);
return CreateFailureResultDto(FlashSalesErrorCodes.FlashSaleIsOver);
}
await RemovePreOrderCacheAsync(id);
@ -253,39 +254,66 @@ public class FlashSalePlanAppService :
if (handle == null)
{
throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
return CreateFailureResultDto(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
}
var userFlashSaleResultCache = await GetUserFlashSaleResultCacheAsync(plan.Id);
if (!userFlashSaleResultCache.IsNullOrWhiteSpace())
if (await FlashSaleCurrentResultCache.GetAsync(plan.Id, CurrentUser.GetId()) is not null)
{
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
return CreateFailureResultDto(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
if (!await FlashSaleInventoryManager.TryReduceInventoryAsync(
plan.TenantId, preOrderCache.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true))
if (!await FlashSaleInventoryManager.TryReduceInventoryAsync(plan.TenantId, preOrderCache.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId))
{
return new FlashSaleOrderResultDto() { IsSuccess = false };
await FlashSaleCurrentResultCache.RemoveAsync(plan.Id, CurrentUser.GetId());
return CreateFailureResultDto(FlashSalesErrorCodes.ProductSkuInventoryExceeded);
}
var result = await CreatePendingFlashSaleResultAsync(plan, userId, async (existsResultId) =>
try
{
await SetUserFlashSaleResultCacheAsync(plan.Id, existsResultId);
await FlashSaleInventoryManager.TryRollBackInventoryAsync(
plan.TenantId, preOrderCache.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true
);
});
var createFlashSaleResultEto =
await PrepareCreateFlashSaleResultEtoAsync(plan, flashSalePlanInput, userId, Clock.Now, preOrderCache);
var createFlashSaleOrderEto = await PrepareCreateFlashSaleOrderEtoAsync(plan, result.Id, input, userId, now, preOrderCache.HashToken);
await FlashSaleCurrentResultCache.SetAsync(plan.Id, CurrentUser.GetId(), new FlashSaleCurrentResultCacheItem
{
TenantId = CurrentTenant.Id,
ResultDto = new FlashSaleResultDto
{
Id = createFlashSaleResultEto.ResultId,
StoreId = createFlashSaleResultEto.Plan.StoreId,
PlanId = createFlashSaleResultEto.Plan.Id,
UserId = createFlashSaleResultEto.UserId
}
});
await DistributedEventBus.PublishAsync(createFlashSaleResultEto, false, false);
return new FlashSaleOrderResultDto
{
IsSuccess = true
};
}
catch (Exception e)
{
Logger.LogWarning("Failed to publish the CreateFlashSaleOrderEto event!");
Logger.LogException(e, LogLevel.Warning);
await SetUserFlashSaleResultCacheAsync(plan.Id, result.Id);
await FlashSaleInventoryManager.TryRollBackInventoryAsync(plan.TenantId,
preOrderCache.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId);
await DistributedEventBus.PublishAsync(createFlashSaleOrderEto);
await FlashSaleCurrentResultCache.RemoveAsync(plan.Id, CurrentUser.GetId());
return CreateFailureResultDto(FlashSalesErrorCodes.DistributedEventBusUnavailable);
}
}
return new FlashSaleOrderResultDto() { IsSuccess = true, FlashSaleResultId = result.Id };
protected virtual FlashSaleOrderResultDto CreateFailureResultDto(string errorCode)
{
return new FlashSaleOrderResultDto
{
IsSuccess = false,
ErrorCode = errorCode,
ErrorMessage = L[errorCode]
};
}
#region PreOrderCache
@ -333,30 +361,6 @@ public class FlashSalePlanAppService :
#endregion
#region UserFlashSaleResultCache
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(Guid planId)
{
return Task.FromResult(string.Format(UserFlashSaleResultCacheKeyFormat, CurrentTenant.Id, planId, CurrentUser.GetId()));
}
protected virtual async Task<string> GetUserFlashSaleResultCacheAsync(Guid planId)
{
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(planId);
return await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
}
protected virtual async Task SetUserFlashSaleResultCacheAsync(Guid planId, Guid resultId)
{
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(planId);
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, resultId.ToString(), new DistributedCacheEntryOptions()
{
AbsoluteExpiration = DateTimeOffset.Now.Add(Options.UserFlashSaleResultCacheExpires)
});
}
#endregion
protected virtual Task ValidatePreOrderAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku)
{
if (!product.IsPublished)
@ -402,60 +406,33 @@ public class FlashSalePlanAppService :
return Task.CompletedTask;
}
protected virtual Task<CreateFlashSaleOrderEto> PrepareCreateFlashSaleOrderEtoAsync(
FlashSalePlanCacheItem plan, Guid resultId, CreateOrderInput input,
Guid userId, DateTime now, string hashToken)
protected virtual Task<CreateFlashSaleResultEto> PrepareCreateFlashSaleResultEtoAsync(
FlashSalePlanCacheItem plan, OrderFlashSalePlanInput flashSalePlanInput, Guid userId,
DateTime reducedInventoryTime, FlashSalePlanPreOrderCacheItem preOrderCacheItem)
{
var planEto = ObjectMapper.Map<FlashSalePlanCacheItem, FlashSalePlanEto>(plan);
planEto.TenantId = CurrentTenant.Id;
var eto = new CreateFlashSaleOrderEto()
var eto = new CreateFlashSaleResultEto
{
TenantId = CurrentTenant.Id,
PlanId = plan.Id,
UserId = userId,
PendingResultId = resultId,
StoreId = plan.StoreId,
CreateTime = now,
CustomerRemark = input.CustomerRemark,
ResultId = GuidGenerator.Create(),
ReducedInventoryTime = reducedInventoryTime,
CustomerRemark = flashSalePlanInput.CustomerRemark,
Plan = planEto,
HashToken = hashToken
ProductInventoryProviderName = preOrderCacheItem.InventoryProviderName,
HashToken = preOrderCacheItem.HashToken
};
if (input.ExtraProperties != null)
if (flashSalePlanInput.ExtraProperties != null)
{
foreach (var item in input.ExtraProperties)
{
eto.ExtraProperties.Add(item.Key, item.Value);
}
flashSalePlanInput.MapExtraPropertiesTo(eto, MappingPropertyDefinitionChecks.Source);
}
return Task.FromResult(eto);
}
protected virtual async Task<FlashSaleResult> CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId, Func<Guid, Task> existResultPreProcess)
{
// Prevent repeat submit
var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x =>
x.PlanId == plan.Id && x.UserId == userId && x.Status != FlashSaleResultStatus.Failed && x.Reason != FlashSaleResultFailedReason.InvalidHashToken);
if (existsResult != null)
{
await existResultPreProcess(existsResult.Id);
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
var result = new FlashSaleResult(
id: GuidGenerator.Create(),
tenantId: CurrentTenant.Id,
storeId: plan.StoreId,
planId: plan.Id,
userId: userId
);
return await FlashSaleResultRepository.InsertAsync(result, autoSave: true);
}
protected virtual async Task<ProductCacheItem> GetProductCacheAsync(Guid productId)
{
return await ProductDistributedCache.GetOrAddAsync(productId, async () =>

124
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandler.cs

@ -0,0 +1,124 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Products.Products;
using Microsoft.Extensions.Logging;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class CreateFlashSaleResultEventHandler : IDistributedEventHandler<CreateFlashSaleResultEto>,
ITransientDependency
{
protected IObjectMapper ObjectMapper { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected ILogger<CreateFlashSaleResultEventHandler> Logger { get; }
protected IAbpDistributedLock AbpDistributedLock { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; }
protected IFlashSaleResultRepository FlashSaleResultRepository { get; }
public CreateFlashSaleResultEventHandler(
IObjectMapper objectMapper,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
ILogger<CreateFlashSaleResultEventHandler> logger,
IAbpDistributedLock abpDistributedLock,
IDistributedEventBus distributedEventBus,
IFlashSaleInventoryManager flashSaleInventoryManager,
IFlashSaleCurrentResultCache flashSaleCurrentResultCache,
IFlashSaleResultRepository flashSaleResultRepository)
{
ObjectMapper = objectMapper;
CurrentTenant = currentTenant;
UnitOfWorkManager = unitOfWorkManager;
Logger = logger;
AbpDistributedLock = abpDistributedLock;
DistributedEventBus = distributedEventBus;
FlashSaleInventoryManager = flashSaleInventoryManager;
FlashSaleCurrentResultCache = flashSaleCurrentResultCache;
FlashSaleResultRepository = flashSaleResultRepository;
}
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(CreateFlashSaleResultEto eventData)
{
await using var handle = await AbpDistributedLock.TryAcquireAsync(await GetLockKeyAsync(eventData));
if (handle is null)
{
throw new AbpException("Concurrent flash sale result creation");
}
var ongoingResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x =>
x.PlanId == eventData.Plan.Id &&
x.UserId == eventData.UserId &&
x.Status != FlashSaleResultStatus.Failed);
if (ongoingResult is not null)
{
Logger.LogWarning("Duplicate ongoing flash sale result creation");
await FlashSaleCurrentResultCache.SetAsync(eventData.Plan.Id, eventData.UserId,
new FlashSaleCurrentResultCacheItem
{
TenantId = ongoingResult.TenantId,
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(ongoingResult)
});
// try to roll back the inventory.
UnitOfWorkManager.Current.OnCompleted(async () =>
{
if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync(eventData.TenantId,
eventData.ProductInventoryProviderName, eventData.Plan.StoreId,
eventData.Plan.ProductId, eventData.Plan.ProductSkuId))
{
Logger.LogWarning("Failed to roll back the flash sale inventory.");
}
});
return; // avoid to create a result entity and an order.
}
var result = new FlashSaleResult(
id: eventData.ResultId,
tenantId: CurrentTenant.Id,
storeId: eventData.Plan.StoreId,
planId: eventData.Plan.Id,
userId: eventData.UserId,
reducedInventoryTime: eventData.ReducedInventoryTime
);
var eto = new CreateFlashSaleOrderEto
{
TenantId = eventData.TenantId,
ResultId = eventData.ResultId,
UserId = eventData.UserId,
CustomerRemark = eventData.CustomerRemark,
Plan = eventData.Plan,
HashToken = eventData.HashToken
};
eventData.MapExtraPropertiesTo(eto, MappingPropertyDefinitionChecks.None);
await DistributedEventBus.PublishAsync(eto);
await FlashSaleResultRepository.InsertAsync(result, autoSave: true);
}
protected virtual Task<string> GetLockKeyAsync(CreateFlashSaleResultEto eventData)
{
return Task.FromResult($"eshopflashsales-creating-result_{eventData.Plan.Id}-{eventData.UserId}");
}
}

59
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCache.cs

@ -0,0 +1,59 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.Options;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class FlashSaleCurrentResultCache : IFlashSaleCurrentResultCache, ITransientDependency
{
/// <summary>
/// The <see cref="GetKeyAsync(Guid,Guid)"/> cache key format.
/// <para>{0}: FlashSalePlan ID</para>
/// <para>{1}: User ID</para>
/// </summary>
public const string FlashSaleCurrentResultCacheKeyFormat = "eshopflashsales-current-result_{0}_{1}";
protected IDistributedCache<FlashSaleCurrentResultCacheItem> Cache { get; }
protected FlashSalesOptions Options { get; }
public FlashSaleCurrentResultCache(
IDistributedCache<FlashSaleCurrentResultCacheItem> cache,
IOptions<FlashSalesOptions> options)
{
Cache = cache;
Options = options.Value;
}
public virtual async Task<FlashSaleCurrentResultCacheItem> GetAsync(Guid planId, Guid userId)
{
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId);
return await Cache.GetAsync(flashSaleCurrentResultCacheKey);
}
public virtual async Task SetAsync(Guid planId, Guid userId, FlashSaleCurrentResultCacheItem cacheItem)
{
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId);
await Cache.SetAsync(flashSaleCurrentResultCacheKey, cacheItem,
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.Add(Options.FlashSaleCurrentResultCacheExpires)
});
}
public virtual async Task RemoveAsync(Guid planId, Guid userId)
{
var flashSaleCurrentResultCacheKey = await GetKeyAsync(planId, userId);
await Cache.RemoveAsync(flashSaleCurrentResultCacheKey);
}
protected virtual Task<string> GetKeyAsync(Guid planId, Guid userId)
{
return Task.FromResult(string.Format(FlashSaleCurrentResultCacheKeyFormat, planId, userId));
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCacheItem.cs

@ -0,0 +1,13 @@
using System;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
[Serializable]
public class FlashSaleCurrentResultCacheItem : IMultiTenant
{
public Guid? TenantId { get; set; }
public FlashSaleResultDto ResultDto { get; set; }
}

45
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs

@ -4,7 +4,9 @@ using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.Permissions;
using EasyAbp.EShop.Stores.Stores;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Users;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
@ -17,24 +19,46 @@ public class FlashSaleResultAppService :
protected override string GetPolicyName { get; set; } = FlashSalesPermissions.FlashSaleResult.Default;
protected override string GetListPolicyName { get; set; } = FlashSalesPermissions.FlashSaleResult.Default;
public FlashSaleResultAppService(IFlashSaleResultRepository flashSaleResultRepository) : base(flashSaleResultRepository)
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; }
public FlashSaleResultAppService(
IFlashSaleCurrentResultCache flashSaleCurrentResultCache,
IFlashSaleResultRepository flashSaleResultRepository) : base(flashSaleResultRepository)
{
FlashSaleCurrentResultCache = flashSaleCurrentResultCache;
}
public override async Task<FlashSaleResultDto> GetAsync(Guid id)
[Authorize]
public override Task<FlashSaleResultDto> GetAsync(Guid id)
{
var flashSaleResult = await GetEntityByIdAsync(id);
throw new NotSupportedException();
}
public virtual async Task<FlashSaleResultDto> GetCurrentAsync(Guid planId)
{
await CheckGetPolicyAsync();
var cache = await FlashSaleCurrentResultCache.GetAsync(planId, CurrentUser.GetId());
if (flashSaleResult.UserId == CurrentUser.Id)
if (cache is not null)
{
await CheckGetPolicyAsync();
return cache.ResultDto;
}
else
var list = await GetListAsync(new FlashSaleResultGetListInput
{
MaxResultCount = 1,
Sorting = "Status ASC, CreationTime DESC",
PlanId = planId,
UserId = CurrentUser.GetId()
});
if (list.TotalCount == 0)
{
await CheckMultiStorePolicyAsync(flashSaleResult.StoreId, FlashSalesPermissions.FlashSaleResult.Manage);
throw new EntityNotFoundException(typeof(FlashSaleResult));
}
return await MapToGetOutputDtoAsync(flashSaleResult);
return list.Items[0];
}
public override async Task<PagedResultDto<FlashSaleResultDto>> GetListAsync(FlashSaleResultGetListInput input)
@ -51,7 +75,8 @@ public class FlashSaleResultAppService :
return await base.GetListAsync(input);
}
protected override async Task<IQueryable<FlashSaleResult>> CreateFilteredQueryAsync(FlashSaleResultGetListInput input)
protected override async Task<IQueryable<FlashSaleResult>> CreateFilteredQueryAsync(
FlashSaleResultGetListInput input)
{
return (await base.CreateFilteredQueryAsync(input))
.WhereIf(input.StoreId.HasValue, x => x.StoreId == input.StoreId.Value)
@ -60,4 +85,4 @@ public class FlashSaleResultAppService :
.WhereIf(input.UserId.HasValue, x => x.UserId == input.UserId.Value)
.WhereIf(input.OrderId.HasValue, x => x.OrderId == input.OrderId.Value);
}
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleCurrentResultCache.cs

@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public interface IFlashSaleCurrentResultCache
{
Task<FlashSaleCurrentResultCacheItem> GetAsync(Guid planId, Guid userId);
Task SetAsync(Guid planId, Guid userId, FlashSaleCurrentResultCacheItem cacheItem);
Task RemoveAsync(Guid planId, Guid userId);
}

4
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/Options/FlashSalesOptions.cs

@ -1,6 +1,6 @@
using System;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
namespace EasyAbp.EShop.Plugins.FlashSales.Options;
public class FlashSalesOptions
{
@ -12,5 +12,5 @@ public class FlashSalesOptions
/// <summary>
/// Default: 5 minutes
/// </summary>
public TimeSpan UserFlashSaleResultCacheExpires { get; set; } = TimeSpan.FromMinutes(5);
public TimeSpan FlashSaleCurrentResultCacheExpires { get; set; } = TimeSpan.FromMinutes(5);
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs

@ -18,16 +18,16 @@ public class FlashSaleInventoryManager : IFlashSaleInventoryManager, ITransientD
}
public virtual async Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId,
Guid productId, Guid productSkuId, int quantity, bool increaseSold)
Guid productId, Guid productSkuId)
{
return await FlashSaleInventoryReducerAppService.TryReduceAsync(new ReduceInventoryInput(
tenantId, providerName, storeId, productId, productSkuId, quantity, increaseSold));
tenantId, providerName, storeId, productId, productSkuId, 1, true));
}
public virtual async Task<bool> TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId,
Guid productId, Guid productSkuId, int quantity, bool decreaseSold)
Guid productId, Guid productSkuId)
{
return await FlashSaleInventoryReducerAppService.TryIncreaseAsync(new IncreaseInventoryInput(
tenantId, providerName, storeId, productId, productSkuId, quantity, decreaseSold));
tenantId, providerName, storeId, productId, productSkuId, 1, true));
}
}

8
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs

@ -9,16 +9,10 @@ public class CreateFlashSaleOrderEto : ExtensibleObject, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid StoreId { get; set; }
public Guid PlanId { get; set; }
public Guid ResultId { get; set; }
public Guid UserId { get; set; }
public Guid PendingResultId { get; set; }
public DateTime CreateTime { get; set; }
public string CustomerRemark { get; set; }
public FlashSalePlanEto Plan { get; set; }

12
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs → plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs

@ -4,11 +4,11 @@ using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class CreateFlashSaleOrderCompleteEto : ExtensibleObject, IMultiTenant
public class FlashSaleOrderCreationResultEto : ExtensibleObject, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid PendingResultId { get; set; }
public Guid ResultId { get; set; }
public bool Success { get; set; }
@ -21,4 +21,12 @@ public class CreateFlashSaleOrderCompleteEto : ExtensibleObject, IMultiTenant
public Guid UserId { get; set; }
public Guid? OrderId { get; set; }
public string ProductInventoryProviderName { get; set; }
public Guid ProductId { get; set; }
public Guid ProductSkuId { get; set; }
public bool AllowToTryAgain { get; set; }
}

26
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEto.cs

@ -0,0 +1,26 @@
using System;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
[Serializable]
public class CreateFlashSaleResultEto : ExtensibleObject, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid ResultId { get; set; }
public Guid UserId { get; set; }
public DateTime ReducedInventoryTime { get; set; }
public string CustomerRemark { get; set; }
public FlashSalePlanEto Plan { get; set; }
public string ProductInventoryProviderName { get; set; }
public string HashToken { get; set; }
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs

@ -2,7 +2,5 @@
public static class FlashSaleResultFailedReason
{
public const string InsufficientInventory = "InsufficientInventory";
public const string InvalidHashToken = "InvalidHashToken";
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs

@ -29,4 +29,6 @@ public static class FlashSalesErrorCodes
public const string RelatedFlashSaleResultsExist = $"{Namespace}:{nameof(RelatedFlashSaleResultsExist)}";
public const string FlashSaleResultStatusNotPending = $"{Namespace}:{nameof(FlashSaleResultStatusNotPending)}";
public const string DistributedEventBusUnavailable = $"{Namespace}:{nameof(DistributedEventBusUnavailable)}";
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json

@ -14,6 +14,7 @@
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "Duplicate flash-sales order",
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "Related flash-sales results exist",
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "The status of flash-sale result has been changed",
"EasyAbp.EShop.Plugins.FlashSales:Menu:DistributedEventBusUnavailable": "The distributed event bus is unavailable",
"Menu:FlashSalesManagement": "Flash-Sales Management",
"Permission:FlashSalePlan": "FlashSale Plan",
"Permission:Manage": "Manage",
@ -41,6 +42,7 @@
"FlashSaleResultReason": "Reason",
"FlashSaleResultUserId": "User ID",
"FlashSaleResultOrderId": "Order ID",
"FlashSaleResultReducedInventoryTime": "Inventory Reduction Time",
"FlashSaleResultCreationTime": "Creation Time",
"ViewFlashSaleResult": "View",
"Enum:FlashSaleResultStatus.Pending": "Pending",

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json

@ -14,6 +14,7 @@
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "重复闪购下单",
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "已存在关联的闪购结果",
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "闪购结果的状态已经变更",
"EasyAbp.EShop.Plugins.FlashSales:Menu:DistributedEventBusUnavailable": "分布式事件总线不可用",
"Menu:FlashSalesManagement": "闪购",
"Permission:FlashSalePlan": "闪购计划",
"Permission:Manage": "管理",
@ -41,6 +42,7 @@
"FlashSaleResultReason": "原因",
"FlashSaleResultUserId": "用户 ID",
"FlashSaleResultOrderId": "订单 ID",
"FlashSaleResultReducedInventoryTime": "扣库存时间",
"FlashSaleResultCreationTime": "创建时间",
"ViewFlashSaleResult": "查看",
"Enum:FlashSaleResultStatus.Pending": "等待中",

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json

@ -14,6 +14,7 @@
"EasyAbp.EShop.Plugins.FlashSales:DuplicateFlashSalesOrder": "重複閃購下單",
"EasyAbp.EShop.Plugins.FlashSales:RelatedFlashSaleResultsExist": "已存在關聯的閃購結果",
"EasyAbp.EShop.Plugins.FlashSales:FlashSaleResultStatusNotPending": "閃購結果的状态已經變更",
"EasyAbp.EShop.Plugins.FlashSales:Menu:DistributedEventBusUnavailable": "分佈式事件總線不可用",
"Menu:FlashSalesManagement": "閃購管理",
"Permission:FlashSalePlan": "閃購計畫",
"Permission:Manage": "管理",
@ -41,6 +42,7 @@
"FlashSaleResultReason": "原因",
"FlashSaleResultUserId": "用戶 ID",
"FlashSaleResultOrderId": "訂單 ID",
"FlashSaleResultReducedInventoryTime": "扣庫存時間",
"FlashSaleResultCreationTime": "創建時間",
"ViewFlashSaleResult": "查看",
"Enum:FlashSaleResultStatus.Pending": "等待中",

37
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs

@ -1,37 +0,0 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class CreateFlashSaleOrderCompleteEventHandler : IDistributedEventHandler<CreateFlashSaleOrderCompleteEto>, ITransientDependency
{
protected IFlashSaleResultRepository FlashSaleResultRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
public CreateFlashSaleOrderCompleteEventHandler(IFlashSaleResultRepository flashSaleResultRepository, IGuidGenerator guidGenerator)
{
FlashSaleResultRepository = flashSaleResultRepository;
GuidGenerator = guidGenerator;
}
[UnitOfWork]
public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData)
{
var flashSaleResult = await FlashSaleResultRepository.GetAsync(eventData.PendingResultId);
if (eventData.Success)
{
flashSaleResult.MarkAsSuccessful(eventData.OrderId.Value);
}
else
{
flashSaleResult.MarkAsFailed(eventData.Reason);
}
await FlashSaleResultRepository.UpdateAsync(flashSaleResult, autoSave: true);
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs

@ -1,12 +1,12 @@
using System;
using EasyAbp.EShop.Stores.Stores;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class FlashSaleResult : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMultiStore
public class FlashSaleResult : FullAuditedAggregateRoot<Guid>, IFlashSaleResult, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
@ -22,16 +22,19 @@ public class FlashSaleResult : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMu
public virtual Guid? OrderId { get; protected set; }
public virtual DateTime ReducedInventoryTime { get; protected set; }
protected FlashSaleResult() { }
public FlashSaleResult(Guid id, Guid? tenantId, Guid storeId, Guid planId, Guid userId)
: base(id)
public FlashSaleResult(
Guid id, Guid? tenantId, Guid storeId, Guid planId, Guid userId, DateTime reducedInventoryTime) : base(id)
{
TenantId = tenantId;
StoreId = storeId;
PlanId = planId;
Status = FlashSaleResultStatus.Pending;
UserId = userId;
ReducedInventoryTime = reducedInventoryTime;
}
public void MarkAsSuccessful(Guid orderId)
@ -44,7 +47,7 @@ public class FlashSaleResult : FullAuditedAggregateRoot<Guid>, IMultiTenant, IMu
OrderId = orderId;
}
public void MarkAsFailed(string reason)
public void MarkAsFailed([NotNull] string reason)
{
if (Status != FlashSaleResultStatus.Pending)
{

17
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResult.cs

@ -0,0 +1,17 @@
using System;
using EasyAbp.EShop.Stores.Stores;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public interface IFlashSaleResult : IMultiStore
{
Guid PlanId { get; }
FlashSaleResultStatus Status { get; }
string Reason { get; }
Guid UserId { get; }
Guid? OrderId { get; }
}

9
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs

@ -4,6 +4,8 @@ using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
@ -51,15 +53,18 @@ public class FlashSalePlanController :
return Service.DeleteAsync(id);
}
[UnitOfWork(IsDisabled = true)]
[HttpPost("{id}/pre-order")]
public virtual Task<FlashSalePlanPreOrderDto> PreOrderAsync(Guid id)
{
return Service.PreOrderAsync(id);
}
[DisableAuditing]
[UnitOfWork(IsDisabled = true)]
[HttpPost("{id}/order")]
public virtual Task<FlashSaleOrderResultDto> OrderAsync(Guid id, CreateOrderInput input)
public virtual Task<FlashSaleOrderResultDto> OrderAsync(Guid id, OrderFlashSalePlanInput flashSalePlanInput)
{
return Service.OrderAsync(id, input);
return Service.OrderAsync(id, flashSalePlanInput);
}
}

6
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultController.cs

@ -30,4 +30,10 @@ public class FlashSaleResultController : FlashSalesController, IFlashSaleResultA
{
return Service.GetListAsync(input);
}
[HttpGet("current/{planId}")]
public virtual Task<FlashSaleResultDto> GetCurrentAsync(Guid planId)
{
return Service.GetCurrentAsync(planId);
}
}

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/ViewModels/ViewFlashSaleResultViewModel.cs

@ -23,4 +23,7 @@ public class ViewFlashSaleResultViewModel
[Display(Name = "FlashSaleResultOrderId")]
public Guid? OrderId { get; set; }
[Display(Name = "FlashSaleResultReducedInventoryTime")]
public DateTime ReducedInventoryTime { get; set; }
}

5
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/index.js

@ -60,6 +60,11 @@ $(function () {
data: "creationTime",
dataFormat: 'datetime'
},
{
title: l('FlashSaleResultReducedInventoryTime'),
data: "reducedInventoryTime",
dataFormat: 'datetime'
},
]
}));

4
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs

@ -6,8 +6,8 @@ namespace EasyAbp.Eshop.Products.Products;
public interface IFlashSaleInventoryManager
{
Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool increaseSold);
Guid productSkuId);
Task<bool> TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool decreaseSold);
Guid productSkuId);
}

8
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs

@ -22,9 +22,7 @@ public class FlashSaleInventoryAppService : ProductsAppService, IFlashSaleInvent
input.ProviderName,
input.StoreId,
input.ProductId,
input.ProductSkuId,
input.Quantity,
input.IncreaseSold
input.ProductSkuId
);
}
@ -37,9 +35,7 @@ public class FlashSaleInventoryAppService : ProductsAppService, IFlashSaleInvent
input.ProviderName,
input.StoreId,
input.ProductId,
input.ProductSkuId,
input.Quantity,
input.ReduceSold
input.ProductSkuId
);
}
}

8
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs

@ -16,18 +16,18 @@ public class LocalFlashSaleInventoryManager : ILocalFlashSaleInventoryManager, I
}
public virtual async Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool increaseSold)
Guid productSkuId)
{
var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId);
return await (await ProductInventoryProviderResolver.GetAsync(providerName))
.TryReduceInventoryAsync(model, quantity, increaseSold, true);
.TryReduceInventoryAsync(model, 1, true, true);
}
public virtual async Task<bool> TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool decreaseSold)
Guid productSkuId)
{
var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId);
return await (await ProductInventoryProviderResolver.GetAsync(providerName))
.TryIncreaseInventoryAsync(model, quantity, decreaseSold, true);
.TryIncreaseInventoryAsync(model, 1, true, true);
}
}

23
plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Plugins.FlashSales;
using EasyAbp.EShop.Plugins.FlashSales;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.ProductDetails;
@ -93,14 +94,11 @@ public class CreateFlashSaleOrderEventHandlerTests : OrdersPluginsFlashSalesAppl
OrderRepository.InsertAsync(default, default, default)
.ReturnsForAnyArgs(callInfo => callInfo.Arg<Order>());
var createFlashSaleOrderEto = new CreateFlashSaleOrderEto()
var createFlashSaleOrderEto = new CreateFlashSaleOrderEto
{
TenantId = CurrentTenant.Id,
PlanId = FlashSalesTestData.Plan1Id,
StoreId = FlashSalesTestData.Store1Id,
UserId = CurrentUser.GetId(),
PendingResultId = FlashSalesTestData.Result1Id,
CreateTime = DateTime.Now,
ResultId = FlashSalesTestData.Result1Id,
CustomerRemark = "My Remark",
HashToken = "My Hash Token",
Plan = new FlashSalePlanEto
@ -119,9 +117,9 @@ public class CreateFlashSaleOrderEventHandlerTests : OrdersPluginsFlashSalesAppl
await EventHandler.HandleEventAsync(createFlashSaleOrderEto);
await DistributedEventBus.Received()
.PublishAsync(Arg.Is<CreateFlashSaleOrderCompleteEto>(eto =>
.PublishAsync(Arg.Is<FlashSaleOrderCreationResultEto>(eto =>
eto.TenantId == CurrentTenant.Id &&
eto.PendingResultId == FlashSalesTestData.Result1Id &&
eto.ResultId == FlashSalesTestData.Result1Id &&
eto.Success &&
eto.StoreId == FlashSalesTestData.Store1Id &&
eto.PlanId == FlashSalesTestData.Plan1Id &&
@ -137,14 +135,11 @@ public class CreateFlashSaleOrderEventHandlerTests : OrdersPluginsFlashSalesAppl
FlashSalePlanHasher.HashAsync(default, default, default)
.ReturnsForAnyArgs("My Hash Token");
var createFlashSaleOrderEto = new CreateFlashSaleOrderEto()
var createFlashSaleOrderEto = new CreateFlashSaleOrderEto
{
TenantId = CurrentTenant.Id,
PlanId = FlashSalesTestData.Plan1Id,
StoreId = FlashSalesTestData.Store1Id,
UserId = CurrentUser.GetId(),
PendingResultId = FlashSalesTestData.Result1Id,
CreateTime = DateTime.Now,
ResultId = FlashSalesTestData.Result1Id,
CustomerRemark = "My Remark",
HashToken = "My Hash Token Failed",
Plan = new FlashSalePlanEto
@ -163,9 +158,9 @@ public class CreateFlashSaleOrderEventHandlerTests : OrdersPluginsFlashSalesAppl
await EventHandler.HandleEventAsync(createFlashSaleOrderEto);
await DistributedEventBus.Received()
.PublishAsync(Arg.Is<CreateFlashSaleOrderCompleteEto>(eto =>
.PublishAsync(Arg.Is<FlashSaleOrderCreationResultEto>(eto =>
eto.TenantId == CurrentTenant.Id &&
eto.PendingResultId == FlashSalesTestData.Result1Id &&
eto.ResultId == FlashSalesTestData.Result1Id &&
!eto.Success &&
eto.StoreId == FlashSalesTestData.Store1Id &&
eto.PlanId == FlashSalesTestData.Plan1Id &&

4
plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs

@ -1,7 +1,7 @@
using EasyAbp.EShop.Orders.Plugins.FlashSales;
using EasyAbp.EShop.Plugins.FlashSales;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Plugins.FlashSales;
namespace EasyAbp.EShop.Orders.Plugins.FlashSales;
[DependsOn(
typeof(EShopOrdersPluginsFlashSalesApplicationModule),

3
plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Plugins.FlashSales;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Plugins.FlashSales;
namespace EasyAbp.EShop.Orders.Plugins.FlashSales;
/* Inherit from this class for your application layer tests.
* See SampleAppService_Tests for example.

165
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandlerTest.cs

@ -1,165 +0,0 @@
using System;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute;
using NSubstitute.ReceivedExtensions;
using Shouldly;
using Volo.Abp.Users;
using Xunit;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class CreateFlashSaleOrderFailedEventHandlerTest : FlashSalesApplicationTestBase
{
protected CreateFlashSaleOrderFailedEventHandler EventHandler { get; }
protected IDistributedCache DistributedCache { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; set; }
private ProductDto Product1 { get; set; }
public CreateFlashSaleOrderFailedEventHandlerTest()
{
EventHandler = GetRequiredService<CreateFlashSaleOrderFailedEventHandler >();
DistributedCache = GetRequiredService<IDistributedCache>();
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
Product1 = CreateMockProductDto();
var productAppService = Substitute.For<IProductAppService>();
productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1));
services.Replace(ServiceDescriptor.Singleton(productAppService));
FlashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>();
services.Replace(ServiceDescriptor.Singleton(FlashSaleInventoryManager));
base.AfterAddApplication(services);
}
[Fact]
public async Task HandleEventAsync()
{
var plan = await CreateFlashSalePlanAsync();
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId());
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString());
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = pendingFlashResult.TenantId,
PendingResultId = pendingFlashResult.Id,
Success = false,
StoreId = pendingFlashResult.StoreId,
PlanId = pendingFlashResult.PlanId,
OrderId = null,
Reason = FlashSaleResultFailedReason.InvalidHashToken,
UserId = pendingFlashResult.UserId
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true)
.Returns(Task.FromResult(true));
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
userFlashSaleResultCache.ShouldBeNull();
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true);
}
[Fact]
public async Task HandleEventAsync_Should_Not_Remove_UserFlashSaleResultCache_When_TryRollBackInventory_Failed()
{
var plan = await CreateFlashSalePlanAsync();
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId());
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString());
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = pendingFlashResult.TenantId,
PendingResultId = pendingFlashResult.Id,
Success = false,
StoreId = pendingFlashResult.StoreId,
PlanId = pendingFlashResult.PlanId,
OrderId = null,
Reason = FlashSaleResultFailedReason.InvalidHashToken,
UserId = pendingFlashResult.UserId
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true)
.Returns(Task.FromResult(false));
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString());
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(plan.TenantId, Product1.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true);
}
[Fact]
public async Task HandleEventAsync_Should_Ignore_When_Success_Is_True()
{
var plan = await CreateFlashSalePlanAsync();
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId());
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString());
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = pendingFlashResult.TenantId,
PendingResultId = pendingFlashResult.Id,
Success = true,
StoreId = pendingFlashResult.StoreId,
PlanId = pendingFlashResult.PlanId,
OrderId = Guid.NewGuid(),
Reason = null,
UserId = pendingFlashResult.UserId
};
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString());
await FlashSaleInventoryManager.DidNotReceiveWithAnyArgs()
.TryRollBackInventoryAsync(default, default, Guid.Empty, Guid.Empty, Guid.Empty, default, default);
}
[Fact]
public async Task HandleEventAsync_Should_Ignore_When_Reason_Not_InvalidHashToken()
{
var plan = await CreateFlashSalePlanAsync();
var pendingFlashResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, CurrentUser.GetId());
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, pendingFlashResult.Id.ToString());
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = pendingFlashResult.TenantId,
PendingResultId = pendingFlashResult.Id,
Success = false,
StoreId = pendingFlashResult.StoreId,
PlanId = pendingFlashResult.PlanId,
OrderId = null,
Reason = "Other",
UserId = pendingFlashResult.UserId
};
await EventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var userFlashSaleResultCache = await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
userFlashSaleResultCache.ShouldBe(pendingFlashResult.Id.ToString());
await FlashSaleInventoryManager.DidNotReceiveWithAnyArgs()
.TryRollBackInventoryAsync(default, default, Guid.Empty, Guid.Empty, Guid.Empty, default, default);
}
}

257
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandlerTests.cs

@ -0,0 +1,257 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute;
using Shouldly;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Users;
using Xunit;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
public class FlashSaleOrderCreationResultEventHandlerTests : FlashSalesApplicationTestBase
{
protected IObjectMapper ObjectMapper { get; }
protected IFlashSaleCurrentResultCache FlashSaleCurrentResultCache { get; }
protected FlashSaleOrderCreationResultEventHandler FlashSaleOrderCreationResultEventHandler { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; set; }
private ProductDto Product1 { get; set; }
public FlashSaleOrderCreationResultEventHandlerTests()
{
ObjectMapper = GetRequiredService<IObjectMapper>();
FlashSaleCurrentResultCache = GetRequiredService<IFlashSaleCurrentResultCache>();
FlashSaleOrderCreationResultEventHandler = GetRequiredService<FlashSaleOrderCreationResultEventHandler>();
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
Product1 = CreateMockProductDto();
var productAppService = Substitute.For<IProductAppService>();
productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1));
services.Replace(ServiceDescriptor.Singleton(productAppService));
var productDetailAppService = Substitute.For<IProductDetailAppService>();
services.Replace(ServiceDescriptor.Singleton(productDetailAppService));
productDetailAppService.GetAsync(FlashSalesTestData.ProductDetail1Id).Returns(Task.FromResult(
new ProductDetailDto
{
Id = FlashSalesTestData.ProductDetail1Id,
CreationTime = FlashSalesTestData.ProductDetailLastModificationTime,
LastModificationTime = FlashSalesTestData.ProductDetailLastModificationTime,
StoreId = FlashSalesTestData.Store1Id,
Description = "My Details 1"
}));
productDetailAppService.GetAsync(FlashSalesTestData.ProductDetail2Id).Returns(Task.FromResult(
new ProductDetailDto
{
Id = FlashSalesTestData.ProductDetail2Id,
StoreId = FlashSalesTestData.Store1Id,
Description = "My Details 2"
}));
var flashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>();
services.Replace(ServiceDescriptor.Singleton(flashSaleInventoryManager));
base.AfterAddApplication(services);
}
protected async Task SetFlashSaleCurrentResultCacheAsync(FlashSaleResult flashSaleResult)
{
await FlashSaleCurrentResultCache.SetAsync(flashSaleResult.PlanId, flashSaleResult.UserId,
new FlashSaleCurrentResultCacheItem
{
TenantId = CurrentTenant.Id,
ResultDto = ObjectMapper.Map<FlashSaleResult, FlashSaleResultDto>(flashSaleResult)
});
}
[Fact]
public async Task HandleEventAsync_When_Create_Order_Success()
{
var plan = await CreateFlashSalePlanAsync();
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult);
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto
{
TenantId = flashSaleResult.TenantId,
ResultId = flashSaleResult.Id,
Success = true,
StoreId = flashSaleResult.StoreId,
PlanId = flashSaleResult.PlanId,
OrderId = GuidGenerator.Create(),
Reason = null,
UserId = flashSaleResult.UserId,
ProductInventoryProviderName = Product1.InventoryProviderName,
ProductId = plan.ProductId,
ProductSkuId = plan.ProductSkuId,
AllowToTryAgain = false
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId)
.Returns(Task.FromResult(true));
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto);
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Successful);
flashResult.OrderId.ShouldBe(flashSaleOrderCreationResultEto.OrderId);
flashResult.Reason.ShouldBe(null);
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId);
flashSaleCurrentResultCache.ShouldNotBeNull();
flashSaleCurrentResultCache.ResultDto.Id.ShouldBe(flashSaleResult.Id);
flashSaleCurrentResultCache.ResultDto.Status.ShouldBe(FlashSaleResultStatus.Successful);
await FlashSaleInventoryManager.DidNotReceive()
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId);
}
[Fact]
public async Task HandleEventAsync_When_Create_Order_Failed_And_Not_Allow_To_Try_Again()
{
var plan = await CreateFlashSalePlanAsync();
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult);
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto
{
TenantId = flashSaleResult.TenantId,
ResultId = flashSaleResult.Id,
Success = false,
StoreId = FlashSalesTestData.Store1Id,
PlanId = flashSaleResult.PlanId,
OrderId = null,
Reason = "Failed reason",
UserId = flashSaleResult.UserId,
ProductInventoryProviderName = Product1.InventoryProviderName,
ProductId = plan.ProductId,
ProductSkuId = plan.ProductSkuId,
AllowToTryAgain = false
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId)
.Returns(Task.FromResult(true));
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto);
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed);
flashResult.OrderId.ShouldBe(null);
flashResult.Reason.ShouldBe("Failed reason");
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId);
flashSaleCurrentResultCache.ShouldNotBeNull();
flashSaleCurrentResultCache.ResultDto.Id.ShouldBe(flashSaleResult.Id);
flashSaleCurrentResultCache.ResultDto.Status.ShouldBe(FlashSaleResultStatus.Failed);
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId);
}
[Fact]
public async Task HandleEventAsync_When_Create_Order_Failed_And_Allow_To_Try_Again()
{
var plan = await CreateFlashSalePlanAsync();
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult);
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto
{
TenantId = flashSaleResult.TenantId,
ResultId = flashSaleResult.Id,
Success = false,
StoreId = FlashSalesTestData.Store1Id,
PlanId = flashSaleResult.PlanId,
OrderId = null,
Reason = "Failed reason",
UserId = flashSaleResult.UserId,
ProductInventoryProviderName = Product1.InventoryProviderName,
ProductId = plan.ProductId,
ProductSkuId = plan.ProductSkuId,
AllowToTryAgain = true
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId)
.Returns(Task.FromResult(true));
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto);
var flashResult = await FlashSaleResultRepository.GetAsync(flashSaleResult.Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed);
flashResult.OrderId.ShouldBe(null);
flashResult.Reason.ShouldBe("Failed reason");
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(flashResult.PlanId, flashResult.UserId);
flashSaleCurrentResultCache.ShouldBeNull();
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId);
}
[Fact]
public async Task Should_Not_Remove_FlashSaleCurrentResultCache_When_TryRollBackInventory_Failed()
{
var plan = await CreateFlashSalePlanAsync();
var flashSaleResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, CurrentUser.GetId());
await SetFlashSaleCurrentResultCacheAsync(flashSaleResult);
var flashSaleOrderCreationResultEto = new FlashSaleOrderCreationResultEto
{
TenantId = flashSaleResult.TenantId,
ResultId = flashSaleResult.Id,
Success = false,
StoreId = flashSaleResult.StoreId,
PlanId = flashSaleResult.PlanId,
OrderId = null,
Reason = FlashSaleResultFailedReason.InvalidHashToken,
UserId = flashSaleResult.UserId,
ProductInventoryProviderName = Product1.InventoryProviderName,
ProductId = plan.ProductId,
ProductSkuId = plan.ProductSkuId,
AllowToTryAgain = true
};
FlashSaleInventoryManager
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId)
.Returns(Task.FromResult(false));
await FlashSaleOrderCreationResultEventHandler.HandleEventAsync(flashSaleOrderCreationResultEto);
var flashSaleCurrentResultCache = await FlashSaleCurrentResultCache.GetAsync(plan.Id, CurrentUser.GetId());
flashSaleCurrentResultCache.ShouldNotBeNull();
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(flashSaleOrderCreationResultEto.TenantId,
flashSaleOrderCreationResultEto.ProductInventoryProviderName, flashSaleOrderCreationResultEto.StoreId,
flashSaleOrderCreationResultEto.ProductId, flashSaleOrderCreationResultEto.ProductSkuId);
}
}

119
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs

@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.Options;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -15,9 +15,11 @@ using NSubstitute;
using Shouldly;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Users;
using Xunit;
@ -28,12 +30,15 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
protected IDistributedEventBus DistributedEventBus { get; }
protected IDistributedCache<ProductCacheItem, Guid> ProductDistributedCache { get; }
private ProductDto Product1 { get; set; }
public FlashSalePlanAppServiceTests()
{
AppService = GetRequiredService<FlashSalePlanAppService>();
DistributedEventBus = GetRequiredService<IDistributedEventBus>();
ProductDistributedCache = GetRequiredService<IDistributedCache<ProductCacheItem, Guid>>();
}
protected override void AfterAddApplication(IServiceCollection services)
@ -48,6 +53,12 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
var distributedEventBus = Substitute.For<IDistributedEventBus>();
services.Replace(ServiceDescriptor.Singleton(distributedEventBus));
ObjectExtensionManager.Instance.AddOrUpdate<OrderFlashSalePlanInput>(info =>
{
info.AddOrUpdateProperty<string>("key1");
});
base.AfterAddApplication(services);
}
@ -326,13 +337,14 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
.Code.ShouldBe(FlashSalesErrorCodes.ProductIsNotPublished);
Product1.IsPublished = true;
Product1.InventoryStrategy = InventoryStrategy.ReduceAfterPlacing;
await ProductDistributedCache.RemoveAsync(Product1.Id);
await AppService.PreOrderAsync(plan.Id)
.ShouldThrowAsync<UnexpectedInventoryStrategyException>();
Product1.InventoryStrategy = InventoryStrategy.FlashSales;
await ProductDistributedCache.RemoveAsync(Product1.Id);
var plan2 = await CreateFlashSalePlanAsync(isPublished: false);
await AppService.PreOrderAsync(plan2.Id)
@ -350,25 +362,23 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
var plan = await CreateFlashSalePlanAsync();
var hashToken = await GetRequiredService<IFlashSalePlanHasher>()
.HashAsync(plan.LastModificationTime, Product1.LastModificationTime, Product1.GetSkuById(plan.ProductSkuId).LastModificationTime);
var createOrderInput = new CreateOrderInput()
var orderFlashSalePlanInput = new OrderFlashSalePlanInput
{
CustomerRemark = "remark1",
ExtraProperties = { { "key1", "value1" } }
CustomerRemark = "remark1"
};
orderFlashSalePlanInput.SetProperty("key1", "value1");
await AppService.PreOrderAsync(plan.Id);
var result = await AppService.OrderAsync(plan.Id, createOrderInput);
var result = await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput);
result.IsSuccess.ShouldBe(true);
result.FlashSaleResultId.ShouldNotBeNull();
await DistributedEventBus.Received().PublishAsync(Arg.Is<CreateFlashSaleOrderEto>(eto =>
await DistributedEventBus.Received().PublishAsync(Arg.Is<CreateFlashSaleResultEto>(eto =>
eto.TenantId == plan.TenantId &&
eto.StoreId == plan.StoreId &&
eto.PlanId == plan.Id &&
eto.UserId == CurrentUser.GetId() &&
eto.HashToken == hashToken &&
eto.CustomerRemark == createOrderInput.CustomerRemark &&
eto.CustomerRemark == orderFlashSalePlanInput.CustomerRemark &&
eto.Plan != null &&
eto.Plan.Id == plan.Id &&
eto.Plan.TenantId == plan.TenantId &&
eto.Plan.StoreId == plan.StoreId &&
eto.Plan.BeginTime == plan.BeginTime &&
@ -378,104 +388,121 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
eto.Plan.IsPublished == plan.IsPublished &&
eto.ExtraProperties.ContainsKey("key1") &&
eto.ExtraProperties["key1"].ToString() == "value1"
));
), false, false);
}
[Fact]
public async Task OrderAsync_Throw_Exception_When_Not_PreOrder()
{
var plan = await CreateFlashSalePlanAsync();
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.PreOrderExpired);
(await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput)).ErrorCode
.ShouldBe(FlashSalesErrorCodes.PreOrderExpired);
}
[Fact]
public async Task OrderAsync_Throw_Exception_When_FlashSaleNotStarted()
{
var plan = await CreateFlashSalePlanAsync(timeRange: CreateTimeRange.NotStart);
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.FlashSaleNotStarted);
(await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput))
.ErrorCode.ShouldBe(FlashSalesErrorCodes.FlashSaleNotStarted);
}
[Fact]
public async Task OrderAsync_Throw_Exception_When_FlashSaleIsOver()
{
var plan = await CreateFlashSalePlanAsync(timeRange: CreateTimeRange.WillBeExpired);
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
await Task.Delay(TimeSpan.FromSeconds(1.2));
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.FlashSaleIsOver);
(await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput))
.ErrorCode.ShouldBe(FlashSalesErrorCodes.FlashSaleIsOver);
}
[Fact]
public async Task OrderAsync_Throw_Exception_When_BusyToCreateFlashSaleOrder()
{
var plan = await CreateFlashSalePlanAsync();
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
var distributedLock = GetRequiredService<IAbpDistributedLock>();
var lockKey = $"create-flash-sale-order-{plan.Id}-{CurrentUser.GetId()}";
await using var handle = await distributedLock.TryAcquireAsync(lockKey);
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
(await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput))
.ErrorCode.ShouldBe(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
}
[Fact]
public async Task OrderAsync_Throw_Exception_When_Exist_UserFlashSaleResultCache()
public async Task OrderAsync_Throw_Exception_When_Exist_FlashSaleCurrentResultCache()
{
var plan = await CreateFlashSalePlanAsync();
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
var distributedCache = GetRequiredService<IDistributedCache>();
var flashSaleCurrentResultCache = GetRequiredService<IFlashSaleCurrentResultCache>();
var userId = CurrentUser.GetId();
var userFlashSaleResultCacheKey = string.Format(FlashSalePlanAppService.UserFlashSaleResultCacheKeyFormat, plan.TenantId, plan.Id, userId);
await distributedCache.SetStringAsync(userFlashSaleResultCacheKey, Guid.NewGuid().ToString());
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
await flashSaleCurrentResultCache.SetAsync(plan.Id, userId,
new FlashSaleCurrentResultCacheItem
{
TenantId = CurrentTenant.Id,
ResultDto = new FlashSaleResultDto
{
Id = Guid.NewGuid(),
StoreId = plan.StoreId,
PlanId = plan.Id,
UserId = userId
}
});
(await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput))
.ErrorCode.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
[Fact]
public async Task OrderAsync_Return_False_When_TryReduceInventory_Failed()
{
var plan = await CreateFlashSalePlanAsync();
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
FakeFlashSaleInventoryManager.ShouldReduceSuccess = false;
var result = await AppService.OrderAsync(plan.Id, createOrderInput);
var result = await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput);
result.IsSuccess.ShouldBe(false);
result.FlashSaleResultId.ShouldBeNull();
}
[Fact]
public async Task OrderAsync_Return_False_When_Exist_Not_Failed_Result()
{
var plan = await CreateFlashSalePlanAsync();
var createOrderInput = new CreateOrderInput();
var orderFlashSalePlanInput = new OrderFlashSalePlanInput();
await AppService.PreOrderAsync(plan.Id);
var userId = GetRequiredService<ICurrentUser>().GetId();
await CreatePendingResultAsync(plan.Id, plan.StoreId, userId);
var existingResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, userId);
(await AppService.OrderAsync(plan.Id, createOrderInput)
.ShouldThrowAsync<BusinessException>())
.Code.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
Guid? newResultId = null;
DistributedEventBus.PublishAsync<CreateFlashSaleResultEto>(null, false, false).ReturnsForAnyArgs(async info =>
{
var eto = info.Arg<CreateFlashSaleResultEto>();
newResultId = eto.ResultId;
var handler = ServiceProvider.GetRequiredService<CreateFlashSaleResultEventHandler>();
await handler.HandleEventAsync(eto);
});
await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput);
await DistributedEventBus.Received()
.PublishAsync(Arg.Is<CreateFlashSaleResultEto>(eto => eto.ResultId == newResultId), false, false);
var flashSaleResultAppService = ServiceProvider.GetRequiredService<IFlashSaleResultAppService>();
(await flashSaleResultAppService.GetCurrentAsync(plan.Id)).Id.ShouldBe(existingResult.Id);
}
}

151
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandlerTests.cs

@ -0,0 +1,151 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.Eshop.Products.Products;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NSubstitute;
using Shouldly;
using Volo.Abp.Users;
using Xunit;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class CreateFlashSaleResultEventHandlerTests : FlashSalesApplicationTestBase
{
protected IFlashSaleResultAppService FlashSaleResultAppService { get; }
protected CreateFlashSaleResultEventHandler CreateFlashSaleResultEventHandler { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
public CreateFlashSaleResultEventHandlerTests()
{
FlashSaleResultAppService = GetRequiredService<IFlashSaleResultAppService>();
CreateFlashSaleResultEventHandler = GetRequiredService<CreateFlashSaleResultEventHandler>();
FlashSaleInventoryManager = GetRequiredService<IFlashSaleInventoryManager>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
var flashSaleInventoryManager = Substitute.For<IFlashSaleInventoryManager>();
services.Replace(ServiceDescriptor.Singleton(flashSaleInventoryManager));
base.AfterAddApplication(services);
}
[Fact]
public async Task Should_Create_Result()
{
var reducedInventoryTime = DateTime.Now;
var createFlashSaleResultEto = await CreateCreateFlashSaleResultEtoAsync(reducedInventoryTime);
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto);
var flashResult = await FlashSaleResultRepository.GetAsync(FlashSalesTestData.Result1Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
flashResult.OrderId.ShouldBeNull();
flashResult.Reason.ShouldBe(null);
flashResult.ReducedInventoryTime.ShouldBe(reducedInventoryTime);
(await FlashSaleResultAppService.GetCurrentAsync(createFlashSaleResultEto.Plan.Id)).Id.ShouldBe(flashResult.Id);
}
[Fact]
public async Task Should_Not_Create_Result_If_Duplicate()
{
var existFlashResult1 = await CreateFlashSaleResultAsync();
var createFlashSaleResultEto1 = await CreateCreateFlashSaleResultEtoAsync(DateTime.Now);
var planId = createFlashSaleResultEto1.Plan.Id;
existFlashResult1.MarkAsFailed("some reason");
await FlashSaleResultRepository.UpdateAsync(existFlashResult1, true);
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult1.Id);
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto1);
await FlashSaleInventoryManager.DidNotReceive()
.TryRollBackInventoryAsync(createFlashSaleResultEto1.TenantId,
createFlashSaleResultEto1.ProductInventoryProviderName, createFlashSaleResultEto1.Plan.StoreId,
createFlashSaleResultEto1.Plan.ProductId, createFlashSaleResultEto1.Plan.ProductSkuId);
var flashResultList = await FlashSaleResultRepository.GetListAsync();
flashResultList.Count.ShouldBe(2);
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id);
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldNotBe(existFlashResult1.Id);
var existFlashResult2 = flashResultList.First(x => x.Id != existFlashResult1.Id);
var createFlashSaleResultEto2 = await CreateCreateFlashSaleResultEtoAsync(DateTime.Now);
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto2);
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(createFlashSaleResultEto2.TenantId,
createFlashSaleResultEto2.ProductInventoryProviderName, createFlashSaleResultEto2.Plan.StoreId,
createFlashSaleResultEto2.Plan.ProductId, createFlashSaleResultEto2.Plan.ProductSkuId);
flashResultList = await FlashSaleResultRepository.GetListAsync();
flashResultList.Count.ShouldBe(2);
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id);
flashResultList.ShouldContain(x => x.Id == existFlashResult2.Id);
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult2.Id);
existFlashResult2.MarkAsSuccessful(GuidGenerator.Create());
await FlashSaleResultRepository.UpdateAsync(existFlashResult2, true);
await CreateFlashSaleResultEventHandler.HandleEventAsync(createFlashSaleResultEto2);
await FlashSaleInventoryManager.Received()
.TryRollBackInventoryAsync(createFlashSaleResultEto2.TenantId,
createFlashSaleResultEto2.ProductInventoryProviderName, createFlashSaleResultEto2.Plan.StoreId,
createFlashSaleResultEto2.Plan.ProductId, createFlashSaleResultEto2.Plan.ProductSkuId);
flashResultList = await FlashSaleResultRepository.GetListAsync();
flashResultList.Count.ShouldBe(2);
flashResultList.ShouldContain(x => x.Id == existFlashResult1.Id);
flashResultList.ShouldContain(x => x.Id == existFlashResult2.Id);
(await FlashSaleResultAppService.GetCurrentAsync(planId)).Id.ShouldBe(existFlashResult2.Id);
}
protected Task<CreateFlashSaleResultEto> CreateCreateFlashSaleResultEtoAsync(DateTime reducedInventoryTime)
{
return Task.FromResult(new CreateFlashSaleResultEto
{
ResultId = FlashSalesTestData.Result1Id,
UserId = CurrentUser.GetId(),
ReducedInventoryTime = reducedInventoryTime,
Plan = new FlashSalePlanEto
{
Id = FlashSalesTestData.Plan1Id,
TenantId = null,
StoreId = FlashSalesTestData.Store1Id,
BeginTime = reducedInventoryTime,
EndTime = DateTime.Now.AddMinutes(30),
ProductId = FlashSalesTestData.Product1Id,
ProductSkuId = FlashSalesTestData.ProductSku1Id,
IsPublished = true
},
HashToken = "My Hash Token"
});
}
protected async Task<FlashSaleResult> CreateFlashSaleResultAsync()
{
return await WithUnitOfWorkAsync(async () =>
{
var flashSaleResult = new FlashSaleResult(
GuidGenerator.Create(),
null,
FlashSalesTestData.Store1Id,
FlashSalesTestData.Plan1Id,
CurrentUser.GetId(),
DateTime.Now);
await FlashSaleResultRepository.InsertAsync(flashSaleResult);
return flashSaleResult;
});
}
}

9
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs

@ -99,12 +99,9 @@ public abstract class FlashSalesApplicationTestBase : FlashSalesTestBase<EShopPl
protected virtual async Task<FlashSaleResult> CreatePendingResultAsync(Guid planId, Guid storeId, Guid userId)
{
return await WithUnitOfWorkAsync(async () =>
{
return await FlashSaleResultRepository.InsertAsync(
new FlashSaleResult(GuidGenerator.Create(), CurrentTenant.Id, storeId, planId, userId)
);
});
return await WithUnitOfWorkAsync(async () => await FlashSaleResultRepository.InsertAsync(
new FlashSaleResult(GuidGenerator.Create(), CurrentTenant.Id, storeId, planId, userId, DateTime.Now)
));
}
protected virtual async Task<FlashSalePlan> CreateFlashSalePlanAsync(bool useSku2 = false, CreateTimeRange timeRange = CreateTimeRange.Starting, bool isPublished = true)

4
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs

@ -14,13 +14,13 @@ public class FakeFlashSaleInventoryManager : IFlashSaleInventoryManager
}
public Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool increaseSold)
Guid productSkuId)
{
return Task.FromResult(ShouldReduceSuccess);
}
public Task<bool> TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool decreaseSold)
Guid productSkuId)
{
return Task.FromResult(true);
}

85
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs

@ -1,85 +0,0 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using Shouldly;
using Volo.Abp.Guids;
using Xunit;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
public class CreateFlashSaleOrderCompleteEventHandlerTests : FlashSalesDomainTestBase
{
protected IFlashSaleResultRepository FlashSaleResultRepository { get; }
protected CreateFlashSaleOrderCompleteEventHandler CreateFlashSaleOrderCompleteEventHandler { get; }
protected IGuidGenerator GuidGenerator { get; }
public CreateFlashSaleOrderCompleteEventHandlerTests()
{
FlashSaleResultRepository = GetRequiredService<IFlashSaleResultRepository>();
CreateFlashSaleOrderCompleteEventHandler = GetRequiredService<CreateFlashSaleOrderCompleteEventHandler>();
GuidGenerator = GetRequiredService<IGuidGenerator>();
}
[Fact]
public async Task HandleEventAsync_When_Create_Order_Success()
{
var existFlashResult = await CreateFlashSaleResultAsync();
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = existFlashResult.TenantId,
PendingResultId = existFlashResult.Id,
Success = true,
StoreId = existFlashResult.StoreId,
PlanId = existFlashResult.PlanId,
OrderId = GuidGenerator.Create(),
Reason = null,
UserId = existFlashResult.UserId,
};
await CreateFlashSaleOrderCompleteEventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var flashResult = await FlashSaleResultRepository.GetAsync(existFlashResult.Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Successful);
flashResult.OrderId.ShouldBe(createFlashSaleOrderCompleteEto.OrderId);
flashResult.Reason.ShouldBe(null);
}
[Fact]
public async Task HandleEventAsync_When_Create_Order_Failed()
{
var existFlashResult = await CreateFlashSaleResultAsync();
var createFlashSaleOrderCompleteEto = new CreateFlashSaleOrderCompleteEto()
{
TenantId = existFlashResult.TenantId,
PendingResultId = existFlashResult.Id,
Success = false,
StoreId = FlashSalesTestData.Store1Id,
PlanId = existFlashResult.PlanId,
OrderId = null,
Reason = "Failed reason",
UserId = existFlashResult.UserId,
};
await CreateFlashSaleOrderCompleteEventHandler.HandleEventAsync(createFlashSaleOrderCompleteEto);
var flashResult = await FlashSaleResultRepository.GetAsync(existFlashResult.Id);
flashResult.Status.ShouldBe(FlashSaleResultStatus.Failed);
flashResult.OrderId.ShouldBe(null);
flashResult.Reason.ShouldBe("Failed reason");
}
public async Task<FlashSaleResult> CreateFlashSaleResultAsync()
{
return await WithUnitOfWorkAsync(async () =>
{
var flashSaleResult = new FlashSaleResult(
GuidGenerator.Create(),
null,
FlashSalesTestData.Store1Id,
FlashSalesTestData.Plan1Id,
GuidGenerator.Create());
await FlashSaleResultRepository.InsertAsync(flashSaleResult);
return flashSaleResult;
});
}
}

12
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs

@ -18,7 +18,8 @@ public class FlashSaleResultTests
tenantId: null,
storeId: Guid.NewGuid(),
planId: Guid.NewGuid(),
userId: Guid.NewGuid()
userId: Guid.NewGuid(),
DateTime.Now
);
flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
@ -38,7 +39,8 @@ public class FlashSaleResultTests
tenantId: null,
storeId: Guid.NewGuid(),
planId: Guid.NewGuid(),
userId: Guid.NewGuid()
userId: Guid.NewGuid(),
DateTime.Now
);
flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
@ -61,7 +63,8 @@ public class FlashSaleResultTests
tenantId: null,
storeId: Guid.NewGuid(),
planId: Guid.NewGuid(),
userId: Guid.NewGuid()
userId: Guid.NewGuid(),
DateTime.Now
);
flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);
@ -81,7 +84,8 @@ public class FlashSaleResultTests
tenantId: null,
storeId: Guid.NewGuid(),
planId: Guid.NewGuid(),
userId: Guid.NewGuid()
userId: Guid.NewGuid(),
DateTime.Now
);
flashSaleResult.Status.ShouldBe(FlashSaleResultStatus.Pending);

Loading…
Cancel
Save