diff --git a/docs/plugins/flash-sales/README.md b/docs/plugins/flash-sales/README.md index f29c46e7..9b2896a5 100644 --- a/docs/plugins/flash-sales/README.md +++ b/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. diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs index 57d85ec1..9c0c7ddb 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs +++ b/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() { @@ -139,6 +148,8 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler PreOrderAsync(Guid id); - Task OrderAsync(Guid id, CreateOrderInput input); + Task OrderAsync(Guid id, OrderFlashSalePlanInput flashSalePlanInput); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs index 1df7365f..46c19205 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/Dtos/FlashSaleResultDto.cs +++ b/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, 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; } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs index 3c1bce94..4b95c6a5 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResultAppService.cs +++ b/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 GetCurrentAsync(Guid planId); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs deleted file mode 100644 index 7e5f7d89..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandler.cs +++ /dev/null @@ -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, ITransientDependency -{ - protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } - protected IDistributedCache DistributedCache { get; } - protected IFlashSalePlanRepository FlashSalePlanRepository { get; } - protected IProductAppService ProductAppService { get; } - protected ILogger Logger { get; } - - public CreateFlashSaleOrderFailedEventHandler ( - IFlashSaleInventoryManager flashSaleInventoryManager, - IDistributedCache distributedCache, - IFlashSalePlanRepository flashSalePlanRepository, - IProductAppService productAppService, - ILogger 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 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)); - } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandler.cs new file mode 100644 index 00000000..852d3fa9 --- /dev/null +++ b/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, + ITransientDependency +{ + protected ILogger 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 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) + }); + } +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs index b61b3a50..ab38c192 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs +++ b/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 : /// public const string PreOrderCacheKeyFormat = "eshopflashsales_{0}_{1}"; - /// - /// The cache key format. - /// {0}: Tenant ID - /// {1}: FlashSalePlan ID - /// {2}: User ID - /// - 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 optionsMonitor) + IFlashSaleCurrentResultCache flashSaleCurrentResultCache, + IOptions 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 GetAsync(Guid id) @@ -206,6 +204,7 @@ public class FlashSalePlanAppService : } [Authorize] + [UnitOfWork(IsDisabled = true)] public virtual async Task 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 OrderAsync(Guid id, CreateOrderInput input) + public virtual async Task 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 GetUserFlashSaleResultCacheKeyAsync(Guid planId) - { - return Task.FromResult(string.Format(UserFlashSaleResultCacheKeyFormat, CurrentTenant.Id, planId, CurrentUser.GetId())); - } - - protected virtual async Task 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 PrepareCreateFlashSaleOrderEtoAsync( - FlashSalePlanCacheItem plan, Guid resultId, CreateOrderInput input, - Guid userId, DateTime now, string hashToken) + protected virtual Task PrepareCreateFlashSaleResultEtoAsync( + FlashSalePlanCacheItem plan, OrderFlashSalePlanInput flashSalePlanInput, Guid userId, + DateTime reducedInventoryTime, FlashSalePlanPreOrderCacheItem preOrderCacheItem) { var planEto = ObjectMapper.Map(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 CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId, Func 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 GetProductCacheAsync(Guid productId) { return await ProductDistributedCache.GetOrAddAsync(productId, async () => diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandler.cs new file mode 100644 index 00000000..e691edd7 --- /dev/null +++ b/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, + ITransientDependency +{ + protected IObjectMapper ObjectMapper { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected ILogger 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 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(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 GetLockKeyAsync(CreateFlashSaleResultEto eventData) + { + return Task.FromResult($"eshopflashsales-creating-result_{eventData.Plan.Id}-{eventData.UserId}"); + } +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCache.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCache.cs new file mode 100644 index 00000000..f88863a5 --- /dev/null +++ b/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 +{ + /// + /// The cache key format. + /// {0}: FlashSalePlan ID + /// {1}: User ID + /// + public const string FlashSaleCurrentResultCacheKeyFormat = "eshopflashsales-current-result_{0}_{1}"; + + protected IDistributedCache Cache { get; } + protected FlashSalesOptions Options { get; } + + public FlashSaleCurrentResultCache( + IDistributedCache cache, + IOptions options) + { + Cache = cache; + Options = options.Value; + } + + public virtual async Task 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 GetKeyAsync(Guid planId, Guid userId) + { + return Task.FromResult(string.Format(FlashSaleCurrentResultCacheKeyFormat, planId, userId)); + } +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCacheItem.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleCurrentResultCacheItem.cs new file mode 100644 index 00000000..032c129a --- /dev/null +++ b/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; } +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs index c9034bb9..205b8eed 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs +++ b/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 GetAsync(Guid id) + [Authorize] + public override Task GetAsync(Guid id) { - var flashSaleResult = await GetEntityByIdAsync(id); + throw new NotSupportedException(); + } + + public virtual async Task 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> GetListAsync(FlashSaleResultGetListInput input) @@ -51,7 +75,8 @@ public class FlashSaleResultAppService : return await base.GetListAsync(input); } - protected override async Task> CreateFilteredQueryAsync(FlashSaleResultGetListInput input) + protected override async Task> 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); } -} +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleCurrentResultCache.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleCurrentResultCache.cs new file mode 100644 index 00000000..e6b63e7d --- /dev/null +++ b/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 GetAsync(Guid planId, Guid userId); + + Task SetAsync(Guid planId, Guid userId, FlashSaleCurrentResultCacheItem cacheItem); + + Task RemoveAsync(Guid planId, Guid userId); +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/Options/FlashSalesOptions.cs similarity index 63% rename from plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs rename to plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/Options/FlashSalesOptions.cs index e4071710..cb677006 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalesOptions.cs +++ b/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 /// /// Default: 5 minutes /// - public TimeSpan UserFlashSaleResultCacheExpires { get; set; } = TimeSpan.FromMinutes(5); + public TimeSpan FlashSaleCurrentResultCacheExpires { get; set; } = TimeSpan.FromMinutes(5); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs index aaeb31ce..41ad7ee8 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs +++ b/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 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 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)); } } \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs index c17a0f39..4f50a1d3 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs +++ b/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; } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs similarity index 59% rename from plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs rename to plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEto.cs index b5a76b63..88ce602b 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderCompleteEto.cs +++ b/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; } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEto.cs new file mode 100644 index 00000000..b37298ba --- /dev/null +++ b/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; } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs index f4ef7845..d02bcc8d 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs +++ b/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"; } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs index a38555b3..94479a94 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalesErrorCodes.cs +++ b/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)}"; } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json index 0b5b2c37..a090db59 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/en.json +++ b/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", diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json index 5aa756b8..2a5a34e8 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hans.json +++ b/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": "等待中", diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json index 6f7a3e84..91ad46ab 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/Localization/zh-Hant.json +++ b/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": "等待中", diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs deleted file mode 100644 index e4377fcd..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandler.cs +++ /dev/null @@ -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, 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); - } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs index 2617a14a..a1a25619 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult.cs +++ b/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, IMultiTenant, IMultiStore +public class FlashSaleResult : FullAuditedAggregateRoot, IFlashSaleResult, IMultiTenant { public virtual Guid? TenantId { get; protected set; } @@ -22,16 +22,19 @@ public class FlashSaleResult : FullAuditedAggregateRoot, 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, IMultiTenant, IMu OrderId = orderId; } - public void MarkAsFailed(string reason) + public void MarkAsFailed([NotNull] string reason) { if (Status != FlashSaleResultStatus.Pending) { diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResult.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/IFlashSaleResult.cs new file mode 100644 index 00000000..967485f9 --- /dev/null +++ b/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; } +} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs index bd18e94e..8dc38783 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs +++ b/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 PreOrderAsync(Guid id) { return Service.PreOrderAsync(id); } + [DisableAuditing] + [UnitOfWork(IsDisabled = true)] [HttpPost("{id}/order")] - public virtual Task OrderAsync(Guid id, CreateOrderInput input) + public virtual Task OrderAsync(Guid id, OrderFlashSalePlanInput flashSalePlanInput) { - return Service.OrderAsync(id, input); + return Service.OrderAsync(id, flashSalePlanInput); } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultController.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultController.cs index b0095dcc..0f6c1707 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultController.cs +++ b/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 GetCurrentAsync(Guid planId) + { + return Service.GetCurrentAsync(planId); + } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/ViewModels/ViewFlashSaleResultViewModel.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/ViewModels/ViewFlashSaleResultViewModel.cs index 49b9aae1..88ab34ec 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/ViewModels/ViewFlashSaleResultViewModel.cs +++ b/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; } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/index.js b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/index.js index aac69835..f94b74e6 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Web/Pages/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResult/index.js +++ b/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' + }, ] })); diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs index 93558adc..8041e743 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs +++ b/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 TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, - Guid productSkuId, int quantity, bool increaseSold); + Guid productSkuId); Task TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, - Guid productSkuId, int quantity, bool decreaseSold); + Guid productSkuId); } \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs index cbac9f41..3b03efb6 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs +++ b/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 ); } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs index 6e38ba04..aae7dad0 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs +++ b/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 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 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); } } diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs index b7643685..1305861f 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandlerTests.cs +++ b/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()); - 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(eto => + .PublishAsync(Arg.Is(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(eto => + .PublishAsync(Arg.Is(eto => eto.TenantId == CurrentTenant.Id && - eto.PendingResultId == FlashSalesTestData.Result1Id && + eto.ResultId == FlashSalesTestData.Result1Id && !eto.Success && eto.StoreId == FlashSalesTestData.Store1Id && eto.PlanId == FlashSalesTestData.Plan1Id && diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs index 3ac63e8e..fa4d143e 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationTestsModule.cs +++ b/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), diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs index 5abed316..a055ca98 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Orders.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Orders/Plugins/FlashSales/OrdersPluginsFlashSalesApplicationTestBase.cs +++ b/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. diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandlerTest.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandlerTest.cs deleted file mode 100644 index 41e2981f..00000000 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderFailedEventHandlerTest.cs +++ /dev/null @@ -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(); - DistributedCache = GetRequiredService(); - FlashSaleInventoryManager = GetRequiredService(); - } - - protected override void AfterAddApplication(IServiceCollection services) - { - Product1 = CreateMockProductDto(); - - var productAppService = Substitute.For(); - productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1)); - services.Replace(ServiceDescriptor.Singleton(productAppService)); - - FlashSaleInventoryManager = Substitute.For(); - 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); - } -} diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleOrderCreationResultEventHandlerTests.cs new file mode 100644 index 00000000..75a081b2 --- /dev/null +++ b/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(); + FlashSaleCurrentResultCache = GetRequiredService(); + FlashSaleOrderCreationResultEventHandler = GetRequiredService(); + FlashSaleInventoryManager = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + Product1 = CreateMockProductDto(); + + var productAppService = Substitute.For(); + productAppService.GetAsync(FlashSalesTestData.Product1Id).Returns(Task.FromResult(Product1)); + services.Replace(ServiceDescriptor.Singleton(productAppService)); + + var productDetailAppService = Substitute.For(); + 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(); + 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) + }); + } + + [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); + } +} diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs index 6fa1d635..0b748d20 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs +++ b/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 ProductDistributedCache { get; } + private ProductDto Product1 { get; set; } public FlashSalePlanAppServiceTests() { AppService = GetRequiredService(); DistributedEventBus = GetRequiredService(); + ProductDistributedCache = GetRequiredService>(); } protected override void AfterAddApplication(IServiceCollection services) @@ -48,6 +53,12 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase var distributedEventBus = Substitute.For(); services.Replace(ServiceDescriptor.Singleton(distributedEventBus)); + + ObjectExtensionManager.Instance.AddOrUpdate(info => + { + info.AddOrUpdateProperty("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(); 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() .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(eto => + await DistributedEventBus.Received().PublishAsync(Arg.Is(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()) - .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()) - .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()) - .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(); 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()) - .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(); + var flashSaleCurrentResultCache = GetRequiredService(); 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()) - .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().GetId(); - await CreatePendingResultAsync(plan.Id, plan.StoreId, userId); + var existingResult = await CreatePendingResultAsync(plan.Id, plan.StoreId, userId); - (await AppService.OrderAsync(plan.Id, createOrderInput) - .ShouldThrowAsync()) - .Code.ShouldBe(FlashSalesErrorCodes.DuplicateFlashSalesOrder); + Guid? newResultId = null; + DistributedEventBus.PublishAsync(null, false, false).ReturnsForAnyArgs(async info => + { + var eto = info.Arg(); + newResultId = eto.ResultId; + var handler = ServiceProvider.GetRequiredService(); + await handler.HandleEventAsync(eto); + }); + + await AppService.OrderAsync(plan.Id, orderFlashSalePlanInput); + + await DistributedEventBus.Received() + .PublishAsync(Arg.Is(eto => eto.ResultId == newResultId), false, false); + + var flashSaleResultAppService = ServiceProvider.GetRequiredService(); + (await flashSaleResultAppService.GetCurrentAsync(plan.Id)).Id.ShouldBe(existingResult.Id); } } diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleResultEventHandlerTests.cs new file mode 100644 index 00000000..c3234074 --- /dev/null +++ b/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(); + CreateFlashSaleResultEventHandler = GetRequiredService(); + FlashSaleInventoryManager = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + var flashSaleInventoryManager = Substitute.For(); + 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 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 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; + }); + } +} diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs index e0877a3d..d31baa83 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs +++ b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalesApplicationTestBase.cs @@ -99,12 +99,9 @@ public abstract class FlashSalesApplicationTestBase : FlashSalesTestBase 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 CreateFlashSalePlanAsync(bool useSku2 = false, CreateTimeRange timeRange = CreateTimeRange.Starting, bool isPublished = true) diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs index 866e3b8c..faf0f706 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Products/Products/FakeFlashSaleInventoryManager.cs +++ b/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 TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, - Guid productSkuId, int quantity, bool increaseSold) + Guid productSkuId) { return Task.FromResult(ShouldReduceSuccess); } public Task TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, - Guid productSkuId, int quantity, bool decreaseSold) + Guid productSkuId) { return Task.FromResult(true); } diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs deleted file mode 100644 index 5172e23f..00000000 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/CreateFlashSaleOrderCompleteEventHandlerTests.cs +++ /dev/null @@ -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(); - CreateFlashSaleOrderCompleteEventHandler = GetRequiredService(); - GuidGenerator = GetRequiredService(); - } - - [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 CreateFlashSaleResultAsync() - { - return await WithUnitOfWorkAsync(async () => - { - var flashSaleResult = new FlashSaleResult( - GuidGenerator.Create(), - null, - FlashSalesTestData.Store1Id, - FlashSalesTestData.Plan1Id, - GuidGenerator.Create()); - await FlashSaleResultRepository.InsertAsync(flashSaleResult); - - return flashSaleResult; - }); - } -} diff --git a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs b/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs index 7464ce4b..db1466ec 100644 --- a/plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Domain.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultTests.cs +++ b/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);