Browse Source

Merge pull request #159 from EasyAbp/inventory-provider-resolver

Support multi-inventory-provider
pull/161/head
Super 4 years ago
committed by GitHub
parent
commit
bd15d5146c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
  2. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
  3. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
  4. 10
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
  5. 10
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
  6. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
  7. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
  8. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
  9. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
  10. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
  11. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
  12. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
  13. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
  14. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
  15. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/IProductInventoryProvider.cs
  16. 15
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs
  17. 11
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
  18. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
  19. 16
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs
  20. 20
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs
  21. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/IInventoryProviderConfigurationProvider.cs
  22. 13
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfiguration.cs
  23. 20
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurationProvider.cs
  24. 59
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurations.cs
  25. 13
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs
  26. 9
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs
  27. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs
  28. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/NonexistentInventoryProviderException.cs
  29. 43
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
  30. 78
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs
  31. 35
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
  32. 24
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
  33. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
  34. 48
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
  35. 16
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs
  36. 60
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/FakeProductInventoryProvider.cs
  37. 2
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs
  38. 5177
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.Designer.cs
  39. 35
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.cs
  40. 16
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs

@ -34,7 +34,10 @@ namespace EasyAbp.EShop.Products.Products.Dtos
[DisplayName("ProductInventoryStrategy")]
public InventoryStrategy InventoryStrategy { get; set; }
[DisplayName("ProductInventoryProviderName")]
public string InventoryProviderName { get; set; }
[DisplayName("ProductDisplayOrder")]
public int DisplayOrder { get; set; }

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs

@ -22,6 +22,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public InventoryStrategy InventoryStrategy { get; set; }
public string InventoryProviderName { get; set; }
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs

@ -19,6 +19,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public InventoryStrategy InventoryStrategy { get; set; }
public string InventoryProviderName { get; set; }
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }

10
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs

@ -14,16 +14,16 @@ namespace EasyAbp.EShop.Products.ProductInventories
{
private readonly IProductRepository _productRepository;
private readonly IProductInventoryRepository _repository;
private readonly DefaultProductInventoryProvider _productInventoryProvider;
private readonly DefaultProductInventoryProvider _defaultProductInventoryProvider;
public ProductInventoryAppService(
IProductRepository productRepository,
IProductInventoryRepository repository,
DefaultProductInventoryProvider productInventoryProvider)
DefaultProductInventoryProvider defaultProductInventoryProvider)
{
_productRepository = productRepository;
_repository = repository;
_productInventoryProvider = productInventoryProvider;
_defaultProductInventoryProvider = defaultProductInventoryProvider;
}
[Authorize(ProductsPermissions.ProductInventory.Default)]
@ -85,7 +85,7 @@ namespace EasyAbp.EShop.Products.ProductInventories
if (changedInventory >= 0)
{
if (!await _productInventoryProvider.TryIncreaseInventoryAsync(model, changedInventory, false))
if (!await _defaultProductInventoryProvider.TryIncreaseInventoryAsync(model, changedInventory, false))
{
throw new InventoryChangeFailedException(productInventory.ProductId, productInventory.ProductSkuId,
productInventory.Inventory, changedInventory);
@ -93,7 +93,7 @@ namespace EasyAbp.EShop.Products.ProductInventories
}
else
{
if (!await _productInventoryProvider.TryReduceInventoryAsync(model, -changedInventory, false))
if (!await _defaultProductInventoryProvider.TryReduceInventoryAsync(model, -changedInventory, false))
{
throw new InventoryChangeFailedException(productInventory.ProductId, productInventory.ProductSkuId,
productInventory.Inventory, changedInventory);

10
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs

@ -29,7 +29,7 @@ namespace EasyAbp.EShop.Products.Products
private readonly IProductManager _productManager;
private readonly IDistributedCache<ProductViewCacheItem> _cache;
private readonly EShopProductsOptions _options;
private readonly IProductInventoryProvider _productInventoryProvider;
private readonly IProductInventoryProviderResolver _productInventoryProviderResolver;
private readonly IProductViewCacheKeyProvider _productViewCacheKeyProvider;
private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer;
private readonly IProductRepository _repository;
@ -38,7 +38,7 @@ namespace EasyAbp.EShop.Products.Products
IProductManager productManager,
IOptions<EShopProductsOptions> options,
IDistributedCache<ProductViewCacheItem> cache,
IProductInventoryProvider productInventoryProvider,
IProductInventoryProviderResolver productInventoryProviderResolver,
IProductViewCacheKeyProvider productViewCacheKeyProvider,
IAttributeOptionIdsSerializer attributeOptionIdsSerializer,
IProductRepository repository) : base(repository)
@ -46,7 +46,7 @@ namespace EasyAbp.EShop.Products.Products
_productManager = productManager;
_cache = cache;
_options = options.Value;
_productInventoryProvider = productInventoryProvider;
_productInventoryProviderResolver = productInventoryProviderResolver;
_productViewCacheKeyProvider = productViewCacheKeyProvider;
_attributeOptionIdsSerializer = attributeOptionIdsSerializer;
_repository = repository;
@ -290,7 +290,9 @@ namespace EasyAbp.EShop.Products.Products
var models = product.ProductSkus.Select(x =>
new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, x.Id)).ToList();
var inventoryDataDict = await _productInventoryProvider.GetSkuIdInventoryDataMappingAsync(models);
var inventoryProvider = await _productInventoryProviderResolver.GetAsync(product);
var inventoryDataDict = await inventoryProvider.GetSkuIdInventoryDataMappingAsync(models);
productDto.Sold = 0;

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json

@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json

@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json

@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json

@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json

@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json

@ -41,6 +41,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -86,6 +88,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json

@ -40,6 +40,8 @@
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
"ProductInventoryStrategy": "Inventory strategy",
"ProductInventoryProviderName": "Inventory provider",
"ProductInventoryProviderNamePlaceholder": "Keep it empty if you don't understand",
"InventoryStrategy.NoNeed": "No need",
"InventoryStrategy.ReduceAfterPlacing": "Reduce inventory after placing",
"InventoryStrategy.ReduceAfterPayment": "Reduce inventory after payment",
@ -85,6 +87,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "The product unique name '{uniqueName}' is duplicated.",
"EasyAbp.EShop.Products:InventoryChangeFailed": "Inventory of product {productId} (SKU: {productSkuId}) cannot be changed by {changedInventory} from {originalInventory}",
"EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) is nonexistent.",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "The specified inventory provider ({inventoryProviderName}) is nonexistent.",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "Sku code {code} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "Sku {serializedAttributeOptionIds} is duplicate for the product {productId}",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "Sku {serializedAttributeOptionIds} is incorrect for the product {productId}",

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json

@ -41,6 +41,8 @@
"EditProductSku": "编辑",
"ProductSkuDeletionConfirmationMessage": "确认删除 SKU {0}?",
"ProductInventoryStrategy": "库存扣减策略",
"ProductInventoryProviderName": "库存提供者",
"ProductInventoryProviderNamePlaceholder": "如果你不理解它的意义,请留空",
"InventoryStrategy.NoNeed": "无需库存",
"InventoryStrategy.ReduceAfterPlacing": "下单后减库存",
"InventoryStrategy.ReduceAfterPayment": "支付后减库存",
@ -83,6 +85,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的产品名称'{uniqueName}'重复",
"EasyAbp.EShop.Products:InventoryChangeFailed": "产品{productId} (SKU: {productSkuId})的库存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的产品组({productGroupName})不存在",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "指定的库存提供者({inventoryProviderName})不存在",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代码{code}重复",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重复",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正确",

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json

@ -41,7 +41,9 @@
"EditProductSku": "編輯",
"ProductSkuDeletionConfirmationMessage": "確認刪除 SKU {0}?",
"ProductInventoryStrategy": "庫存扣減策略",
"InventoryStrategy.NoNeed": "No need",
"ProductInventoryProviderName": "庫存提供者",
"ProductInventoryProviderNamePlaceholder": "如果你不理解它的意義,請留空",
"InventoryStrategy.NoNeed": "無需庫存",
"InventoryStrategy.ReduceAfterPlacing": "下單後減庫存",
"InventoryStrategy.ReduceAfterPayment": "支付後減庫存",
"ProductIsPublished": "是否發布",
@ -83,6 +85,7 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的產品名稱'{uniqueName}'重復",
"EasyAbp.EShop.Products:InventoryChangeFailed": "產品{productId} (SKU: {productSkuId})的庫存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的產品組({productGroupName})不存在",
"EasyAbp.EShop.Products:NonexistentInventoryProvider": "指定的庫存提供者({inventoryProviderName})不存在",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代碼{code}重復",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重復",
"EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正確",

4
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductInventories/IProductInventoryProvider.cs

@ -6,12 +6,14 @@ namespace EasyAbp.EShop.Products.ProductInventories
{
public interface IProductInventoryProvider
{
string InventoryProviderName { get; }
Task<InventoryDataModel> GetInventoryDataAsync(InventoryQueryModel model);
Task<Dictionary<Guid, InventoryDataModel>> GetSkuIdInventoryDataMappingAsync(IList<InventoryQueryModel> models);
Task<bool> TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, bool decreaseSold);
Task<bool> TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold);
}
}

15
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs

@ -1,5 +1,6 @@
using System;
using EasyAbp.EShop.Stores.Stores;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Products.Products
@ -7,23 +8,25 @@ namespace EasyAbp.EShop.Products.Products
public interface IProduct : IHasExtraProperties, IMultiStore
{
string ProductGroupName { get; }
Guid? ProductDetailId { get; }
string UniqueName { get; }
string DisplayName { get; }
InventoryStrategy InventoryStrategy { get; }
[CanBeNull] string InventoryProviderName { get; }
string MediaResources { get; }
int DisplayOrder { get; }
bool IsPublished { get; }
bool IsStatic { get; }
bool IsHidden { get; }
}
}

11
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
@ -11,11 +10,11 @@ namespace EasyAbp.EShop.Products.Products
public Guid? TenantId { get; set; }
public Guid Id { get; set; }
public Guid StoreId { get; set; }
public string ProductGroupName { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }
@ -24,6 +23,8 @@ namespace EasyAbp.EShop.Products.Products
public InventoryStrategy InventoryStrategy { get; set; }
public string InventoryProviderName { get; set; }
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }
@ -33,9 +34,9 @@ namespace EasyAbp.EShop.Products.Products
public bool IsStatic { get; set; }
public bool IsHidden { get; set; }
public List<ProductAttributeEto> ProductAttributes { get; set; }
public List<ProductSkuEto> ProductSkus { get; set; }
}
}

1
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs

@ -6,6 +6,7 @@
public const string DuplicatedProductUniqueName = "EasyAbp.EShop.Products:DuplicatedProductUniqueName";
public const string InventoryChangeFailed = "EasyAbp.EShop.Products:InventoryChangeFailed";
public const string NonexistentProductGroup = "EasyAbp.EShop.Products:NonexistentProductGroup";
public const string NonexistentInventoryProvider = "EasyAbp.EShop.Products:NonexistentInventoryProvider";
public const string ProductSkuCodeDuplicated = "EasyAbp.EShop.Products:ProductSkuCodeDuplicated";
public const string ProductSkuDuplicated = "EasyAbp.EShop.Products:ProductSkuDuplicated";
public const string ProductSkuIncorrectAttributeOptions = "EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions";

16
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/EShopProductsDomainModule.cs

@ -24,10 +24,10 @@ namespace EasyAbp.EShop.Products
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<Product, ProductEto>();
options.AutoEventSelectors.Add<Product>();
});
Configure<EShopProductsOptions>(options =>
{
options.Groups.Configure<DefaultProductGroup>(group =>
@ -35,6 +35,16 @@ namespace EasyAbp.EShop.Products
group.DisplayName = ProductsConsts.DefaultProductGroupDisplayName;
group.Description = ProductsConsts.DefaultProductGroupDescription;
});
options.InventoryProviders.Configure(
DefaultProductInventoryProvider.DefaultProductInventoryProviderName, provider =>
{
provider.DisplayName =
DefaultProductInventoryProvider.DefaultProductInventoryProviderDisplayName;
provider.Description =
DefaultProductInventoryProvider.DefaultProductInventoryProviderDescription;
provider.ProviderType = typeof(DefaultProductInventoryProvider);
});
});
}
@ -48,4 +58,4 @@ namespace EasyAbp.EShop.Products
});
}
}
}
}

20
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/EShopProductsOptions.cs

@ -1,17 +1,21 @@
using System;
using EasyAbp.EShop.Products.Options.InventoryProviders;
using EasyAbp.EShop.Products.Options.ProductGroups;
using EasyAbp.EShop.Products.Products;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Options
{
public class EShopProductsOptions
{
public ProductGroupConfigurations Groups { get; }
public ProductGroupConfigurations Groups { get; } = new();
public Type DefaultFileDownloadProviderType { get; set; }
public EShopProductsOptions()
{
Groups = new ProductGroupConfigurations();
}
public InventoryProviderConfigurations InventoryProviders { get; } = new();
/// <summary>
/// If the value is <c>null</c>, it will fall back to DefaultProductInventoryProviderName
/// in the <see cref="DefaultProductInventoryProvider"/>.
/// </summary>
[CanBeNull]
public string DefaultInventoryProviderName { get; set; }
}
}

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/IInventoryProviderConfigurationProvider.cs

@ -0,0 +1,7 @@
namespace EasyAbp.EShop.Products.Options.InventoryProviders
{
public interface IInventoryProviderConfigurationProvider
{
InventoryProviderConfiguration Get(string providerName);
}
}

13
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfiguration.cs

@ -0,0 +1,13 @@
using System;
namespace EasyAbp.EShop.Products.Options.InventoryProviders
{
public class InventoryProviderConfiguration
{
public string DisplayName { get; set; }
public string Description { get; set; }
public Type ProviderType { get; set; }
}
}

20
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurationProvider.cs

@ -0,0 +1,20 @@
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Options.InventoryProviders
{
public class InventoryProviderConfigurationProvider : IInventoryProviderConfigurationProvider, ITransientDependency
{
private readonly EShopProductsOptions _options;
public InventoryProviderConfigurationProvider(IOptions<EShopProductsOptions> options)
{
_options = options.Value;
}
public InventoryProviderConfiguration Get(string providerName)
{
return _options.InventoryProviders.GetConfiguration(providerName);
}
}
}

59
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/InventoryProviders/InventoryProviderConfigurations.cs

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp;
namespace EasyAbp.EShop.Products.Options.InventoryProviders
{
public class InventoryProviderConfigurations
{
private readonly Dictionary<string, InventoryProviderConfiguration> _providers;
public InventoryProviderConfigurations()
{
_providers = new Dictionary<string, InventoryProviderConfiguration>();
}
public InventoryProviderConfigurations Configure(
[NotNull] string name,
[NotNull] Action<InventoryProviderConfiguration> configureAction)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNull(configureAction, nameof(configureAction));
configureAction(
_providers.GetOrAdd(
name,
() => new InventoryProviderConfiguration()
)
);
return this;
}
public InventoryProviderConfigurations ConfigureAll(
Action<string, InventoryProviderConfiguration> configureAction)
{
foreach (var provider in _providers)
{
configureAction(provider.Key, provider.Value);
}
return this;
}
[NotNull]
public InventoryProviderConfiguration GetConfiguration([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
return _providers.GetOrDefault(name);
}
[NotNull]
public Dictionary<string, InventoryProviderConfiguration> GetConfigurationsDictionary()
{
return _providers;
}
}
}

13
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/ProductGroupConfiguration.cs

@ -1,9 +1,18 @@
namespace EasyAbp.EShop.Products.Options.ProductGroups
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Options.ProductGroups
{
public class ProductGroupConfiguration
{
public string DisplayName { get; set; }
public string Description { get; set; }
/// <summary>
/// If the value is <c>null</c>, it will fall back to DefaultInventoryProviderName
/// in the <see cref="EShopProductsOptions"/>.
/// </summary>
[CanBeNull]
public string DefaultInventoryProviderName { get; set; }
}
}

9
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductInventoryProvider.cs

@ -13,23 +13,26 @@ namespace EasyAbp.EShop.Products.Products
{
public class DefaultProductInventoryProvider : IProductInventoryProvider, ITransientDependency
{
public static string DefaultProductInventoryProviderName { get; set; } = "Default";
public static string DefaultProductInventoryProviderDisplayName { get; set; } = "Default";
public static string DefaultProductInventoryProviderDescription { get; set; } = "Default";
public string InventoryProviderName { get; } = DefaultProductInventoryProviderName;
// Todo: should use IProductInventoryStore.
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IDistributedEventBus _distributedEventBus;
private readonly IProductInventoryRepository _productInventoryRepository;
public DefaultProductInventoryProvider(
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
IDistributedEventBus distributedEventBus,
IProductInventoryRepository productInventoryRepository)
{
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_unitOfWorkManager = unitOfWorkManager;
_distributedEventBus = distributedEventBus;
_productInventoryRepository = productInventoryRepository;
}

12
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs

@ -0,0 +1,12 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductInventories;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
public interface IProductInventoryProviderResolver
{
Task<bool> ExistProviderAsync([NotNull] string providerName);
Task<IProductInventoryProvider> GetAsync(Product product);
}

14
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/NonexistentInventoryProviderException.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp;
namespace EasyAbp.EShop.Products.Products
{
public class NonexistentInventoryProviderException : BusinessException
{
public NonexistentInventoryProviderException(string inventoryProviderName) :
base(ProductsErrorCodes.NonexistentInventoryProvider)
{
WithData(nameof(inventoryProviderName), inventoryProviderName);
}
}
}

43
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs

@ -1,6 +1,7 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Options.ProductGroups;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
@ -9,37 +10,39 @@ namespace EasyAbp.EShop.Products.Products
public class Product : FullAuditedAggregateRoot<Guid>, IProduct, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid StoreId { get; protected set; }
[NotNull]
public virtual string ProductGroupName { get; protected set; }
[NotNull] public virtual string ProductGroupName { get; protected set; }
public virtual Guid? ProductDetailId { get; protected set; }
[CanBeNull]
public virtual string UniqueName { get; protected set; }
[CanBeNull] public virtual string UniqueName { get; protected set; }
[NotNull] public virtual string DisplayName { get; protected set; }
[NotNull]
public virtual string DisplayName { get; protected set; }
public virtual InventoryStrategy InventoryStrategy { get; protected set; }
[CanBeNull]
public virtual string MediaResources { get; protected set; }
/// <summary>
/// If the value is <c>null</c>, it will fall back to DefaultInventoryProviderName
/// in the <see cref="ProductGroupConfiguration"/>.
/// </summary>
public virtual string InventoryProviderName { get; protected set; }
[CanBeNull] public virtual string MediaResources { get; protected set; }
public virtual int DisplayOrder { get; protected set; }
public virtual bool IsPublished { get; protected set; }
public virtual bool IsStatic { get; protected set; }
public virtual bool IsHidden { get; protected set; }
public virtual TimeSpan? PaymentExpireIn { get; protected set; }
public virtual List<ProductAttribute> ProductAttributes { get; protected set; }
public virtual List<ProductSku> ProductSkus { get; protected set; }
protected Product()
@ -55,6 +58,7 @@ namespace EasyAbp.EShop.Products.Products
[CanBeNull] string uniqueName,
[NotNull] string displayName,
InventoryStrategy inventoryStrategy,
[CanBeNull] string inventoryProviderName,
bool isPublished,
bool isStatic,
bool isHidden,
@ -70,13 +74,14 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = uniqueName?.Trim();
DisplayName = displayName;
InventoryStrategy = inventoryStrategy;
InventoryProviderName = inventoryProviderName;
IsPublished = isPublished;
IsStatic = isStatic;
IsHidden = isHidden;
PaymentExpireIn = paymentExpireIn;
MediaResources = mediaResources;
DisplayOrder = displayOrder;
ProductAttributes = new List<ProductAttribute>();
ProductSkus = new List<ProductSku>();
}
@ -92,4 +97,4 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = UniqueName?.Trim();
}
}
}
}

78
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs

@ -0,0 +1,78 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Options;
using EasyAbp.EShop.Products.ProductInventories;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products;
public class ProductInventoryProviderResolver : IProductInventoryProviderResolver, ITransientDependency
{
protected static ConcurrentDictionary<string, Type> NameToProviderTypeMapping { get; } = new();
protected IServiceProvider ServiceProvider { get; }
public ProductInventoryProviderResolver(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public virtual Task<bool> ExistProviderAsync(string providerName)
{
TryBuildNameToProviderTypeMapping();
return Task.FromResult(NameToProviderTypeMapping.ContainsKey(providerName));
}
public virtual Task<IProductInventoryProvider> GetAsync(Product product)
{
if (!product.InventoryProviderName.IsNullOrWhiteSpace())
{
return Task.FromResult(GetProviderByName(product.InventoryProviderName));
}
var options = ServiceProvider.GetRequiredService<IOptions<EShopProductsOptions>>();
var productGroupConfiguration = options.Value.Groups.GetConfiguration(product.ProductGroupName);
if (!productGroupConfiguration.DefaultInventoryProviderName.IsNullOrWhiteSpace())
{
return Task.FromResult(GetProviderByName(productGroupConfiguration.DefaultInventoryProviderName));
}
return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName));
}
protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName)
{
if (providerName.IsNullOrEmpty())
{
providerName = DefaultProductInventoryProvider.DefaultProductInventoryProviderName;
}
TryBuildNameToProviderTypeMapping();
var providerType = NameToProviderTypeMapping[providerName!];
return (IProductInventoryProvider)ServiceProvider.GetService(providerType);
}
protected virtual void TryBuildNameToProviderTypeMapping()
{
if (!NameToProviderTypeMapping.IsEmpty)
{
return;
}
var options = ServiceProvider.GetRequiredService<IOptions<EShopProductsOptions>>().Value;
foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary())
{
NameToProviderTypeMapping[pair.Key] = pair.Value.ProviderType;
}
}
}

35
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs

@ -6,7 +6,6 @@ using EasyAbp.EShop.Products.Options.ProductGroups;
using EasyAbp.EShop.Products.ProductCategories;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductInventories;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Services;
using Volo.Abp.Uow;
@ -19,7 +18,7 @@ namespace EasyAbp.EShop.Products.Products
private readonly IProductPriceProvider _productPriceProvider;
private readonly IProductDetailRepository _productDetailRepository;
private readonly IProductCategoryRepository _productCategoryRepository;
private readonly IProductInventoryProvider _productInventoryProvider;
private readonly IProductInventoryProviderResolver _productInventoryProviderResolver;
private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer;
private readonly IProductGroupConfigurationProvider _productGroupConfigurationProvider;
@ -28,7 +27,7 @@ namespace EasyAbp.EShop.Products.Products
IProductPriceProvider productPriceProvider,
IProductDetailRepository productDetailRepository,
IProductCategoryRepository productCategoryRepository,
IProductInventoryProvider productInventoryProvider,
IProductInventoryProviderResolver productInventoryProviderResolver,
IAttributeOptionIdsSerializer attributeOptionIdsSerializer,
IProductGroupConfigurationProvider productGroupConfigurationProvider)
{
@ -36,7 +35,7 @@ namespace EasyAbp.EShop.Products.Products
_productPriceProvider = productPriceProvider;
_productDetailRepository = productDetailRepository;
_productCategoryRepository = productCategoryRepository;
_productInventoryProvider = productInventoryProvider;
_productInventoryProviderResolver = productInventoryProviderResolver;
_attributeOptionIdsSerializer = attributeOptionIdsSerializer;
_productGroupConfigurationProvider = productGroupConfigurationProvider;
}
@ -48,6 +47,8 @@ namespace EasyAbp.EShop.Products.Products
await CheckProductGroupNameAsync(product);
await CheckInventoryProviderNameAsync(product);
await CheckProductUniqueNameAsync(product);
await _productRepository.InsertAsync(product, autoSave: true);
@ -69,11 +70,26 @@ namespace EasyAbp.EShop.Products.Products
return Task.CompletedTask;
}
protected virtual async Task CheckInventoryProviderNameAsync(Product product)
{
if (product.InventoryProviderName.IsNullOrEmpty())
{
return;
}
if (!await _productInventoryProviderResolver.ExistProviderAsync(product.InventoryProviderName!))
{
throw new NonexistentInventoryProviderException(product.InventoryProviderName);
}
}
[UnitOfWork(true)]
public virtual async Task<Product> UpdateAsync(Product product, IEnumerable<Guid> categoryIds = null)
{
await CheckProductGroupNameAsync(product);
await CheckInventoryProviderNameAsync(product);
await CheckProductUniqueNameAsync(product);
await _productRepository.UpdateAsync(product, autoSave: true);
@ -225,7 +241,8 @@ namespace EasyAbp.EShop.Products.Products
{
var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
var inventoryData = await _productInventoryProvider.GetInventoryDataAsync(model);
var inventoryData =
await (await _productInventoryProviderResolver.GetAsync(product)).GetInventoryDataAsync(model);
return product.InventoryStrategy == InventoryStrategy.NoNeed || inventoryData.Inventory - quantity >= 0;
}
@ -234,7 +251,7 @@ namespace EasyAbp.EShop.Products.Products
{
var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
return await _productInventoryProvider.GetInventoryDataAsync(model);
return await (await _productInventoryProviderResolver.GetAsync(product)).GetInventoryDataAsync(model);
}
public virtual async Task<bool> TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity,
@ -242,7 +259,8 @@ namespace EasyAbp.EShop.Products.Products
{
var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
return await _productInventoryProvider.TryIncreaseInventoryAsync(model, quantity, reduceSold);
return await (await _productInventoryProviderResolver.GetAsync(product))
.TryIncreaseInventoryAsync(model, quantity, reduceSold);
}
public virtual async Task<bool> TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity,
@ -250,7 +268,8 @@ namespace EasyAbp.EShop.Products.Products
{
var model = new InventoryQueryModel(product.TenantId, product.StoreId, product.Id, productSku.Id);
return await _productInventoryProvider.TryReduceInventoryAsync(model, quantity, increaseSold);
return await (await _productInventoryProviderResolver.GetAsync(product))
.TryReduceInventoryAsync(model, quantity, increaseSold);
}
public virtual async Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku)

24
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs

@ -22,6 +22,8 @@ namespace EasyAbp.EShop.Products.Products
public virtual InventoryStrategy InventoryStrategy { get; protected set; }
public string InventoryProviderName { get; protected set; }
public virtual string MediaResources { get; protected set; }
public virtual int DisplayOrder { get; protected set; }
@ -33,19 +35,19 @@ namespace EasyAbp.EShop.Products.Products
public virtual bool IsHidden { get; protected set; }
#endregion
public virtual string ProductGroupDisplayName { get; protected set; }
public virtual decimal? MinimumPrice { get; protected set; }
public virtual decimal? MaximumPrice { get; protected set; }
public virtual long Sold { get; protected set; }
protected ProductView()
{
}
public ProductView(
Guid id,
Guid? tenantId,
@ -55,6 +57,7 @@ namespace EasyAbp.EShop.Products.Products
string uniqueName,
string displayName,
InventoryStrategy inventoryStrategy,
string inventoryProviderName,
bool isPublished,
bool isStatic,
bool isHidden,
@ -73,27 +76,28 @@ namespace EasyAbp.EShop.Products.Products
UniqueName = uniqueName?.Trim();
DisplayName = displayName;
InventoryStrategy = inventoryStrategy;
InventoryProviderName = inventoryProviderName;
IsPublished = isPublished;
IsStatic = isStatic;
IsHidden = isHidden;
MediaResources = mediaResources;
DisplayOrder = displayOrder;
ProductGroupDisplayName = productGroupDisplayName;
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
Sold = sold;
}
public void SetSold(long sold)
{
Sold = sold;
}
public void SetPrices(decimal? minimumPrice, decimal? maximumPrice)
{
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
}
}
}
}

6
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs

@ -50,7 +50,11 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewM
[Display(Name = "ProductInventoryStrategy")]
public InventoryStrategy InventoryStrategy { get; set; }
[Placeholder("ProductInventoryProviderNamePlaceholder")]
[Display(Name = "ProductInventoryProviderName")]
public string InventoryProviderName { get; set; }
[Display(Name = "ProductDisplayOrder")]
public int DisplayOrder { get; set; }

48
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs

@ -25,13 +25,12 @@ namespace EasyAbp.EShop.Products.Products
public async Task Should_Set_ProductDetailId()
{
var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, true, false, false, null,
null, 0);
ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, null, true, false, false,
null, null, 0);
await ProductManager.CreateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails2Id);
}
@ -42,9 +41,9 @@ namespace EasyAbp.EShop.Products.Products
{
var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
var sku1 = product1.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
sku1.ProductDetailId.ShouldBeNull();
typeof(ProductSku).GetProperty(nameof(ProductSku.ProductDetailId))!.SetValue(sku1,
ProductsTestData.ProductDetails1Id);
@ -65,20 +64,20 @@ namespace EasyAbp.EShop.Products.Products
product1.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails1Id);
var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, true, false, false, null,
null, 0);
ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, null, true, false, false,
null, null, 0);
await ProductManager.CreateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails2Id);
typeof(Product).GetProperty(nameof(Product.ProductDetailId))!.SetValue(product2,
ProductsTestData.ProductDetails1Id);
await ProductManager.UpdateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails1Id);
@ -88,17 +87,17 @@ namespace EasyAbp.EShop.Products.Products
public async Task Should_Remove_ProductDetailId()
{
await Should_Set_ProductDetailId();
var product2 = await ProductRepository.GetAsync(ProductsTestData.Product2Id);
product2.ProductDetailId.ShouldNotBeNull();
typeof(Product).GetProperty(nameof(Product.ProductDetailId))!.SetValue(product2, null);
await ProductManager.UpdateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBeNull();
}
@ -135,7 +134,7 @@ namespace EasyAbp.EShop.Products.Products
ProductsTestData.Product1Attribute1Option4Id,
ProductsTestData.Product1Attribute2Option2Id
};
await Should.NotThrowAsync(async () =>
{
await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(attributeOptionIds));
@ -178,10 +177,27 @@ namespace EasyAbp.EShop.Products.Products
});
}
[Fact]
public async Task Should_Use_Fake_Inventory_Provider()
{
var product2 = new Product(ProductsTestData.Product2Id, null, ProductsTestData.Store1Id, "Default",
ProductsTestData.ProductDetails2Id, "Ball", "Ball", InventoryStrategy.NoNeed, "Fake", true, false,
false, null, null, 0);
await ProductManager.CreateAsync(product2);
var fakeSku = new ProductSku(Guid.NewGuid(), "", null, "", null, 0m, 1, 1, null, null, null);
var inventoryDataModel = await ProductManager.GetInventoryDataAsync(product2, fakeSku);
inventoryDataModel.ShouldNotBeNull();
inventoryDataModel.Inventory.ShouldBe(9998);
}
private async Task<ProductSku> CreateTestSkuAsync(IEnumerable<Guid> attributeOptionIds)
{
return new ProductSku(Guid.NewGuid(), await AttributeOptionIdsSerializer.SerializeAsync(attributeOptionIds),
"test-sku", "CNY", null, 0m, 1, 10, null, null, null);
}
}
}
}

16
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/EShopProductsTestBaseModule.cs

@ -1,4 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using EasyAbp.EShop.Products.Options;
using EasyAbp.EShop.Products.Options.ProductGroups;
using EasyAbp.EShop.Products.Products;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
@ -19,6 +22,17 @@ namespace EasyAbp.EShop.Products
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
Configure<EShopProductsOptions>(options =>
{
options.InventoryProviders.Configure(
"Fake", provider =>
{
provider.DisplayName = "Fake";
provider.Description = "For tests";
provider.ProviderType = typeof(FakeProductInventoryProvider);
});
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

60
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/FakeProductInventoryProvider.cs

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductInventories;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products;
public class FakeProductInventoryProvider : IProductInventoryProvider, ITransientDependency
{
public string InventoryProviderName { get; } = "Fake";
private static InventoryDataModel Model { get; } = new()
{
Inventory = 9998,
Sold = 0
};
public Task<InventoryDataModel> GetInventoryDataAsync(InventoryQueryModel model)
{
return Task.FromResult(Model);
}
public Task<Dictionary<Guid, InventoryDataModel>> GetSkuIdInventoryDataMappingAsync(
IList<InventoryQueryModel> models)
{
var result = new Dictionary<Guid, InventoryDataModel>();
foreach (var model in models)
{
result.Add(model.ProductSkuId, Model);
}
return Task.FromResult(result);
}
public Task<bool> TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity, bool decreaseSold)
{
Model.Inventory++;
if (decreaseSold)
{
Model.Sold--;
}
return Task.FromResult(true);
}
public Task<bool> TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold)
{
Model.Inventory--;
if (increaseSold)
{
Model.Sold++;
}
return Task.FromResult(true);
}
}

2
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs

@ -45,7 +45,7 @@ namespace EasyAbp.EShop.Products
"Product details for store 1"), true);
var product = new Product(ProductsTestData.Product1Id, null, ProductsTestData.Store1Id, "Default",
productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, true, false, false, null, null, 0);
productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, null, true, false, false, null, null, 0);
var attribute1 = new ProductAttribute(ProductsTestData.Product1Attribute1Id, "Size", null, 2);
var attribute2 = new ProductAttribute(ProductsTestData.Product1Attribute2Id, "Color", null, 1);

5177
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.Designer.cs

File diff suppressed because it is too large

35
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220610120322_AddedInventoryProviderName.cs

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
public partial class AddedInventoryProviderName : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "InventoryProviderName",
table: "EasyAbpEShopProductsProductViews",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "InventoryProviderName",
table: "EasyAbpEShopProductsProducts",
type: "nvarchar(max)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "InventoryProviderName",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "InventoryProviderName",
table: "EasyAbpEShopProductsProducts");
}
}
}

16
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

@ -19,7 +19,7 @@ namespace EShopSample.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("ProductVersion", "6.0.4")
.HasAnnotation("ProductVersion", "6.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
@ -1397,6 +1397,9 @@ namespace EShopSample.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("InventoryProviderName")
.HasColumnType("nvarchar(max)");
b.Property<int>("InventoryStrategy")
.HasColumnType("int");
@ -1680,6 +1683,9 @@ namespace EShopSample.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("InventoryProviderName")
.HasColumnType("nvarchar(max)");
b.Property<int>("InventoryStrategy")
.HasColumnType("int");
@ -4683,14 +4689,14 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderExtraFee", b =>
{
b.HasOne("EasyAbp.EShop.Payments.Refunds.RefundItem", null)
.WithMany("RefundItemOrderExtraFees")
.WithMany("OrderExtraFees")
.HasForeignKey("RefundItemId");
});
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItemOrderLine", b =>
{
b.HasOne("EasyAbp.EShop.Payments.Refunds.RefundItem", null)
.WithMany("RefundItemOrderLines")
.WithMany("OrderLines")
.HasForeignKey("RefundItemId");
});
@ -5041,9 +5047,9 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.EShop.Payments.Refunds.RefundItem", b =>
{
b.Navigation("RefundItemOrderExtraFees");
b.Navigation("OrderExtraFees");
b.Navigation("RefundItemOrderLines");
b.Navigation("OrderLines");
});
modelBuilder.Entity("EasyAbp.EShop.Plugins.Coupons.CouponTemplates.CouponTemplate", b =>

Loading…
Cancel
Save