Browse Source

Merge branch 'dev' into booking-plugin

pull/157/head
gdlcf88 4 years ago
parent
commit
c82d23373a
  1. 2
      common.props
  2. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs
  3. 28
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs
  4. 38
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
  5. 22
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs
  6. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Categories/Dtos/CategoryDto.cs
  7. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductAttributeDto.cs
  8. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
  9. 13
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductInventories/ProductInventoryAppService.cs
  10. 27
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
  11. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
  12. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
  13. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
  14. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
  15. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
  16. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
  17. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
  18. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
  19. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
  20. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductAttributeEto.cs
  21. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
  22. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
  23. 21
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
  24. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSkuIncorrectAttributeOptionsException.cs
  25. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs
  26. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs
  27. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs
  28. 25
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs
  29. 62
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
  30. 6
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs
  31. 37
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs

2
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>2.9.1</Version>
<Version>2.9.2</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>EasyAbp Team</Authors>

11
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<decimal?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product,
ProductSkuDto productSku);
}

28
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<IOrderLinePriceOverrider> _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<IOrderLinePriceOverrider> 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<Order> 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<decimal> 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<string> GetStoreCurrencyAsync(Guid storeId)
{
// Todo: Get real store currency configuration.

38
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<CreateOrderLineDto>
{
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);
});
}
}
}

22
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<decimal?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine,
ProductDto product, ProductSkuDto productSku)
{
if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku2Id)
{
return Sku2UnitPrice;
}
return null;
}
}

2
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<CategoryDto> Children { get; set; }
public List<CategoryDto> Children { get; set; }
public string Description { get; set; }

2
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<ProductAttributeOptionDto> ProductAttributeOptions { get; set; }
public List<ProductAttributeOptionDto> ProductAttributeOptions { get; set; }
}
}

4
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<ProductAttributeDto> ProductAttributes { get; set; }
public List<ProductAttributeDto> ProductAttributes { get; set; }
public ICollection<ProductSkuDto> ProductSkus { get; set; }
public List<ProductSkuDto> ProductSkus { get; set; }
public ProductSkuDto GetSkuById(Guid skuId)
{

13
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);

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

@ -444,5 +444,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;
}
}
}

1
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.",

1
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.",

1
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.",

1
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.",

1
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.",

1
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.",

1
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.",

1
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为空再修改商品的属性",

1
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為空再修改商品的屬性",

2
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<ProductAttributeOptionEto> ProductAttributeOptions { get; set; }
public List<ProductAttributeOptionEto> ProductAttributeOptions { get; set; }
}
}

4
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<ProductAttributeEto> ProductAttributes { get; set; }
public List<ProductAttributeEto> ProductAttributes { get; set; }
public ICollection<ProductSkuEto> ProductSkus { get; set; }
public List<ProductSkuEto> ProductSkus { get; set; }
}
}

1
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";

21
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]

14
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);
}
}
}

4
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<SelectListItem> ProductGroups { get; set; }
public List<SelectListItem> ProductGroups { get; set; }
public ICollection<SelectListItem> Categories { get; set; }
public List<SelectListItem> Categories { get; set; }
private readonly ICategoryAppService _categoryAppService;
private readonly IProductDetailAppService _productDetailAppService;

4
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<SelectListItem> ProductGroups { get; set; }
public List<SelectListItem> ProductGroups { get; set; }
public ICollection<SelectListItem> Categories { get; set; }
public List<SelectListItem> Categories { get; set; }
private readonly ICategoryAppService _categoryAppService;
private readonly IProductDetailAppService _productDetailAppService;

12
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<string, Guid> SelectedAttributeOptionIdDict { get; set; }
public Dictionary<string, ICollection<SelectListItem>> Attributes { get; set; }
public Dictionary<string, List<SelectListItem>> 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<string, ICollection<SelectListItem>>();
Attributes = new Dictionary<string, List<SelectListItem>>();
foreach (var attribute in product.ProductAttributes.ToList())
{
@ -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
});

25
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<Guid> {ProductsTestData.Product1Attribute1Option4Id},
AttributeOptionIds = new List<Guid>
{ 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");
}
}
}

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -11,11 +12,13 @@ namespace EasyAbp.EShop.Products.Products
{
private IProductRepository ProductRepository { get; }
private IProductManager ProductManager { get; }
private IAttributeOptionIdsSerializer AttributeOptionIdsSerializer { get; }
public ProductDomainTests()
{
ProductRepository = ServiceProvider.GetRequiredService<IProductRepository>();
ProductManager = ServiceProvider.GetRequiredService<IProductManager>();
AttributeOptionIdsSerializer = ServiceProvider.GetRequiredService<IAttributeOptionIdsSerializer>();
}
[Fact]
@ -121,5 +124,64 @@ namespace EasyAbp.EShop.Products.Products
sku1.ProductDetailId.ShouldBeNull();
});
}
[Fact]
public 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]
public async Task Should_Throw_If_Create_Sku_With_Incorrect_AttributeOptionIds()
{
var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
await Should.ThrowAsync<ProductSkuIncorrectAttributeOptionsException>(async () =>
{
await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[]
{
ProductsTestData.Product1Attribute1Option1Id // need 2 options but input 1
}));
});
await Should.ThrowAsync<ProductSkuIncorrectAttributeOptionsException>(async () =>
{
await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[]
{
ProductsTestData.Product1Attribute1Option1Id,
Guid.NewGuid() // a nonexistent option
}));
});
await Should.ThrowAsync<ProductSkuIncorrectAttributeOptionsException>(async () =>
{
await ProductManager.CreateSkuAsync(product1, await CreateTestSkuAsync(new[]
{
ProductsTestData.Product1Attribute1Option1Id,
ProductsTestData.Product1Attribute1Option2Id // 2 options from attribute1
}));
});
}
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);
}
}
}

6
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();

37
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);

Loading…
Cancel
Save