diff --git a/EShop.sln b/EShop.sln index 44fc2380..88ccbea9 100644 --- a/EShop.sln +++ b/EShop.sln @@ -413,7 +413,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Flash EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Orders.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Orders.Plugins.FlashSales.Application\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.csproj", "{5732E880-CB72-49A0-AC4F-A0620F4E4D16}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application\EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj", "{6AD2F468-D86C-4F9A-B280-3BCC15661C47}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application\EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj", "{6AD2F468-D86C-4F9A-B280-3BCC15661C47}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj", "{C1EB9DC0-F572-41DF-9716-893B154BBB13}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj", "{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1077,6 +1081,14 @@ Global {6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Debug|Any CPU.Build.0 = Debug|Any CPU {6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.ActiveCfg = Release|Any CPU {6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.Build.0 = Release|Any CPU + {C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.Build.0 = Release|Any CPU + {F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1284,6 +1296,8 @@ Global {417AB8E2-1488-4814-9699-3B189D1ABA67} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4} {5732E880-CB72-49A0-AC4F-A0620F4E4D16} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} {6AD2F468-D86C-4F9A-B280-3BCC15661C47} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} + {C1EB9DC0-F572-41DF-9716-893B154BBB13} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} + {F08D9409-4D01-4639-A7B8-A70B7ED8E0F9} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs index 1fd1daca..09ea7ab9 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs @@ -9,4 +9,6 @@ public interface IProductInventoryProviderResolver Task ExistProviderAsync([NotNull] string providerName); Task GetAsync(Product product); + + Task GetAsync([NotNull] string providerName); } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs index bd1e4472..89ea20ca 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs @@ -7,6 +7,7 @@ using EasyAbp.EShop.Products.ProductInventories; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp; using Volo.Abp.DependencyInjection; namespace EasyAbp.EShop.Products.Products; @@ -47,6 +48,11 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName)); } + public virtual Task GetAsync([NotNull] string providerName) + { + return Task.FromResult(GetProviderByName(providerName)); + } + protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName) { if (providerName.IsNullOrEmpty()) @@ -67,7 +73,7 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve { return; } - + var options = ServiceProvider.GetRequiredService>().Value; foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary()) 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 441f4234..7aab2d4c 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 @@ -3,7 +3,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; +using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; +using EasyAbp.EShop.Products.ProductDetails; using EasyAbp.EShop.Products.ProductDetails.Dtos; +using EasyAbp.EShop.Products.Products; using EasyAbp.EShop.Products.Products.Dtos; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; @@ -24,45 +27,59 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler orderDiscountProviders, IOrderRepository orderRepository, - IDistributedEventBus distributedEventBus) + IDistributedEventBus distributedEventBus, + IProductAppService productAppService, + IProductDetailAppService productDetailAppService, + IFlashSalePlanHasher flashSalePlanHasher) { NewOrderGenerator = newOrderGenerator; ObjectMapper = objectMapper; OrderDiscountProviders = orderDiscountProviders; OrderRepository = orderRepository; DistributedEventBus = distributedEventBus; + ProductAppService = productAppService; + ProductDetailAppService = productDetailAppService; + FlashSalePlanHasher = flashSalePlanHasher; } [UnitOfWork(true)] public virtual async Task HandleEventAsync(CreateFlashSaleOrderEto eventData) { - var input = new CreateOrderDto() + var product = await ProductAppService.GetAsync(eventData.Plan.ProductId); + var productSku = product.GetSkuById(eventData.Plan.ProductSkuId); + + if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken)) { - StoreId = eventData.StoreId, - CustomerRemark = eventData.CustomerRemark, - OrderLines = new List() + await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto() { - new CreateOrderLineDto() - { - ProductId = eventData.Plan.ProductId, - ProductSkuId = eventData.Plan.ProductSkuId, - Quantity = 1 - } - } - }; - var productDict = new Dictionary() - { - {eventData.Product.Id, ObjectMapper.Map(eventData.Product)} - }; - var productDetailDict = new Dictionary() - { - {eventData.ProductDetail.Id, ObjectMapper.Map(eventData.ProductDetail)} - }; + TenantId = eventData.TenantId, + PlanId = eventData.PlanId, + OrderId = null, + UserId = eventData.UserId, + StoreId = eventData.StoreId, + PendingResultId = eventData.PendingResultId, + Success = false, + Reason = FlashSaleResultFailedReason.PreOrderExipred + }); + return; + } + + var input = await ConvertToCreateOrderDtoAsync(eventData); + + var productDict = await GetProductDictionaryAsync(product); + + var productDetailDict = await GetProductDetailDictionaryAsync(product, productSku); var order = await NewOrderGenerator.GenerateAsync(eventData.UserId, input, productDict, productDetailDict); @@ -79,4 +96,55 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler> GetProductDictionaryAsync(ProductDto product) + { + var productDict = new Dictionary() + { + {product.Id, product} + }; + + return Task.FromResult(productDict); + } + + protected virtual async Task> GetProductDetailDictionaryAsync(ProductDto product, ProductSkuDto productSku) + { + var dict = new Dictionary(); + + var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; + + if (productDetailId.HasValue) + { + dict.Add(productDetailId.Value, await ProductDetailAppService.GetAsync(productDetailId.Value)); + } + + return dict; + } + + protected virtual Task ConvertToCreateOrderDtoAsync(CreateFlashSaleOrderEto eventData) + { + var input = new CreateOrderDto() + { + StoreId = eventData.StoreId, + CustomerRemark = eventData.CustomerRemark, + OrderLines = new List() + { + new CreateOrderLineDto() + { + ProductId = eventData.Plan.ProductId, + ProductSkuId = eventData.Plan.ProductSkuId, + Quantity = 1 + } + } + }; + + return Task.FromResult(input); + } + + protected virtual async Task ValidateHashTokenAsync(FlashSalePlanEto plan, ProductDto product, ProductSkuDto productSku, string originHashToken) + { + var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); + + return string.Equals(hashToken, originHashToken, StringComparison.InvariantCulture); + } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs b/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs deleted file mode 100644 index 51e511bb..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs +++ /dev/null @@ -1,36 +0,0 @@ -using AutoMapper; -using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; -using EasyAbp.EShop.Products.ProductDetails.Dtos; -using EasyAbp.EShop.Products.Products.Dtos; -using Volo.Abp.AutoMapper; -using Volo.Abp.DependencyInjection; - -namespace EasyAbp.EShop.Orders.Plugins.FlashSales -{ - public class EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile : Profile - { - public EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile() - { - /* You can configure your AutoMapper mapping configuration here. - * Alternatively, you can split your mapping configurations - * into multiple profile classes for a better organization. */ - CreateMap(MemberList.Destination) - .Ignore(dto => dto.Sold) - .Ignore(dto => dto.MinimumPrice) - .Ignore(dto => dto.MaximumPrice) - .MapExtraProperties(); - CreateMap(MemberList.Destination) - .ForSourceMember(entity => entity.SerializedAttributeOptionIds, opt => opt.DoNotValidate()) - .Ignore(dto => dto.DiscountedPrice) - .Ignore(dto => dto.Inventory) - .Ignore(dto => dto.Sold) - .MapExtraProperties(); - CreateMap(MemberList.Destination) - .MapExtraProperties(); - CreateMap(MemberList.Destination) - .MapExtraProperties(); - CreateMap(MemberList.Destination) - .MapExtraProperties(); - } - } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj index 80f4e744..12758834 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj @@ -16,6 +16,8 @@ + + diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs index c88bd54a..48e94a13 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs @@ -1,4 +1,5 @@ using EasyAbp.EShop.Products; +using EasyAbp.EShop.Products.Plugins.FlashSales; using EasyAbp.EShop.Stores; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Application; @@ -13,6 +14,8 @@ namespace EasyAbp.EShop.Plugins.FlashSales; typeof(EShopPluginsFlashSalesDomainModule), typeof(EShopPluginsFlashSalesApplicationContractsModule), typeof(EShopStoresApplicationSharedModule), + typeof(EShopProductsPluginsFlashSalesAbstractionsModule), + typeof(EShopProductsPluginsFlashSalesApplicationContractsModule), typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpCachingModule) 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 6fb4fa52..63bb7c10 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 @@ -1,6 +1,7 @@ using System; 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.Permissions; @@ -37,9 +38,9 @@ public class FlashSalePlanAppService : protected IProductAppService ProductAppService { get; } - protected IDistributedCache TokenDistributedCache { get; } + protected IDistributedCache PreOrderDistributedCache { get; } - protected IDistributedCache DistributedCache { get; } + protected IDistributedCache PlanDistributedCache { get; } protected IDistributedEventBus DistributedEventBus { get; } @@ -49,25 +50,33 @@ public class FlashSalePlanAppService : protected IFlashSalePlanHasher FlashSalePlanHasher { get; } + protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } + + protected IDistributedCache DistributedCache { get; } + public FlashSalePlanAppService( IFlashSalePlanRepository flashSalePlanRepository, IProductAppService productAppService, - IDistributedCache tokenDistributedCache, - IDistributedCache distributedCache, + IDistributedCache tokenDistributedCache, + IDistributedCache planDistributedCache, IDistributedEventBus distributedEventBus, IFlashSaleResultRepository flashSaleResultRepository, IAbpDistributedLock distributedLock, - IFlashSalePlanHasher flashSalePlanHasher) + IFlashSalePlanHasher flashSalePlanHasher, + IFlashSaleInventoryManager flashSaleInventoryManager, + IDistributedCache distributedCache) : base(flashSalePlanRepository) { FlashSalePlanRepository = flashSalePlanRepository; ProductAppService = productAppService; - TokenDistributedCache = tokenDistributedCache; - DistributedCache = distributedCache; + PreOrderDistributedCache = tokenDistributedCache; + PlanDistributedCache = planDistributedCache; DistributedEventBus = distributedEventBus; FlashSaleResultRepository = flashSaleResultRepository; DistributedLock = distributedLock; FlashSalePlanHasher = flashSalePlanHasher; + FlashSaleInventoryManager = flashSaleInventoryManager; + DistributedCache = distributedCache; } public override async Task GetAsync(Guid id) @@ -180,15 +189,15 @@ public class FlashSalePlanAppService : await ValidatePreOrderAsync(plan, product, productSku); - await SetCacheHashTokenAsync(plan, product, productSku); + await SetPreOrderCacheAsync(plan, product, productSku); } public virtual async Task CheckPreOrderAsync(Guid id) { var plan = await GetFlashSalePlanCacheAsync(id); - var cacheHashToken = await GetCacheHashTokenAsync(plan); - if (cacheHashToken.IsNullOrWhiteSpace()) + var preOrderCache = await GetPreOrderCacheAsync(plan); + if (preOrderCache == null) { throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); } @@ -196,7 +205,7 @@ public class FlashSalePlanAppService : var product = await ProductAppService.GetAsync(plan.ProductId); var productSku = product.GetSkuById(plan.ProductSkuId); - if (!await CompareHashTokenAsync(cacheHashToken, plan, product, productSku)) + if (!await CompareHashTokenAsync(preOrderCache.HashToken, plan, product, productSku)) { throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); } @@ -218,39 +227,81 @@ public class FlashSalePlanAppService : throw new BusinessException(FlashSalesErrorCodes.FlashSaleIsOver); } - var cacheHashToken = await GetCacheHashTokenAsync(plan); - if (cacheHashToken.IsNullOrWhiteSpace()) + var preOrderCache = await GetPreOrderCacheAsync(plan); + if (preOrderCache == null) { throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); } - await RemoveCacheHashTokenAsync(plan); + await RemovePreOrderCacheAsync(plan); var userId = CurrentUser.GetId(); - var result = await CreatePendingFlashSaleResultAsync(plan, userId); + var key = $"create-flash-sale-order-{plan.Id}-{userId}"; + + await using var handle = await DistributedLock.TryAcquireAsync(key); + + if (handle == null) + { + throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder); + } + + var userFlashSaleResultCache = await GetUserFlashSaleResultCacheAsync(plan, userId); + if (!userFlashSaleResultCache.IsNullOrWhiteSpace()) + { + throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); + } + + if (!await FlashSaleInventoryManager.TryIncreaseInventoryAsync( + plan.TenantId, preOrderCache.InventoryProviderName, + plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true)) + { + throw new BusinessException(FlashSalesErrorCodes.ProductSkuInventoryExceeded); + } - var flashSalesReduceInventoryEto = await PrepareFlashSalesReduceInventoryEtoAsync(plan, result.Id, input, userId, now, cacheHashToken); + // Prevent repeat submit + var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => x.PlanId == plan.Id && x.UserId == userId); - /* - * FlashSalesReduceInventoryEto(success) -> CreateFlashSalesOrderEto -> CreateFlashSalesOrderCompleteEto - * FlashSalesReduceInventoryEto(failed) -> CreateFlashSalesOrderCompleteEto - */ - await DistributedEventBus.PublishAsync(flashSalesReduceInventoryEto); + if (existsResult != null) + { + await SetUserFlashSaleResultCacheAsync(plan, userId, existsResult.Id); + + await FlashSaleInventoryManager.TryIncreaseInventoryAsync( + plan.TenantId, preOrderCache.InventoryProviderName, + plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true + ); + + throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); + } + + var result = new FlashSaleResult( + id: GuidGenerator.Create(), + tenantId: CurrentTenant.Id, + storeId: plan.StoreId, + planId: plan.Id, + status: FlashSaleResultStatus.Pending, + reason: null, + userId: userId, + orderId: null + ); + + await FlashSaleResultRepository.InsertAsync(result, autoSave: true); + + await SetUserFlashSaleResultCacheAsync(plan, userId, result.Id); + + var createFlashSaleOrderEto = await PrepareCreateFlashSaleOrderEtoAsync(plan, result.Id, input, userId, now, preOrderCache.HashToken); + + await DistributedEventBus.PublishAsync(createFlashSaleOrderEto); } - protected virtual Task PrepareFlashSalesReduceInventoryEtoAsync( - FlashSalePlanCacheItem plan, - Guid resultId, - CreateOrderInput input, - Guid userId, - DateTime now, - string hashToken) + protected virtual Task PrepareCreateFlashSaleOrderEtoAsync( + FlashSalePlanCacheItem plan, Guid resultId, CreateOrderInput input, + Guid userId, DateTime now, string hashToken) { var planEto = ObjectMapper.Map(plan); planEto.TenantId = CurrentTenant.Id; - var eto = new FlashSaleReduceInventoryEto() + var eto = new CreateFlashSaleOrderEto() { TenantId = CurrentTenant.Id, PlanId = plan.Id, @@ -273,33 +324,40 @@ public class FlashSalePlanAppService : protected virtual async Task GetFlashSalePlanCacheAsync(Guid id) { - return await DistributedCache.GetOrAddAsync(id, async () => + return await PlanDistributedCache.GetOrAddAsync(id, async () => { var flashSalePlan = await FlashSalePlanRepository.GetAsync(id); return ObjectMapper.Map(flashSalePlan); }); } - protected virtual Task GetCacheKeyAsync(FlashSalePlanCacheItem plan) + protected virtual Task GetPreOrderCacheKeyAsync(FlashSalePlanCacheItem plan) { return Task.FromResult($"eshopflashsales_{CurrentTenant.Id}_{CurrentUser.Id}_{plan.ProductSkuId}"); } - protected virtual async Task GetCacheHashTokenAsync(FlashSalePlanCacheItem plan) + protected virtual async Task GetPreOrderCacheAsync(FlashSalePlanCacheItem plan) { - return await TokenDistributedCache.GetStringAsync(await GetCacheKeyAsync(plan)); + return await PreOrderDistributedCache.GetAsync(await GetPreOrderCacheKeyAsync(plan)); } - protected virtual async Task RemoveCacheHashTokenAsync(FlashSalePlanCacheItem plan) + protected virtual async Task RemovePreOrderCacheAsync(FlashSalePlanCacheItem plan) { - await TokenDistributedCache.RemoveAsync(await GetCacheKeyAsync(plan)); + await PreOrderDistributedCache.RemoveAsync(await GetPreOrderCacheKeyAsync(plan)); } - protected virtual async Task SetCacheHashTokenAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku) + protected virtual async Task SetPreOrderCacheAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku) { var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); - await TokenDistributedCache.SetStringAsync(await GetCacheKeyAsync(plan), hashToken, new DistributedCacheEntryOptions() + await PreOrderDistributedCache.SetAsync(await GetPreOrderCacheKeyAsync(plan), new FlashSalePlanPreOrderCacheItem() + { + HashToken = hashToken, + PlanId = plan.Id, + ProductId = product.Id, + ProductSkuId = productSku.Id, + InventoryProviderName = product.InventoryProviderName, + }, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(3) }); @@ -317,36 +375,21 @@ public class FlashSalePlanAppService : return cacheHashToken == hashToken; } - protected virtual async Task CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId) + protected virtual Task GetUserFlashSaleResultCacheKeyAsync(FlashSalePlanCacheItem plan, Guid userId) { - var lockKey = $"create-flash-sales-order-{plan.Id}-{userId}"; - - await using var handle = await DistributedLock.TryAcquireAsync(lockKey); - - if (handle == null) - { - throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder); - } + return Task.FromResult($"flash-sale-result-{plan.Id}-{userId}"); + } - // Prevent repeat submit - if (await FlashSaleResultRepository.AnyAsync(x => - x.PlanId == plan.Id && x.UserId == userId && (x.Status == FlashSaleResultStatus.Successful || x.Status == FlashSaleResultStatus.Pending)) - ) - { - throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); - } + protected virtual async Task GetUserFlashSaleResultCacheAsync(FlashSalePlanCacheItem plan, Guid userId) + { + var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(plan, userId); + return await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey); + } - var result = new FlashSaleResult( - id: GuidGenerator.Create(), - tenantId: CurrentTenant.Id, - storeId: plan.StoreId, - planId: plan.Id, - status: FlashSaleResultStatus.Pending, - reason: null, - userId: userId, - orderId: null - ); - return await FlashSaleResultRepository.InsertAsync(result, autoSave: true); + protected virtual async Task SetUserFlashSaleResultCacheAsync(FlashSalePlanCacheItem plan, Guid userId, Guid resultId) + { + var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(plan, userId); + await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, resultId.ToString()); } protected virtual Task ValidatePreOrderAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku) diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs new file mode 100644 index 00000000..4aac4954 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs @@ -0,0 +1,17 @@ +using System; + +namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; + +[Serializable] +public class FlashSalePlanPreOrderCacheItem +{ + public string HashToken { get; set; } + + public Guid PlanId { get; set; } + + public Guid ProductId { get; set; } + + public Guid ProductSkuId { get; set; } + + public string InventoryProviderName { get; set; } +} 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 new file mode 100644 index 00000000..374e15d9 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.Eshop.Products.Products; +using EasyAbp.EShop.Products.Products.Dtos; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Products.Products; + +[ExposeServices(typeof(IFlashSaleInventoryManager), IncludeSelf = true)] +[Dependency(TryRegister = true)] +public class FlashSaleInventoryManager : IFlashSaleInventoryManager, ITransientDependency +{ + protected IFlashSaleInventoryAppService FlashSaleInventoryReducerAppService { get; } + + public FlashSaleInventoryManager(IFlashSaleInventoryAppService flashSaleInventoryReducerAppService) + { + FlashSaleInventoryReducerAppService = flashSaleInventoryReducerAppService; + } + + public virtual async Task TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + Guid productSkuId, int quantity, bool increaseSold) + { + return await FlashSaleInventoryReducerAppService.TryReduceInventoryAsync( + new ReduceInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, increaseSold)); + } + + public virtual async Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + Guid productSkuId, int quantity, bool decreaseSold) + { + return await FlashSaleInventoryReducerAppService.TryIncreaseInventoryAsync + (new IncreaseInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, decreaseSold)); + } +} 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 af228c85..c17a0f39 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 @@ -21,9 +21,7 @@ public class CreateFlashSaleOrderEto : ExtensibleObject, IMultiTenant public string CustomerRemark { get; set; } - public FlashSaleProductEto Product { get; set; } - - public FlashSaleProductDetailEto ProductDetail { get; set; } - public FlashSalePlanEto Plan { get; set; } + + public string HashToken { get; set; } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeEto.cs deleted file mode 100644 index 7e9f1e8c..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeEto.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using EasyAbp.EShop.Products.Products; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleProductAttributeEto : FullAuditedEntityEto, IProductAttribute -{ - public string DisplayName { get; set; } - - public string Description { get; set; } - - public int DisplayOrder { get; set; } - - public List ProductAttributeOptions { get; set; } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeOptionEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeOptionEto.cs deleted file mode 100644 index 4548b41c..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeOptionEto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using EasyAbp.EShop.Products.Products; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleProductAttributeOptionEto : FullAuditedEntityEto, IProductAttributeOption -{ - public string DisplayName { get; set; } - - public string Description { get; set; } - - public int DisplayOrder { get; set; } -} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductDetailEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductDetailEto.cs deleted file mode 100644 index 9b2a267d..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductDetailEto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Volo.Abp.MultiTenancy; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleProductDetailEto : FullAuditedEntityEto, IMultiTenant -{ - public Guid? TenantId { get; set; } - - public Guid? StoreId { get; set; } - - public string Description { get; set; } -} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductEto.cs deleted file mode 100644 index 6fe3f12c..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductEto.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using EasyAbp.EShop.Products.Products; -using Volo.Abp.MultiTenancy; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleProductEto : FullAuditedEntityEto, IProduct, IMultiTenant -{ - public Guid? TenantId { get; set; } - - public Guid StoreId { get; set; } - - public string ProductGroupName { get; set; } - - public string ProductGroupDisplayName { get; set; } - - public Guid? ProductDetailId { get; set; } - - public string UniqueName { get; set; } - - public string DisplayName { get; set; } - - public InventoryStrategy InventoryStrategy { get; set; } - - public string InventoryProviderName { get; set; } - - public string MediaResources { get; set; } - - public int DisplayOrder { get; set; } - - public bool IsPublished { get; set; } - - public bool IsStatic { get; set; } - - public bool IsHidden { get; set; } - - public TimeSpan? PaymentExpireIn { get; set; } - - public List ProductAttributes { get; set; } - - public List ProductSkus { get; set; } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductSkuEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductSkuEto.cs deleted file mode 100644 index c629db7f..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductSkuEto.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using EasyAbp.EShop.Products.Products; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleProductSkuEto : FullAuditedEntityEto, IProductSku -{ - public List AttributeOptionIds { get; set; } - - public string SerializedAttributeOptionIds { get; set; } - - public string Name { get; set; } - - public string Currency { get; set; } - - public decimal? OriginalPrice { get; set; } - - public decimal Price { get; set; } - - public int OrderMinQuantity { get; set; } - - public int OrderMaxQuantity { get; set; } - - public TimeSpan? PaymentExpireIn { get; set; } - - public string MediaResources { get; set; } - - public Guid? ProductDetailId { get; set; } -} \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleReduceInventoryEto.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleReduceInventoryEto.cs deleted file mode 100644 index 99beb450..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleReduceInventoryEto.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Volo.Abp.MultiTenancy; -using Volo.Abp.ObjectExtending; - -namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; - -[Serializable] -public class FlashSaleReduceInventoryEto : ExtensibleObject, IMultiTenant -{ - public Guid? TenantId { get; set; } - - public Guid StoreId { get; set; } - - public Guid PlanId { 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; } - - public string HashToken { get; set; } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj new file mode 100644 index 00000000..ceb72923 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard2.0 + + + + + + + + diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs new file mode 100644 index 00000000..6926ce29 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace EasyAbp.EShop.Products.Plugins.FlashSales; + +[DependsOn( + typeof(EShopProductsApplicationContractsModule) +)] +public class EShopProductsPluginsFlashSalesAbstractionsModule : AbpModule +{ +} 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 new file mode 100644 index 00000000..0896e7d5 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace EasyAbp.Eshop.Products.Products; + +public interface IFlashSaleInventoryManager +{ + Task TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold); + + Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold); +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xml b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xml new file mode 100644 index 00000000..00e1d9a1 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xsd b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xsd new file mode 100644 index 00000000..3f3946e2 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj new file mode 100644 index 00000000..91c56364 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard2.0 + + + + + + + + diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationContractsModule.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationContractsModule.cs new file mode 100644 index 00000000..efd5d81b --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationContractsModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; + +namespace EasyAbp.EShop.Products.Plugins.FlashSales; + +[DependsOn( + typeof(EShopProductsApplicationContractsModule) +)] +public class EShopProductsPluginsFlashSalesApplicationContractsModule : AbpModule +{ +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissionDefinitionProvider.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissionDefinitionProvider.cs new file mode 100644 index 00000000..aedf09da --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissionDefinitionProvider.cs @@ -0,0 +1,26 @@ +using EasyAbp.EShop.Products.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace EasyAbp.EShop.Products.Permissions +{ + public class ProductsPluginsFlashSalesPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var moduleGroup = context.AddGroup(ProductsPluginsFlashSalesPermissions.GroupName, L("Permission:Products")); + + var flashSaleInventoryPermission = moduleGroup.AddPermission(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Default, L("Permission:FlashSaleInventory")) + .WithProviders(ClientPermissionValueProvider.ProviderName); + flashSaleInventoryPermission.AddChild(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Increase, L("Permission:InventoryIncrease")) + .WithProviders(ClientPermissionValueProvider.ProviderName); + flashSaleInventoryPermission.AddChild(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Reduce, L("Permission:InventoryReduce")) + .WithProviders(ClientPermissionValueProvider.ProviderName); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissions.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissions.cs new file mode 100644 index 00000000..66e90962 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissions.cs @@ -0,0 +1,21 @@ +using Volo.Abp.Reflection; + +namespace EasyAbp.EShop.Products.Permissions +{ + public class ProductsPluginsFlashSalesPermissions + { + public const string GroupName = "EasyAbp.EShop.Products.Plugins.FlashSales"; + + public class FlashSaleInventory + { + public const string Default = GroupName + ".FlashSaleInventory"; + public const string Reduce = Default + ".Reduce"; + public const string Increase = Default + ".Increase"; + } + + public static string[] GetAll() + { + return ReflectionHelper.GetPublicConstantsRecursively(typeof(ProductsPluginsFlashSalesPermissions)); + } + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/IncreaseInventoryInput.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/IncreaseInventoryInput.cs new file mode 100644 index 00000000..e520d5bb --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/IncreaseInventoryInput.cs @@ -0,0 +1,37 @@ +using System; +using EasyAbp.EShop.Stores.Stores; +using Volo.Abp.MultiTenancy; + +namespace EasyAbp.EShop.Products.Products.Dtos; + +public class IncreaseInventoryInput : IMultiTenant, IMultiStore +{ + public Guid? TenantId { get; set; } + + public string ProviderName { get; set; } + + public Guid StoreId { get; set; } + + public Guid ProductId { get; set; } + + public Guid ProductSkuId { get; set; } + + public int Quantity { get; set; } + + public bool ReduceSold { get; set; } + + public IncreaseInventoryInput() + { + } + + public IncreaseInventoryInput(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool reduceSold) + { + TenantId = tenantId; + ProviderName = providerName; + StoreId = storeId; + ProductId = productId; + ProductSkuId = productSkuId; + Quantity = quantity; + ReduceSold = reduceSold; + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ReduceInventoryInput.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ReduceInventoryInput.cs new file mode 100644 index 00000000..fa5ac94c --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ReduceInventoryInput.cs @@ -0,0 +1,37 @@ +using System; +using EasyAbp.EShop.Stores.Stores; +using Volo.Abp.MultiTenancy; + +namespace EasyAbp.EShop.Products.Products.Dtos; + +public class ReduceInventoryInput : IMultiTenant, IMultiStore +{ + public Guid? TenantId { get; set; } + + public string ProviderName { get; set; } + + public Guid StoreId { get; set; } + + public Guid ProductId { get; set; } + + public Guid ProductSkuId { get; set; } + + public int Quantity { get; set; } + + public bool IncreaseSold { get; set; } + + public ReduceInventoryInput() + { + } + + public ReduceInventoryInput(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold) + { + TenantId = tenantId; + ProviderName = providerName; + StoreId = storeId; + ProductId = productId; + ProductSkuId = productSkuId; + Quantity = quantity; + IncreaseSold = increaseSold; + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/IFlashSaleInventoryAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/IFlashSaleInventoryAppService.cs new file mode 100644 index 00000000..339d2e60 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/IFlashSaleInventoryAppService.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.EShop.Products.Products.Dtos; +using Volo.Abp.Application.Services; + +namespace EasyAbp.EShop.Products.Products; + +public interface IFlashSaleInventoryAppService : IApplicationService +{ + Task TryReduceInventoryAsync(ReduceInventoryInput input); + + Task TryIncreaseInventoryAsync(IncreaseInventoryInput input); +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml new file mode 100644 index 00000000..00e1d9a1 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd new file mode 100644 index 00000000..3f3946e2 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj index 308d5504..88afd375 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj @@ -10,6 +10,8 @@ + + diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationAutoMapperProfile.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationAutoMapperProfile.cs deleted file mode 100644 index fbb7050b..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationAutoMapperProfile.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using AutoMapper; -using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; -using EasyAbp.EShop.Products.Options; -using EasyAbp.EShop.Products.ProductDetails; -using EasyAbp.EShop.Products.Products; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace EasyAbp.EShop.Plugins.FlashSales; - -public class EShopProductsPluginsFlashSalesApplicationAutoMapperProfile : Profile, ISingletonDependency -{ - public EShopProductsPluginsFlashSalesApplicationAutoMapperProfile( - IAttributeOptionIdsSerializer attributeOptionIdsSerializer, - IOptionsMonitor options) - { - CreateMap() - .ForMember(x => x.ProductGroupDisplayName, opt => opt.Ignore()) - .AfterMap((src, dest) => - { - var dict = options.CurrentValue.Groups.GetConfigurationsDictionary(); - dest.ProductGroupDisplayName = dict[src.ProductGroupName].DisplayName; - }) - .MapExtraProperties(); - CreateMap() - .ForMember(x => x.AttributeOptionIds, opt => opt.Ignore()) - .AfterMap(async (src, dest) => dest.AttributeOptionIds = (await attributeOptionIdsSerializer.DeserializeAsync(src.SerializedAttributeOptionIds)).ToList()) - .MapExtraProperties(); - CreateMap() - .MapExtraProperties(); - CreateMap() - .MapExtraProperties(); - CreateMap() - .MapExtraProperties(); - } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs index a1a599dc..dcee02c4 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs @@ -1,28 +1,14 @@ using EasyAbp.EShop.Plugins.FlashSales; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AutoMapper; using Volo.Abp.Modularity; namespace EasyAbp.EShop.Products.Plugins.FlashSales; [DependsOn( typeof(EShopProductsApplicationModule), - typeof(EShopPluginsFlashSalesApplicationContractsModule) + typeof(EShopPluginsFlashSalesApplicationContractsModule), + typeof(EShopProductsPluginsFlashSalesApplicationContractsModule), + typeof(EShopProductsPluginsFlashSalesAbstractionsModule) )] public class EShopProductsPluginsFlashSalesApplicationModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.Configurators.Add(abpAutoMapperConfigurationContext => - { - var profile = abpAutoMapperConfigurationContext.ServiceProvider - .GetRequiredService(); - - abpAutoMapperConfigurationContext.MapperConfiguration.AddProfile(profile); - }); - }); - } } 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 new file mode 100644 index 00000000..9a3a56e2 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using EasyAbp.EShop.Products.Permissions; +using EasyAbp.EShop.Products.Products.Dtos; + +namespace EasyAbp.EShop.Products.Products; + +public class FlashSaleInventoryAppService : ProductsAppService, IFlashSaleInventoryAppService +{ + protected ILocalFlashSaleInventoryManager LocalFlashSaleInventoryReducer { get; } + + public FlashSaleInventoryAppService(ILocalFlashSaleInventoryManager localFlashSaleInventoryReducer) + { + LocalFlashSaleInventoryReducer = localFlashSaleInventoryReducer; + } + + public virtual async Task TryReduceInventoryAsync(ReduceInventoryInput input) + { + await CheckPolicyAsync(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Reduce); + + return await LocalFlashSaleInventoryReducer.TryReduceInventoryAsync( + input.TenantId, + input.ProviderName, + input.StoreId, + input.ProductId, + input.ProductSkuId, + input.Quantity, + input.IncreaseSold + ); + } + + public virtual async Task TryIncreaseInventoryAsync(IncreaseInventoryInput input) + { + await CheckPolicyAsync(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Increase); + + return await LocalFlashSaleInventoryReducer.TryIncreaseInventoryAsync( + input.TenantId, + input.ProviderName, + input.StoreId, + input.ProductId, + input.ProductSkuId, + input.Quantity, + input.ReduceSold + ); + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleReduceInventoryEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleReduceInventoryEventHandler.cs deleted file mode 100644 index 45f6da3c..00000000 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleReduceInventoryEventHandler.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; -using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; -using EasyAbp.EShop.Products.ProductDetails; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EventBus.Distributed; -using Volo.Abp.ObjectMapping; -using Volo.Abp.Uow; - -namespace EasyAbp.EShop.Products.Products; - -public class FlashSaleReduceInventoryEventHandler : IDistributedEventHandler, ITransientDependency -{ - protected IProductRepository ProductRepository { get; } - - protected IProductDetailRepository ProductDetailRepository { get; } - - protected ProductManager ProductManager { get; } - - protected IDistributedEventBus DistributedEventBus { get; } - - protected IObjectMapper ObjectMapper { get; } - - protected IFlashSalePlanHasher FlashSalePlanHasher { get; } - - public FlashSaleReduceInventoryEventHandler( - IProductRepository productRepository, - IProductDetailRepository productDetailRepository, - ProductManager productManager, - IDistributedEventBus distributedEventBus, - IObjectMapper objectMapper, - IFlashSalePlanHasher flashSalePlanHasher) - { - ProductRepository = productRepository; - ProductDetailRepository = productDetailRepository; - ProductManager = productManager; - DistributedEventBus = distributedEventBus; - ObjectMapper = objectMapper; - FlashSalePlanHasher = flashSalePlanHasher; - } - - [UnitOfWork] - public virtual async Task HandleEventAsync(FlashSaleReduceInventoryEto eventData) - { - var product = await ProductRepository.GetAsync(eventData.Plan.ProductId); - var productSku = product.ProductSkus.Single(x => x.Id == eventData.Plan.ProductSkuId); - - var hashToken = await FlashSalePlanHasher.HashAsync(eventData.Plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); - - if (hashToken != eventData.HashToken) - { - await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto() - { - TenantId = eventData.TenantId, - PlanId = eventData.PlanId, - OrderId = null, - UserId = eventData.UserId, - StoreId = eventData.StoreId, - PendingResultId = eventData.PendingResultId, - Success = false, - Reason = FlashSaleResultFailedReason.PreOrderExipred - }); - return; - } - - if (!await ProductManager.TryReduceInventoryAsync(product, productSku, 1, true)) - { - await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto() - { - TenantId = eventData.TenantId, - PlanId = eventData.PlanId, - OrderId = null, - UserId = eventData.UserId, - StoreId = eventData.StoreId, - PendingResultId = eventData.PendingResultId, - Success = false, - Reason = FlashSaleResultFailedReason.InsufficientInventory - }); - return; - } - - var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; - - var productDetailEto = productDetailId.HasValue ? - ObjectMapper.Map(await ProductDetailRepository.GetAsync(productDetailId.Value)) : - null; - - var productEto = ObjectMapper.Map(product); - - var createFlashSalesOrderEto = new CreateFlashSaleOrderEto() - { - TenantId = eventData.TenantId, - PlanId = eventData.PlanId, - UserId = eventData.UserId, - PendingResultId = eventData.PendingResultId, - StoreId = eventData.StoreId, - CreateTime = eventData.CreateTime, - CustomerRemark = eventData.CustomerRemark, - Product = productEto, - ProductDetail = productDetailEto, - Plan = eventData.Plan - }; - - foreach (var item in eventData.ExtraProperties) - { - createFlashSalesOrderEto.ExtraProperties.Add(item.Key, item.Value); - } - - await DistributedEventBus.PublishAsync(createFlashSalesOrderEto); - } -} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/ILocalFlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/ILocalFlashSaleInventoryManager.cs new file mode 100644 index 00000000..95982ed4 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/ILocalFlashSaleInventoryManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using EasyAbp.Eshop.Products.Products; + +namespace EasyAbp.EShop.Products.Products; + +public interface ILocalFlashSaleInventoryManager : IFlashSaleInventoryManager +{ +} 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 new file mode 100644 index 00000000..f13f758e --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EasyAbp.EShop.Products.ProductInventories; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Products.Products; + +public class LocalFlashSaleInventoryManager : ILocalFlashSaleInventoryManager, ITransientDependency +{ + protected IProductInventoryProviderResolver ProductInventoryProviderResolver { get; } + + public LocalFlashSaleInventoryManager(IProductInventoryProviderResolver productInventoryProviderResolver) + { + ProductInventoryProviderResolver = productInventoryProviderResolver; + } + + public virtual async Task TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + Guid productSkuId, int quantity, bool increaseSold) + { + var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId); + return await (await ProductInventoryProviderResolver.GetAsync(providerName)) + .TryReduceInventoryAsync(model, quantity, increaseSold); + } + + public virtual async Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + Guid productSkuId, int quantity, bool decreaseSold) + { + var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId); + return await (await ProductInventoryProviderResolver.GetAsync(providerName)) + .TryIncreaseInventoryAsync(model, quantity, decreaseSold); + } +}