From 378d145a200c942097f1c07b556cd7d0731efb69 Mon Sep 17 00:00:00 2001 From: gdlcf88 <47396430@qq.com> Date: Fri, 1 May 2020 02:45:44 +0800 Subject: [PATCH] Improved product purchasable check, related to: #18 --- .../Orders/Order.cs | 3 ++ .../Products/Products/IProductAppService.cs | 5 +- .../Products/Products/ProductAppService.cs | 11 ++-- .../Products/CheckProductPurchasableResult.cs | 17 +++++++ .../GetProductPurchasableStatusResult.cs | 9 ---- .../Products/ProductPurchasableStatus.cs | 8 --- .../BasicProductPurchasableCheckHandler.cs | 51 +++++++++++++++++++ .../DefaultProductInventoryProvider.cs | 22 ++++++++ .../Products/IProductInventoryProvider.cs | 12 +++++ .../Products/Products/IProductManager.cs | 16 ++++++ .../IProductPurchasableCheckHandler.cs | 12 +++++ .../IProductPurchasableStatusProvider.cs | 13 ----- .../EShop/Products/Products/ProductManager.cs | 40 +++++++++++++++ ...bleCheckHandlerMissingPropertyException.cs | 13 +++++ .../ProductPurchasableStatusProvider.cs | 29 ----------- 15 files changed, 195 insertions(+), 66 deletions(-) create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/CheckProductPurchasableResult.cs delete mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/GetProductPurchasableStatusResult.cs delete mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductPurchasableStatus.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/BasicProductPurchasableCheckHandler.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableCheckHandler.cs delete mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableStatusProvider.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableCheckHandlerMissingPropertyException.cs delete mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableStatusProvider.cs diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/Orders/Order.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/Orders/Order.cs index 4f91069e..d255169f 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/Orders/Order.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/Orders/Order.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using EasyAbp.EShop.Stores.Stores; using JetBrains.Annotations; using Volo.Abp.Domain.Entities.Auditing; @@ -40,5 +41,7 @@ namespace EasyAbp.EShop.Orders.Orders [CanBeNull] public virtual string StaffRemark { get; protected set; } + + public virtual List OrderLines { get; protected set; } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs index b88f4367..d912fd40 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductAppService.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using EasyAbp.EShop.Products.Products.Dtos; -using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; namespace EasyAbp.EShop.Products.Products @@ -24,6 +24,7 @@ namespace EasyAbp.EShop.Products.Products Task DeleteSkuAsync(Guid productId, Guid productSkuId, Guid storeId); - Task GetPurchasableStatusAsync(Guid productId, Guid productSkuId, Guid storeId); + Task CheckPurchasableAsync(Guid productId, Guid productSkuId, Guid storeId, + Dictionary extraProperties); } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs index 8c6b29ab..d9ffc915 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs @@ -24,20 +24,20 @@ namespace EasyAbp.EShop.Products.Products protected override string GetPolicyName { get; set; } = null; protected override string GetListPolicyName { get; set; } = null; - private readonly IProductPurchasableStatusProvider _productPurchasableStatusProvider; + private readonly IProductManager _productManager; private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer; private readonly IProductStoreRepository _productStoreRepository; private readonly IProductCategoryRepository _productCategoryRepository; private readonly IProductRepository _repository; public ProductAppService( - IProductPurchasableStatusProvider productPurchasableStatusProvider, + IProductManager productManager, IAttributeOptionIdsSerializer attributeOptionIdsSerializer, IProductStoreRepository productStoreRepository, IProductCategoryRepository productCategoryRepository, IProductRepository repository) : base(repository) { - _productPurchasableStatusProvider = productPurchasableStatusProvider; + _productManager = productManager; _attributeOptionIdsSerializer = attributeOptionIdsSerializer; _productStoreRepository = productStoreRepository; _productCategoryRepository = productCategoryRepository; @@ -354,13 +354,14 @@ namespace EasyAbp.EShop.Products.Products return ObjectMapper.Map(product); } - public async Task GetPurchasableStatusAsync(Guid productId, Guid productSkuId, Guid storeId) + public async Task CheckPurchasableAsync(Guid productId, Guid productSkuId, + Guid storeId, Dictionary extraProperties) { var product = await _repository.GetAsync(productId); var productSku = product.ProductSkus.Single(sku => sku.Id == productSkuId); - return await _productPurchasableStatusProvider.GetPurchasableStatusAsync(product, productSku, storeId); + return await _productManager.GetPurchasableStatusAsync(product, productSku, storeId, extraProperties); } protected virtual async Task UpdateProductCategoriesAsync(Guid productId, IEnumerable categoryIds) diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/CheckProductPurchasableResult.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/CheckProductPurchasableResult.cs new file mode 100644 index 00000000..c0e9d6ac --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/CheckProductPurchasableResult.cs @@ -0,0 +1,17 @@ +namespace EasyAbp.EShop.Products.Products +{ + public class CheckProductPurchasableResult + { + public bool IsPurchasable { get; set; } + + public string Reason { get; set; } + + public CheckProductPurchasableResult( + bool isPurchasable, + string reason = null) + { + IsPurchasable = isPurchasable; + Reason = reason; + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/GetProductPurchasableStatusResult.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/GetProductPurchasableStatusResult.cs deleted file mode 100644 index 4307890e..00000000 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/GetProductPurchasableStatusResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace EasyAbp.EShop.Products.Products -{ - public class GetProductPurchasableStatusResult - { - public ProductPurchasableStatus PurchasableStatus { get; set; } - - public string Reason { get; set; } - } -} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductPurchasableStatus.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductPurchasableStatus.cs deleted file mode 100644 index d7b20303..00000000 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductPurchasableStatus.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace EasyAbp.EShop.Products.Products -{ - public enum ProductPurchasableStatus - { - CanBePurchased = 1, - CanNotBePurchased = 2 - } -} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/BasicProductPurchasableCheckHandler.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/BasicProductPurchasableCheckHandler.cs new file mode 100644 index 00000000..5999c393 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/BasicProductPurchasableCheckHandler.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Products.Products +{ + public class BasicProductPurchasableCheckHandler : IProductPurchasableCheckHandler, ITransientDependency + { + private readonly IProductInventoryProvider _productInventoryProvider; + + public BasicProductPurchasableCheckHandler( + IProductInventoryProvider productInventoryProvider) + { + _productInventoryProvider = productInventoryProvider; + } + + public async Task CheckAsync(Product product, ProductSku productSku, Guid storeId, + Dictionary extraProperties) + { + if (!await IsProductPublishedAsync(product)) + { + return new CheckProductPurchasableResult(false, "Unpublished project"); + } + + if (!await IsInventorySufficientAsync(product, productSku, storeId, extraProperties)) + { + return new CheckProductPurchasableResult(false, "Insufficient inventory"); + } + + return new CheckProductPurchasableResult(true); + } + + protected virtual Task IsProductPublishedAsync(Product product) + { + return Task.FromResult(product.IsPublished); + } + + protected virtual async Task IsInventorySufficientAsync(Product product, ProductSku productSku, Guid storeId, Dictionary extraProperties) + { + if (!extraProperties.TryGetValue("Quantity", out var quantity)) + { + throw new ProductPurchasableCheckHandlerMissingPropertyException( + nameof(BasicProductPurchasableCheckHandler), "Quantity"); + } + + return await _productInventoryProvider.IsInventorySufficientAsync(product, productSku, storeId, + Convert.ToInt32(quantity)); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs new file mode 100644 index 00000000..d8b65835 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Products.Products +{ + [Dependency(TryRegister = true)] + public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency + { + public virtual async Task IsInventorySufficientAsync(Product product, ProductSku productSku, Guid storeId, int quantity) + { + var inventory = await GetInventoryAsync(product, productSku, storeId); + + return product.InventoryStrategy == InventoryStrategy.NoNeed || inventory - quantity >= 0; + } + + public virtual Task GetInventoryAsync(Product product, ProductSku productSku, Guid storeId) + { + return Task.FromResult(productSku.Inventory); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs new file mode 100644 index 00000000..a9a95487 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace EasyAbp.EShop.Products.Products +{ + public interface IProductInventoryProvider + { + Task IsInventorySufficientAsync(Product product, ProductSku productSku, Guid storeId, int quantity); + + Task GetInventoryAsync(Product product, ProductSku productSku, Guid storeId); + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs new file mode 100644 index 00000000..8ba3e28b --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; + +namespace EasyAbp.EShop.Products.Products +{ + public interface IProductManager : IDomainService + { + Task CheckPurchasableAsync(Product product, ProductSku productSku, Guid storeId, + Dictionary extraProperties); + + Task GetPurchasableStatusAsync(Product product, ProductSku productSku, + Guid storeId, Dictionary extraProperties); + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableCheckHandler.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableCheckHandler.cs new file mode 100644 index 00000000..c671cf80 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableCheckHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace EasyAbp.EShop.Products.Products +{ + public interface IProductPurchasableCheckHandler + { + Task CheckAsync(Product product, ProductSku productSku, Guid storeId, + Dictionary extraProperties); + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableStatusProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableStatusProvider.cs deleted file mode 100644 index 80784e22..00000000 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPurchasableStatusProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace EasyAbp.EShop.Products.Products -{ - public interface IProductPurchasableStatusProvider - { - Task CheckPurchasableAsync(Product product, ProductSku productSku, Guid storeId); - - Task GetPurchasableStatusAsync(Product product, ProductSku productSku, - Guid storeId); - } -} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs new file mode 100644 index 00000000..9e7ea444 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Domain.Services; + +namespace EasyAbp.EShop.Products.Products +{ + public class ProductManager : DomainService, IProductManager + { + public async Task CheckPurchasableAsync(Product product, ProductSku productSku, Guid storeId, + Dictionary extraProperties) + { + var result = await GetPurchasableStatusAsync(product, productSku, storeId, extraProperties); + + if (!result.IsPurchasable) + { + throw new ProductIsNotPurchasableException(product.Id, result.Reason); + } + } + + public async Task GetPurchasableStatusAsync(Product product, + ProductSku productSku, Guid storeId, Dictionary extraProperties) + { + var handlers = ServiceProvider.GetServices(); + + foreach (var handler in handlers) + { + var result = await handler.CheckAsync(product, productSku, storeId, extraProperties); + + if (!result.IsPurchasable) + { + return result; + } + } + + return new CheckProductPurchasableResult(true); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableCheckHandlerMissingPropertyException.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableCheckHandlerMissingPropertyException.cs new file mode 100644 index 00000000..978cf2de --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableCheckHandlerMissingPropertyException.cs @@ -0,0 +1,13 @@ +using System; +using Volo.Abp; + +namespace EasyAbp.EShop.Products.Products +{ + public class ProductPurchasableCheckHandlerMissingPropertyException : BusinessException + { + public ProductPurchasableCheckHandlerMissingPropertyException(string handlerName, string propertyKey) : base( + message: $"The {handlerName} is missing extra property: {propertyKey}") + { + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableStatusProvider.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableStatusProvider.cs deleted file mode 100644 index cd57c392..00000000 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductPurchasableStatusProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace EasyAbp.EShop.Products.Products -{ - public class ProductPurchasableStatusProvider : IProductPurchasableStatusProvider, ITransientDependency - { - public async Task CheckPurchasableAsync(Product product, ProductSku productSku, Guid storeId) - { - var result = await GetPurchasableStatusAsync(product, productSku, storeId); - - if (result.PurchasableStatus != ProductPurchasableStatus.CanBePurchased) - { - throw new ProductIsNotPurchasableException(product.Id, result.Reason); - } - } - - public async Task GetPurchasableStatusAsync(Product product, ProductSku productSku, Guid storeId) - { - // Todo: Determine whether the product can be purchased. - return new GetProductPurchasableStatusResult - { - PurchasableStatus = ProductPurchasableStatus.CanBePurchased, - Reason = null - }; - } - } -} \ No newline at end of file