From f13d2ddd63761f17ed9563a8a53d8f99a80f569c Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Mon, 23 May 2022 02:29:56 +0800 Subject: [PATCH 1/7] Fix the bug with creating the wrong ProductInventory entity at SKU creation --- .../ProductInventoryAppService.cs | 13 +++++++++++++ .../Products/ProductSku/CreateModal.cshtml.cs | 8 +++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs index 3b63397f..3d595c87 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using System; using System.Threading.Tasks; using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Entities; namespace EasyAbp.EShop.Products.ProductInventories { @@ -32,6 +33,13 @@ namespace EasyAbp.EShop.Products.ProductInventories if (productInventory == null) { + var product = await _productRepository.GetAsync(productId); + + if (!product.ProductSkus.Exists(x => x.Id == productSkuId)) + { + throw new EntityNotFoundException(typeof(ProductSku), productSkuId); + } + productInventory = new ProductInventory(GuidGenerator.Create(), CurrentTenant.Id, productId, productSkuId, 0, 0); @@ -45,6 +53,11 @@ namespace EasyAbp.EShop.Products.ProductInventories { var product = await _productRepository.GetAsync(input.ProductId); + if (!product.ProductSkus.Exists(x => x.Id == input.ProductSkuId)) + { + throw new EntityNotFoundException(typeof(ProductSku), input.ProductSkuId); + } + await AuthorizationService.CheckMultiStorePolicyAsync(product.StoreId, ProductsPermissions.ProductInventory.Update, ProductsPermissions.ProductInventory.CrossStore); diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs index 27131c88..55fae06e 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs @@ -71,12 +71,14 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku createDto.ProductDetailId = detail.Id; } - var skuDto = await _productAppService.CreateSkuAsync(ProductId, createDto); + var product = await _productAppService.CreateSkuAsync(ProductId, createDto); + var productSku = product.ProductSkus + .Single(x => !x.AttributeOptionIds.Except(createDto.AttributeOptionIds).Any()); await _productInventoryAppService.UpdateAsync(new UpdateProductInventoryDto { - ProductId = ProductId, - ProductSkuId = skuDto.Id, + ProductId = product.Id, + ProductSkuId = productSku.Id, ChangedInventory = ProductSku.Inventory }); From 1a54a52bcd85866c6130de6a2c9e10ae95989650 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 24 May 2022 01:36:13 +0800 Subject: [PATCH 2/7] Use List instead of ICollection --- .../EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs | 2 +- .../EShop/Products/Products/Dtos/ProductAttributeDto.cs | 2 +- .../EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs | 4 ++-- .../EasyAbp/EShop/Products/Products/ProductAttributeEto.cs | 2 +- .../EasyAbp/EShop/Products/Products/ProductEto.cs | 4 ++-- .../EShop/Products/Products/Product/CreateModal.cshtml.cs | 4 ++-- .../Pages/EShop/Products/Products/Product/EditModal.cshtml.cs | 4 ++-- .../EShop/Products/Products/ProductSku/CreateModal.cshtml.cs | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs index 45df2f80..74f86409 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs @@ -19,7 +19,7 @@ namespace EasyAbp.EShop.Products.Categories.Dtos public Guid? ParentId { get; set; } - public ICollection Children { get; set; } + public List Children { get; set; } public string Description { get; set; } diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductAttributeDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductAttributeDto.cs index c730928f..0e75efce 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductAttributeDto.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductAttributeDto.cs @@ -15,6 +15,6 @@ namespace EasyAbp.EShop.Products.Products.Dtos public int DisplayOrder { get; set; } - public ICollection ProductAttributeOptions { get; set; } + public List ProductAttributeOptions { get; set; } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs index 8182c039..d79f0aaa 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs @@ -40,9 +40,9 @@ namespace EasyAbp.EShop.Products.Products.Dtos public decimal? MaximumPrice { get; set; } - public ICollection ProductAttributes { get; set; } + public List ProductAttributes { get; set; } - public ICollection ProductSkus { get; set; } + public List ProductSkus { get; set; } public ProductSkuDto GetSkuById(Guid skuId) { diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductAttributeEto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductAttributeEto.cs index e7b51cdd..9f8b15b9 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductAttributeEto.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductAttributeEto.cs @@ -15,6 +15,6 @@ namespace EasyAbp.EShop.Products.Products public int DisplayOrder { get; set; } - public ICollection ProductAttributeOptions { get; set; } + public List ProductAttributeOptions { 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/ProductEto.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs index 778794d9..6c72fc38 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs @@ -34,8 +34,8 @@ namespace EasyAbp.EShop.Products.Products public bool IsHidden { get; set; } - public ICollection ProductAttributes { get; set; } + public List ProductAttributes { get; set; } - public ICollection ProductSkus { get; set; } + public List ProductSkus { get; set; } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs index b132a188..ef4b0ce4 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs @@ -21,9 +21,9 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product [BindProperty] public CreateEditProductViewModel Product { get; set; } - public ICollection ProductGroups { get; set; } + public List ProductGroups { get; set; } - public ICollection Categories { get; set; } + public List Categories { get; set; } private readonly ICategoryAppService _categoryAppService; private readonly IProductDetailAppService _productDetailAppService; diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs index 3499a774..1b10b4d9 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs @@ -26,9 +26,9 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product [BindProperty] public CreateEditProductViewModel Product { get; set; } - public ICollection ProductGroups { get; set; } + public List ProductGroups { get; set; } - public ICollection Categories { get; set; } + public List Categories { get; set; } private readonly ICategoryAppService _categoryAppService; private readonly IProductDetailAppService _productDetailAppService; diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs index 27131c88..3cd33fca 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs @@ -26,7 +26,7 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku [BindProperty] public Dictionary SelectedAttributeOptionIdDict { get; set; } - public Dictionary> Attributes { get; set; } + public Dictionary> Attributes { get; set; } private readonly IProductInventoryAppService _productInventoryAppService; private readonly IProductDetailAppService _productDetailAppService; @@ -46,7 +46,7 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku { var product = await _productAppService.GetAsync(ProductId); - Attributes = new Dictionary>(); + Attributes = new Dictionary>(); foreach (var attribute in product.ProductAttributes.ToList()) { From 40f7d5c5365ef5b41496190904e9fe9780845ea0 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 24 May 2022 02:11:18 +0800 Subject: [PATCH 3/7] Returns orderly ProductAttribute list and ProductAttributeOption list --- .../Products/Products/ProductAppService.cs | 27 ++++++++++++++ .../Products/ProductAppServiceTests.cs | 25 ++++++++++++- .../ProductsTestData.cs | 6 +++ .../ProductsTestDataBuilder.cs | 37 ++++++++++++------- 4 files changed, 81 insertions(+), 14 deletions(-) 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 fe7ce521..20a76dca 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 @@ -451,5 +451,32 @@ namespace EasyAbp.EShop.Products.Products } ).ToList())); } + + protected override ProductDto MapToGetOutputDto(Product entity) + { + var productDto = base.MapToGetOutputDto(entity); + + return SortAttributesAndOptions(productDto); + } + + protected override ProductDto MapToGetListOutputDto(Product entity) + { + var productDto = base.MapToGetListOutputDto(entity); + + return SortAttributesAndOptions(productDto); + } + + protected virtual ProductDto SortAttributesAndOptions(ProductDto productDto) + { + productDto.ProductAttributes = productDto.ProductAttributes.OrderByDescending(x => x.DisplayOrder).ToList(); + + foreach (var productAttributeDto in productDto.ProductAttributes) + { + productAttributeDto.ProductAttributeOptions = productAttributeDto.ProductAttributeOptions + .OrderByDescending(x => x.DisplayOrder).ToList(); + } + + return productDto; + } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs index f7c53551..57cc997f 100644 --- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs @@ -199,7 +199,8 @@ namespace EasyAbp.EShop.Products.Products { await _productAppService.CreateSkuAsync(ProductsTestData.Product1Id, new CreateProductSkuDto { - AttributeOptionIds = new List {ProductsTestData.Product1Attribute1Option4Id}, + AttributeOptionIds = new List + { ProductsTestData.Product1Attribute1Option4Id, ProductsTestData.Product1Attribute2Option1Id }, ProductDetailId = wrongProductDetailId, Currency = "CNY", Price = 10m, @@ -208,5 +209,27 @@ namespace EasyAbp.EShop.Products.Products }); })).EntityType.ShouldBe(typeof(ProductDetail)); } + + [Fact] + public async Task Should_Get_Orderly_ProductAttributes_And_ProductAttributeOptions() + { + var productDto = await _productAppService.GetAsync(ProductsTestData.Product1Id); + + productDto.ProductAttributes.Count.ShouldBe(2); + + var size = productDto.ProductAttributes[0]; + var color = productDto.ProductAttributes[1]; + + size.DisplayName.ShouldBe("Size"); + color.DisplayName.ShouldBe("Color"); + + size.ProductAttributeOptions[0].DisplayName.ShouldBe("S"); + size.ProductAttributeOptions[1].DisplayName.ShouldBe("M"); + size.ProductAttributeOptions[2].DisplayName.ShouldBe("L"); + size.ProductAttributeOptions[3].DisplayName.ShouldBe("XL"); + + color.ProductAttributeOptions[0].DisplayName.ShouldBe("Red"); + color.ProductAttributeOptions[1].DisplayName.ShouldBe("Green"); + } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs index 4a0ef23f..78d9c339 100644 --- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs @@ -16,6 +16,8 @@ namespace EasyAbp.EShop.Products public static Guid Product1Attribute1Id { get; } = Guid.NewGuid(); + public static Guid Product1Attribute2Id { get; } = Guid.NewGuid(); + public static Guid Product1Attribute1Option1Id { get; } = Guid.NewGuid(); public static Guid Product1Attribute1Option2Id { get; } = Guid.NewGuid(); @@ -24,6 +26,10 @@ namespace EasyAbp.EShop.Products public static Guid Product1Attribute1Option4Id { get; } = Guid.NewGuid(); + public static Guid Product1Attribute2Option1Id { get; } = Guid.NewGuid(); + + public static Guid Product1Attribute2Option2Id { get; } = Guid.NewGuid(); + public static Guid Product1Sku1Id { get; } = Guid.NewGuid(); public static Guid Product1Sku2Id { get; } = Guid.NewGuid(); diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs index 56dfb166..b26cae67 100644 --- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs @@ -47,31 +47,42 @@ namespace EasyAbp.EShop.Products var product = new Product(ProductsTestData.Product1Id, null, ProductsTestData.Store1Id, "Default", productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, true, false, false, null, null, 0); - var attribute = new ProductAttribute(ProductsTestData.Product1Attribute1Id, "Size", null); + var attribute1 = new ProductAttribute(ProductsTestData.Product1Attribute1Id, "Size", null, 2); + var attribute2 = new ProductAttribute(ProductsTestData.Product1Attribute2Id, "Color", null, 1); - attribute.ProductAttributeOptions.AddRange(new[] + attribute1.ProductAttributeOptions.AddRange(new[] { - new ProductAttributeOption(ProductsTestData.Product1Attribute1Option1Id, "S", null), - new ProductAttributeOption(ProductsTestData.Product1Attribute1Option2Id, "M", null), - new ProductAttributeOption(ProductsTestData.Product1Attribute1Option3Id, "L", null), - new ProductAttributeOption(ProductsTestData.Product1Attribute1Option4Id, "XL", null) + new ProductAttributeOption(ProductsTestData.Product1Attribute1Option4Id, "XL", null, 1), + new ProductAttributeOption(ProductsTestData.Product1Attribute1Option2Id, "M", null, 3), + new ProductAttributeOption(ProductsTestData.Product1Attribute1Option1Id, "S", null, 4), + new ProductAttributeOption(ProductsTestData.Product1Attribute1Option3Id, "L", null, 2), }); - - product.ProductAttributes.Add(attribute); + attribute2.ProductAttributeOptions.AddRange(new[] + { + new ProductAttributeOption(ProductsTestData.Product1Attribute2Option2Id, "Green", null, 1), + new ProductAttributeOption(ProductsTestData.Product1Attribute2Option1Id, "Red", null, 2), + }); + + product.ProductAttributes.Add(attribute2); + product.ProductAttributes.Add(attribute1); + await _productManager.CreateAsync(product); var productSku1 = new ProductSku(ProductsTestData.Product1Sku1Id, await _attributeOptionIdsSerializer.SerializeAsync(new[] - { ProductsTestData.Product1Attribute1Option1Id }), null, "CNY", null, 1m, 1, 10, null, null, null); - + { ProductsTestData.Product1Attribute1Option1Id, ProductsTestData.Product1Attribute2Option1Id }), + null, "CNY", null, 1m, 1, 10, null, null, null); + var productSku2 = new ProductSku(ProductsTestData.Product1Sku2Id, await _attributeOptionIdsSerializer.SerializeAsync(new[] - { ProductsTestData.Product1Attribute1Option2Id }), null, "CNY", null, 2m, 1, 10, null, null, null); - + { ProductsTestData.Product1Attribute1Option2Id, ProductsTestData.Product1Attribute2Option1Id }), + null, "CNY", null, 2m, 1, 10, null, null, null); + var productSku3 = new ProductSku(ProductsTestData.Product1Sku3Id, await _attributeOptionIdsSerializer.SerializeAsync(new[] - { ProductsTestData.Product1Attribute1Option3Id }), null, "CNY", null, 3m, 1, 10, null, null, null); + { ProductsTestData.Product1Attribute1Option3Id, ProductsTestData.Product1Attribute2Option2Id }), + null, "CNY", null, 3m, 1, 10, null, null, null); await _productManager.CreateSkuAsync(product, productSku1); await _productManager.CreateSkuAsync(product, productSku2); From c015cf808ca0d0c27005e6353624c2080b003173 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 24 May 2022 02:47:59 +0800 Subject: [PATCH 4/7] Check attribute options for SKU creation --- .../Products/Localization/Products/cs.json | 1 + .../Products/Localization/Products/en.json | 1 + .../Products/Localization/Products/pl.json | 1 + .../Products/Localization/Products/pt-BR.json | 1 + .../Products/Localization/Products/sl.json | 1 + .../Products/Localization/Products/tr.json | 1 + .../Products/Localization/Products/vi.json | 1 + .../Localization/Products/zh-Hans.json | 1 + .../Localization/Products/zh-Hant.json | 1 + .../EShop/Products/ProductsErrorCodes.cs | 1 + .../EShop/Products/Products/ProductManager.cs | 21 ++++-- ...ctSkuIncorrectAttributeOptionsException.cs | 14 ++++ .../Products/ProductDomainTests.cs | 65 +++++++++++++++++++ 13 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSkuIncorrectAttributeOptionsException.cs diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json index 5472daf3..8dd0d2ff 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json @@ -87,6 +87,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json index 195e1add..7d39ef3b 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json @@ -88,6 +88,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json index 8f38f8a1..67b62e2b 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json @@ -87,6 +87,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json index 4bbf27e2..ba6b48db 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json @@ -87,6 +87,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json index 92f16776..1fee2035 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json @@ -88,6 +88,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json index d8e25e1c..cb9c4994 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json @@ -88,6 +88,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json index 68e43d47..aa785b24 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json @@ -87,6 +87,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "The specified product group ({productGroupName}) 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}", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "Should ensure there are no SKUs using the attribute option which you want to delete.", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json index 6bad87c7..e261b4ed 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json @@ -85,6 +85,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "指定的产品组({productGroupName})不存在", "EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代码{code}重复", "EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重复", + "EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正确", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "您没有权限获取带有隐藏类别的类别列表", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "请确保没有使用您想要删除的属性选项的sku", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "请先确保Sku为空再修改商品的属性", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json index 33ab9343..eabbaa0b 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json @@ -85,6 +85,7 @@ "EasyAbp.EShop.Products:NonexistentProductGroup": "指定的產品組({productGroupName})不存在", "EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代碼{code}重復", "EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重復", + "EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions": "商品{productId}的Sku{serializedAttributeOptionIds}不正確", "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "您沒有權限獲取帶有隱藏類別的類別列表", "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed": "請確保沒有使用您想要刪除的屬性選項的sku", "EasyAbp.EShop.Products:ProductAttributesModificationFailed": "請先確保Sku為空再修改商品的屬性", diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs index 5ce4947f..6b198991 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs @@ -8,6 +8,7 @@ public const string NonexistentProductGroup = "EasyAbp.EShop.Products:NonexistentProductGroup"; public const string ProductSkuCodeDuplicated = "EasyAbp.EShop.Products:ProductSkuCodeDuplicated"; public const string ProductSkuDuplicated = "EasyAbp.EShop.Products:ProductSkuDuplicated"; + public const string ProductSkuIncorrectAttributeOptions = "EasyAbp.EShop.Products:ProductSkuIncorrectAttributeOptions"; public const string NotAllowedToGetCategoryListWithShowHidden = "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden"; public const string ProductAttributeOptionsDeletionFailed = "EasyAbp.EShop.Products:ProductAttributeOptionsDeletionFailed"; public const string ProductAttributesModificationFailed = "EasyAbp.EShop.Products:ProductAttributesModificationFailed"; 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 index 08889dae..abfce434 100644 --- 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 @@ -19,6 +19,7 @@ namespace EasyAbp.EShop.Products.Products private readonly IProductDetailRepository _productDetailRepository; private readonly IProductCategoryRepository _productCategoryRepository; private readonly IProductInventoryProvider _productInventoryProvider; + private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer; private readonly IProductGroupConfigurationProvider _productGroupConfigurationProvider; public ProductManager( @@ -27,6 +28,7 @@ namespace EasyAbp.EShop.Products.Products IProductDetailRepository productDetailRepository, IProductCategoryRepository productCategoryRepository, IProductInventoryProvider productInventoryProvider, + IAttributeOptionIdsSerializer attributeOptionIdsSerializer, IProductGroupConfigurationProvider productGroupConfigurationProvider) { _productRepository = productRepository; @@ -34,6 +36,7 @@ namespace EasyAbp.EShop.Products.Products _productDetailRepository = productDetailRepository; _productCategoryRepository = productCategoryRepository; _productInventoryProvider = productInventoryProvider; + _attributeOptionIdsSerializer = attributeOptionIdsSerializer; _productGroupConfigurationProvider = productGroupConfigurationProvider; } @@ -131,15 +134,25 @@ namespace EasyAbp.EShop.Products.Products return Task.CompletedTask; } - protected virtual Task CheckSkuAttributeOptionsAsync(Product product, ProductSku productSku) + protected virtual async Task CheckSkuAttributeOptionsAsync(Product product, ProductSku productSku) { + var attributeOptionIds = + (await _attributeOptionIdsSerializer.DeserializeAsync(productSku.SerializedAttributeOptionIds)) + .ToList(); + + if (!product.ProductAttributes.TrueForAll(attribute => + attribute.ProductAttributeOptions.Select(option => option.Id).Intersect(attributeOptionIds) + .Count() == 1)) + { + throw new ProductSkuIncorrectAttributeOptionsException(product.Id, + productSku.SerializedAttributeOptionIds); + } + if (product.ProductSkus.Where(sku => sku.Id != productSku.Id).FirstOrDefault(sku => - sku.SerializedAttributeOptionIds.Equals(productSku.SerializedAttributeOptionIds)) != null) + sku.SerializedAttributeOptionIds.Equals(productSku.SerializedAttributeOptionIds)) != null) { throw new ProductSkuDuplicatedException(product.Id, productSku.SerializedAttributeOptionIds); } - - return Task.CompletedTask; } [UnitOfWork] diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSkuIncorrectAttributeOptionsException.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSkuIncorrectAttributeOptionsException.cs new file mode 100644 index 00000000..7e5e8981 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSkuIncorrectAttributeOptionsException.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp; + +namespace EasyAbp.EShop.Products.Products +{ + public class ProductSkuIncorrectAttributeOptionsException : BusinessException + { + public ProductSkuIncorrectAttributeOptionsException(Guid productId, string serializedAttributeOptionIds) : base(ProductsErrorCodes.ProductSkuIncorrectAttributeOptions) + { + WithData(nameof(productId), productId); + WithData(nameof(serializedAttributeOptionIds), serializedAttributeOptionIds); + } + } +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs index 60355b76..a88505c1 100644 --- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; +using Volo.Abp.Uow; using Xunit; namespace EasyAbp.EShop.Products.Products @@ -11,11 +13,13 @@ namespace EasyAbp.EShop.Products.Products { private IProductRepository ProductRepository { get; } private IProductManager ProductManager { get; } + private IAttributeOptionIdsSerializer AttributeOptionIdsSerializer { get; } public ProductDomainTests() { ProductRepository = ServiceProvider.GetRequiredService(); ProductManager = ServiceProvider.GetRequiredService(); + AttributeOptionIdsSerializer = ServiceProvider.GetRequiredService(); } [Fact] @@ -121,5 +125,66 @@ namespace EasyAbp.EShop.Products.Products sku1.ProductDetailId.ShouldBeNull(); }); } + + [Fact] + [UnitOfWork] + public virtual async Task Should_Create_Sku() + { + var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id); + + var attributeOptionIds = new[] + { + ProductsTestData.Product1Attribute1Option4Id, + ProductsTestData.Product1Attribute2Option2Id + }; + + await Should.NotThrowAsync(async () => + { + await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(attributeOptionIds)); + }); + + var serializedAttributeOptionIds = await AttributeOptionIdsSerializer.SerializeAsync(attributeOptionIds); + + product1.ProductSkus.Count(x => x.SerializedAttributeOptionIds == serializedAttributeOptionIds).ShouldBe(1); + } + + [Fact] + [UnitOfWork] + public virtual async Task Should_Throw_If_Create_Sku_With_Incorrect_AttributeOptionIds() + { + var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id); + + await Should.ThrowAsync(async () => + { + await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[] + { + ProductsTestData.Product1Attribute1Option1Id // need 2 options but input 1 + })); + }); + + await Should.ThrowAsync(async () => + { + await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[] + { + ProductsTestData.Product1Attribute1Option1Id, + Guid.NewGuid() // a nonexistent option + })); + }); + + await Should.ThrowAsync(async () => + { + await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[] + { + ProductsTestData.Product1Attribute1Option1Id, + ProductsTestData.Product1Attribute1Option2Id // 2 options from attribute1 + })); + }); + } + + private async Task CreateTestSkuAsync(IEnumerable attributeOptionIds) + { + return new ProductSku(Guid.NewGuid(), await AttributeOptionIdsSerializer.SerializeAsync(attributeOptionIds), + "test-sku", "CNY", null, 0m, 1, 10, null, null, null); + } } } From 916ca7446d4dfe394b416186620da6213499260c Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 24 May 2022 12:15:44 +0800 Subject: [PATCH 5/7] Remove UOW attributes in ProductDomainTests --- .../Products/ProductDomainTests.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs index a88505c1..6bc7dca4 100644 --- a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; -using Volo.Abp.Uow; using Xunit; namespace EasyAbp.EShop.Products.Products @@ -127,8 +126,7 @@ namespace EasyAbp.EShop.Products.Products } [Fact] - [UnitOfWork] - public virtual async Task Should_Create_Sku() + public async Task Should_Create_Sku() { var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id); @@ -149,8 +147,7 @@ namespace EasyAbp.EShop.Products.Products } [Fact] - [UnitOfWork] - public virtual async Task Should_Throw_If_Create_Sku_With_Incorrect_AttributeOptionIds() + public async Task Should_Throw_If_Create_Sku_With_Incorrect_AttributeOptionIds() { var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id); From d7af7b757d2642287e081292ee34e6994f263ac6 Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Tue, 24 May 2022 12:19:41 +0800 Subject: [PATCH 6/7] Change version to 2.9.2 --- common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.props b/common.props index bcb0536b..7780dae9 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 2.9.1 + 2.9.2 $(NoWarn);CS1591 true EasyAbp Team From bc79f52f792c179063ff370111a189557235a11b Mon Sep 17 00:00:00 2001 From: gdlcf88 Date: Sun, 5 Jun 2022 00:53:01 +0800 Subject: [PATCH 7/7] Introduce IOrderLinePriceOverrider --- .../Orders/Orders/IOrderLinePriceOverrider.cs | 11 ++++++ .../EShop/Orders/Orders/NewOrderGenerator.cs | 28 ++++++++++++-- .../Orders/OrderAppServiceTests.cs | 38 ++++++++++++++++++- .../Orders/TestOrderLinePriceOverrider.cs | 22 +++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs create mode 100644 modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs new file mode 100644 index 00000000..c86f5463 --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using EasyAbp.EShop.Orders.Orders.Dtos; +using EasyAbp.EShop.Products.Products.Dtos; + +namespace EasyAbp.EShop.Orders.Orders; + +public interface IOrderLinePriceOverrider +{ + Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product, + ProductSkuDto productSku); +} \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs index fe4c5926..b6a5eaae 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs @@ -23,6 +23,7 @@ namespace EasyAbp.EShop.Orders.Orders private readonly IServiceProvider _serviceProvider; private readonly IOrderNumberGenerator _orderNumberGenerator; private readonly IProductSkuDescriptionProvider _productSkuDescriptionProvider; + private readonly IEnumerable _orderLinePriceOverriders; public NewOrderGenerator( IClock clock, @@ -30,7 +31,8 @@ namespace EasyAbp.EShop.Orders.Orders ICurrentTenant currentTenant, IServiceProvider serviceProvider, IOrderNumberGenerator orderNumberGenerator, - IProductSkuDescriptionProvider productSkuDescriptionProvider) + IProductSkuDescriptionProvider productSkuDescriptionProvider, + IEnumerable orderLinePriceOverriders) { _clock = clock; _guidGenerator = guidGenerator; @@ -38,6 +40,7 @@ namespace EasyAbp.EShop.Orders.Orders _serviceProvider = serviceProvider; _orderNumberGenerator = orderNumberGenerator; _productSkuDescriptionProvider = productSkuDescriptionProvider; + _orderLinePriceOverriders = orderLinePriceOverriders; } public virtual async Task GenerateAsync(Guid customerUserId, CreateOrderDto input, @@ -119,8 +122,10 @@ namespace EasyAbp.EShop.Orders.Orders { throw new OrderLineInvalidQuantityException(product.Id, productSku.Id, inputOrderLine.Quantity); } + + var unitPrice = await GetUnitPriceAsync(input, inputOrderLine, product, productSku); - var totalPrice = productSku.Price * inputOrderLine.Quantity; + var totalPrice = unitPrice * inputOrderLine.Quantity; var orderLine = new OrderLine( id: _guidGenerator.Create(), @@ -137,7 +142,7 @@ namespace EasyAbp.EShop.Orders.Orders skuDescription: await _productSkuDescriptionProvider.GenerateAsync(product, productSku), mediaResources: product.MediaResources, currency: productSku.Currency, - unitPrice: productSku.Price, + unitPrice: unitPrice, totalPrice: totalPrice, totalDiscount: 0, actualTotalPrice: totalPrice, @@ -149,6 +154,23 @@ namespace EasyAbp.EShop.Orders.Orders return orderLine; } + protected virtual async Task GetUnitPriceAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, + ProductDto product, ProductSkuDto productSku) + { + foreach (var overrider in _orderLinePriceOverriders) + { + var overridenUnitPrice = + await overrider.GetUnitPriceOrNullAsync(input, inputOrderLine, product, productSku); + + if (overridenUnitPrice is not null) + { + return overridenUnitPrice.Value; + } + } + + return productSku.Price; + } + protected virtual Task GetStoreCurrencyAsync(Guid storeId) { // Todo: Get real store currency configuration. diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs index 63670c45..885afd26 100644 --- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs @@ -292,7 +292,7 @@ namespace EasyAbp.EShop.Orders.Orders }); } - [Fact] + [Fact] public async Task Paid_Order_Should_Not_Be_Auto_Canceled_When_Payment_Overtime() { // Arrange @@ -462,5 +462,41 @@ namespace EasyAbp.EShop.Orders.Orders order.CancellationReason.ShouldBe(OrdersConsts.CancellationReason); }); } + + [Fact] + public async Task Should_Override_Unit_Price() + { + var createOrderDto = new CreateOrderDto + { + CustomerRemark = "customer remark", + StoreId = OrderTestData.Store1Id, + OrderLines = new List + { + new() + { + ProductId = OrderTestData.Product1Id, + ProductSkuId = OrderTestData.ProductSku1Id, + Quantity = 10 + }, + new() + { + ProductId = OrderTestData.Product1Id, + ProductSkuId = OrderTestData.ProductSku2Id, + Quantity = 2 + } + } + }; + + await WithUnitOfWorkAsync(async () => + { + var order = await _orderAppService.CreateAsync(createOrderDto); + var orderLine = order.OrderLines.Find(x => x.ProductSkuId == OrderTestData.ProductSku2Id); + + order.ProductTotalPrice.ShouldBe(10 * 1m + 2 * TestOrderLinePriceOverrider.Sku2UnitPrice); + orderLine.ShouldNotBeNull(); + orderLine.UnitPrice.ShouldBe(TestOrderLinePriceOverrider.Sku2UnitPrice); + orderLine.TotalPrice.ShouldBe(orderLine.Quantity * orderLine.UnitPrice); + }); + } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs new file mode 100644 index 00000000..22d4c016 --- /dev/null +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using EasyAbp.EShop.Orders.Orders.Dtos; +using EasyAbp.EShop.Products.Products.Dtos; +using Volo.Abp.DependencyInjection; + +namespace EasyAbp.EShop.Orders.Orders; + +public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientDependency +{ + public static decimal Sku2UnitPrice { get; set; } = 100m; + + public async Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, + ProductDto product, ProductSkuDto productSku) + { + if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku2Id) + { + return Sku2UnitPrice; + } + + return null; + } +} \ No newline at end of file