Browse Source

Merge pull request #143 from EasyAbp/redesign-product-detail

Make ProductDetail non-essential and reusable
pull/148/head
Super 5 years ago
committed by GitHub
parent
commit
bc938575ec
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderLineDto.cs
  2. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/INewOrderGenerator.cs
  3. 14
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs
  4. 33
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
  5. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrderLine.cs
  6. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderLineEto.cs
  7. 8
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs
  8. 140
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
  9. 8
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs
  10. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/ProductDetails/Dtos/ProductDetailDto.cs
  11. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/CreateUpdateProductDto.cs
  12. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
  13. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
  14. 15
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductDetails/ProductDetailAppService.cs
  15. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/cs.json
  16. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/en.json
  17. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pl.json
  18. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/pt-BR.json
  19. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/sl.json
  20. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/tr.json
  21. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/vi.json
  22. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hans.json
  23. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
  24. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProduct.cs
  25. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
  26. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
  27. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
  28. 13
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDetailHasBeenUsedException.cs
  29. 39
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
  30. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs
  31. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
  32. 15
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs
  33. 49
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs
  34. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductDetailViewModel.cs
  35. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/ViewModels/CreateEditProductViewModel.cs
  36. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs
  37. 62
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/EditModal.cshtml.cs
  38. 23
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/CreateEditSkuProductDetailViewModel.cs
  39. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/EditProductSkuViewModel.cs
  40. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/ProductsWebAutoMapperProfile.cs
  41. 74
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs
  42. 110
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/ProductDomainTests.cs
  43. 4
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs
  44. 3
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs
  45. 5131
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411111127_MadeProductDetailIdNullable.Designer.cs
  46. 70
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411111127_MadeProductDetailIdNullable.cs
  47. 5134
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411204916_AddedProductDetailIdToOrderLine.Designer.cs
  48. 26
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411204916_AddedProductDetailIdToOrderLine.cs
  49. 11
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderLineDto.cs

@ -13,9 +13,11 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
public Guid ProductSkuId { get; set; }
public Guid? ProductDetailId { get; set; }
public DateTime ProductModificationTime { get; set; }
public DateTime ProductDetailModificationTime { get; set; }
public DateTime? ProductDetailModificationTime { get; set; }
public string ProductGroupName { get; set; }

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/INewOrderGenerator.cs

@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Orders.Orders
{
public interface INewOrderGenerator
{
Task<Order> GenerateAsync(Guid customerUserId, CreateOrderDto input, Dictionary<Guid, ProductDto> productDict);
Task<Order> GenerateAsync(Guid customerUserId, CreateOrderDto input, Dictionary<Guid, ProductDto> productDict,
Dictionary<Guid, ProductDetailDto> productDetailDict);
}
}

14
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.DependencyInjection;
@ -40,13 +41,13 @@ namespace EasyAbp.EShop.Orders.Orders
}
public virtual async Task<Order> GenerateAsync(Guid customerUserId, CreateOrderDto input,
Dictionary<Guid, ProductDto> productDict)
Dictionary<Guid, ProductDto> productDict, Dictionary<Guid, ProductDetailDto> productDetailDict)
{
var orderLines = new List<OrderLine>();
foreach (var inputOrderLine in input.OrderLines)
{
orderLines.Add(await GenerateOrderLineAsync(input, inputOrderLine, productDict));
orderLines.Add(await GenerateOrderLineAsync(input, inputOrderLine, productDict, productDetailDict));
}
var storeCurrency = await GetStoreCurrencyAsync(input.StoreId);
@ -105,11 +106,15 @@ namespace EasyAbp.EShop.Orders.Orders
}
protected virtual async Task<OrderLine> GenerateOrderLineAsync(CreateOrderDto input,
CreateOrderLineDto inputOrderLine, Dictionary<Guid, ProductDto> productDict)
CreateOrderLineDto inputOrderLine, Dictionary<Guid, ProductDto> productDict,
Dictionary<Guid, ProductDetailDto> productDetailDict)
{
var product = productDict[inputOrderLine.ProductId];
var productSku = product.GetSkuById(inputOrderLine.ProductSkuId);
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId;
var productDetail = productDetailId.HasValue ? productDetailDict[productDetailId.Value] : null;
if (!inputOrderLine.Quantity.IsBetween(productSku.OrderMinQuantity, productSku.OrderMaxQuantity))
{
throw new OrderLineInvalidQuantityException(product.Id, productSku.Id, inputOrderLine.Quantity);
@ -121,8 +126,9 @@ namespace EasyAbp.EShop.Orders.Orders
id: _guidGenerator.Create(),
productId: product.Id,
productSkuId: productSku.Id,
productDetailId: productDetailId,
productModificationTime: product.LastModificationTime ?? product.CreationTime,
productDetailModificationTime: productSku.LastModificationTime ?? productSku.CreationTime,
productDetailModificationTime: productDetail?.LastModificationTime ?? productDetail?.CreationTime,
productGroupName: product.ProductGroupName,
productGroupDisplayName: product.ProductGroupDisplayName,
productUniqueName: product.UniqueName,

33
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs

@ -4,6 +4,8 @@ using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Authorization;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Stores.Stores;
@ -24,17 +26,20 @@ namespace EasyAbp.EShop.Orders.Orders
private readonly INewOrderGenerator _newOrderGenerator;
private readonly IProductAppService _productAppService;
private readonly IProductDetailAppService _productDetailAppService;
private readonly IOrderManager _orderManager;
private readonly IOrderRepository _repository;
public OrderAppService(
INewOrderGenerator newOrderGenerator,
IProductAppService productAppService,
IProductDetailAppService productDetailAppService,
IOrderManager orderManager,
IOrderRepository repository) : base(repository)
{
_newOrderGenerator = newOrderGenerator;
_productAppService = productAppService;
_productDetailAppService = productDetailAppService;
_orderManager = orderManager;
_repository = repository;
}
@ -94,7 +99,18 @@ namespace EasyAbp.EShop.Orders.Orders
new OrderOperationAuthorizationRequirement(OrderOperation.Creation)
);
var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict);
var productDetailIds = input.OrderLines
.Select(dto =>
productDict[dto.ProductId].GetSkuById(dto.ProductSkuId).ProductDetailId ??
productDict[dto.ProductId].ProductDetailId)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
var productDetailDict = await GetProductDetailDictionaryAsync(productDetailIds);
// Todo: Can we use IProductDataScopedCache/IProductDetailDataScopedCache instead of productDict/productDetailDict?
var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict, productDetailDict);
await DiscountOrderAsync(order, productDict);
@ -116,7 +132,7 @@ namespace EasyAbp.EShop.Orders.Orders
{
var dict = new Dictionary<Guid, ProductDto>();
foreach (var productId in productIds.Distinct().ToList())
foreach (var productId in productIds.Distinct())
{
dict.Add(productId, await _productAppService.GetAsync(productId));
}
@ -124,6 +140,19 @@ namespace EasyAbp.EShop.Orders.Orders
return dict;
}
protected virtual async Task<Dictionary<Guid, ProductDetailDto>> GetProductDetailDictionaryAsync(
IEnumerable<Guid> productDetailIds)
{
var dict = new Dictionary<Guid, ProductDetailDto>();
foreach (var productDetailId in productDetailIds.Distinct())
{
dict.Add(productDetailId, await _productDetailAppService.GetAsync(productDetailId));
}
return dict;
}
[RemoteService(false)]
public override Task<OrderDto> UpdateAsync(Guid id, CreateOrderDto input)
{

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrderLine.cs

@ -9,9 +9,11 @@ namespace EasyAbp.EShop.Orders.Orders
Guid ProductSkuId { get; }
Guid? ProductDetailId { get; }
DateTime ProductModificationTime { get; }
DateTime ProductDetailModificationTime { get; }
DateTime? ProductDetailModificationTime { get; }
string ProductGroupName { get; }

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderLineEto.cs

@ -12,9 +12,11 @@ namespace EasyAbp.EShop.Orders.Orders
public Guid ProductSkuId { get; set; }
public Guid? ProductDetailId { get; set; }
public DateTime ProductModificationTime { get; set; }
public DateTime ProductDetailModificationTime { get; set; }
public DateTime? ProductDetailModificationTime { get; set; }
public string ProductGroupName { get; set; }

8
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLine.cs

@ -13,9 +13,11 @@ namespace EasyAbp.EShop.Orders.Orders
public virtual Guid ProductSkuId { get; protected set; }
public virtual Guid? ProductDetailId { get; protected set; }
public virtual DateTime ProductModificationTime { get; protected set; }
public virtual DateTime ProductDetailModificationTime { get; protected set; }
public virtual DateTime? ProductDetailModificationTime { get; protected set; }
[NotNull]
public virtual string ProductGroupName { get; protected set; }
@ -68,8 +70,9 @@ namespace EasyAbp.EShop.Orders.Orders
Guid id,
Guid productId,
Guid productSkuId,
Guid? productDetailId,
DateTime productModificationTime,
DateTime productDetailModificationTime,
DateTime? productDetailModificationTime,
[NotNull] string productGroupName,
[NotNull] string productGroupDisplayName,
[CanBeNull] string productUniqueName,
@ -86,6 +89,7 @@ namespace EasyAbp.EShop.Orders.Orders
{
ProductId = productId;
ProductSkuId = productSkuId;
ProductDetailId = productDetailId;
ProductModificationTime = productModificationTime;
ProductDetailModificationTime = productDetailModificationTime;
ProductGroupName = productGroupName;

140
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.EntityFrameworkCore;
@ -33,10 +35,12 @@ namespace EasyAbp.EShop.Orders.Orders
CreationTime = DateTime.Now,
IsPublished = true,
Id = OrderTestData.Product1Id,
StoreId = OrderTestData.Store1Id,
ProductGroupName = "Default",
ProductGroupDisplayName = "Default",
UniqueName = "Pencil",
DisplayName = "Hello pencil",
ProductDetailId = OrderTestData.ProductDetail1Id,
ProductSkus = new List<ProductSkuDto>
{
new ProductSkuDto
@ -48,6 +52,18 @@ namespace EasyAbp.EShop.Orders.Orders
AttributeOptionIds = new List<Guid>(),
Price = 1m,
Currency = "CNY",
ProductDetailId = null
},
new ProductSkuDto
{
Id = OrderTestData.ProductSku2Id,
Name = "My SKU 2",
OrderMinQuantity = 0,
OrderMaxQuantity = 100,
AttributeOptionIds = new List<Guid>(),
Price = 2m,
Currency = "CNY",
ProductDetailId = OrderTestData.ProductDetail2Id
}
},
InventoryStrategy = InventoryStrategy.NoNeed,
@ -55,6 +71,28 @@ namespace EasyAbp.EShop.Orders.Orders
}));
services.AddTransient(_ => productAppService);
var productDetailAppService = Substitute.For<IProductDetailAppService>();
productDetailAppService.GetAsync(OrderTestData.ProductDetail1Id).Returns(Task.FromResult(
new ProductDetailDto
{
Id = OrderTestData.ProductDetail1Id,
CreationTime = OrderTestData.ProductDetailLastModificationTime,
LastModificationTime = OrderTestData.ProductDetailLastModificationTime,
StoreId = OrderTestData.Store1Id,
Description = "My Details 1"
}));
productDetailAppService.GetAsync(OrderTestData.ProductDetail2Id).Returns(Task.FromResult(
new ProductDetailDto
{
Id = OrderTestData.ProductDetail2Id,
StoreId = OrderTestData.Store1Id,
Description = "My Details 2"
}));
services.AddTransient(_ => productDetailAppService);
}
[Fact]
@ -72,55 +110,26 @@ namespace EasyAbp.EShop.Orders.Orders
ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id,
Quantity = 10
},
new CreateOrderLineDto
{
ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku2Id,
Quantity = 1
}
}
};
OrderDto createResponse = null;
// Act
var createResponse = await _orderAppService.CreateAsync(createOrderDto);
await WithUnitOfWorkAsync(async () =>
{
createResponse = await _orderAppService.CreateAsync(createOrderDto);
});
var response = await _orderAppService.GetAsync(createResponse.Id);
// Assert
response.ShouldNotBeNull();
response.Currency.ShouldBe("CNY");
response.CanceledTime.ShouldBeNull();
response.CancellationReason.ShouldBeNullOrEmpty();
response.CompletionTime.ShouldBeNull();
response.CustomerRemark.ShouldBe("customer remark");
response.OrderNumber.ShouldNotBeNull();
response.OrderStatus.ShouldBe(OrderStatus.Pending);
response.PaidTime.ShouldBeNull();
response.PaymentId.ShouldBeNull();
response.RefundAmount.ShouldBe(0m);
response.StaffRemark.ShouldBeNullOrEmpty();
response.StoreId.ShouldBe(OrderTestData.Store1Id);
response.TotalDiscount.ShouldBe(0m);
response.TotalPrice.ShouldBe(10m);
response.ActualTotalPrice.ShouldBe(10m);
response.CustomerUserId.ShouldBe(Guid.Parse("2e701e62-0953-4dd3-910b-dc6cc93ccb0d"));
response.ProductTotalPrice.ShouldBe(10m);
response.ReducedInventoryAfterPaymentTime.ShouldBeNull();
response.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
response.OrderLines.Count.ShouldBe(1);
var responseOrderLine = response.OrderLines.First();
responseOrderLine.ProductId.ShouldBe(OrderTestData.Product1Id);
responseOrderLine.ProductSkuId.ShouldBe(OrderTestData.ProductSku1Id);
responseOrderLine.ProductDisplayName.ShouldBe("Hello pencil");
responseOrderLine.ProductUniqueName.ShouldBe("Pencil");
responseOrderLine.ProductGroupName.ShouldBe("Default");
responseOrderLine.ProductGroupDisplayName.ShouldBe("Default");
responseOrderLine.SkuName.ShouldBe("My SKU");
responseOrderLine.UnitPrice.ShouldBe(1m);
responseOrderLine.TotalPrice.ShouldBe(10m);
responseOrderLine.TotalDiscount.ShouldBe(0m);
responseOrderLine.ActualTotalPrice.ShouldBe(10m);
responseOrderLine.Currency.ShouldBe("CNY");
responseOrderLine.Quantity.ShouldBe(10);
responseOrderLine.ProductModificationTime.ShouldBe(OrderTestData.ProductLastModificationTime);
responseOrderLine.RefundAmount.ShouldBe(0m);
responseOrderLine.RefundedQuantity.ShouldBe(0);
UsingDbContext(context =>
{
context.Orders.Count().ShouldBe(1);
@ -139,31 +148,36 @@ namespace EasyAbp.EShop.Orders.Orders
order.StaffRemark.ShouldBeNullOrEmpty();
order.StoreId.ShouldBe(OrderTestData.Store1Id);
order.TotalDiscount.ShouldBe(0m);
order.TotalPrice.ShouldBe(10m);
order.ActualTotalPrice.ShouldBe(10m);
order.TotalPrice.ShouldBe(12m);
order.ActualTotalPrice.ShouldBe(12m);
order.CustomerUserId.ShouldBe(Guid.Parse("2e701e62-0953-4dd3-910b-dc6cc93ccb0d"));
order.ProductTotalPrice.ShouldBe(10m);
order.ProductTotalPrice.ShouldBe(12m);
order.ReducedInventoryAfterPaymentTime.ShouldBeNull();
order.ReducedInventoryAfterPlacingTime.ShouldNotBeNull();
order.OrderLines.Count.ShouldBe(1);
var orderLine = order.OrderLines.First();
orderLine.ProductId.ShouldBe(OrderTestData.Product1Id);
orderLine.ProductSkuId.ShouldBe(OrderTestData.ProductSku1Id);
orderLine.ProductDisplayName.ShouldBe("Hello pencil");
orderLine.ProductUniqueName.ShouldBe("Pencil");
orderLine.ProductGroupName.ShouldBe("Default");
orderLine.ProductGroupDisplayName.ShouldBe("Default");
orderLine.SkuName.ShouldBe("My SKU");
orderLine.UnitPrice.ShouldBe(1m);
orderLine.TotalPrice.ShouldBe(10m);
orderLine.TotalDiscount.ShouldBe(0m);
orderLine.ActualTotalPrice.ShouldBe(10m);
orderLine.Currency.ShouldBe("CNY");
orderLine.Quantity.ShouldBe(10);
orderLine.ProductModificationTime.ShouldBe(OrderTestData.ProductLastModificationTime);
orderLine.RefundAmount.ShouldBe(0m);
orderLine.RefundedQuantity.ShouldBe(0);
order.OrderLines.Count.ShouldBe(2);
var orderLine1 = response.OrderLines.Single(x => x.ProductSkuId == OrderTestData.ProductSku1Id);
orderLine1.ProductId.ShouldBe(OrderTestData.Product1Id);
orderLine1.ProductSkuId.ShouldBe(OrderTestData.ProductSku1Id);
orderLine1.ProductDetailId.ShouldBe(OrderTestData.ProductDetail1Id);
orderLine1.ProductDisplayName.ShouldBe("Hello pencil");
orderLine1.ProductUniqueName.ShouldBe("Pencil");
orderLine1.ProductGroupName.ShouldBe("Default");
orderLine1.ProductGroupDisplayName.ShouldBe("Default");
orderLine1.SkuName.ShouldBe("My SKU");
orderLine1.UnitPrice.ShouldBe(1m);
orderLine1.TotalPrice.ShouldBe(10m);
orderLine1.TotalDiscount.ShouldBe(0m);
orderLine1.ActualTotalPrice.ShouldBe(10m);
orderLine1.Currency.ShouldBe("CNY");
orderLine1.Quantity.ShouldBe(10);
orderLine1.ProductModificationTime.ShouldBe(OrderTestData.ProductLastModificationTime);
orderLine1.ProductDetailModificationTime.ShouldBe(OrderTestData.ProductDetailLastModificationTime);
orderLine1.RefundAmount.ShouldBe(0m);
orderLine1.RefundedQuantity.ShouldBe(0);
var orderLine2 = response.OrderLines.Single(x => x.ProductSkuId == OrderTestData.ProductSku2Id);
orderLine2.ProductDetailId.ShouldBe(OrderTestData.ProductDetail2Id);
});
}

8
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/OrderTestData.cs

@ -11,6 +11,14 @@ namespace EasyAbp.EShop.Orders
public static Guid ProductSku1Id { get; } = Guid.NewGuid();
public static Guid ProductSku2Id { get; } = Guid.NewGuid();
public static Guid ProductDetail1Id { get; } = Guid.NewGuid();
public static Guid ProductDetail2Id { get; } = Guid.NewGuid();
public static DateTime ProductLastModificationTime { get; } = DateTime.Today;
public static DateTime ProductDetailLastModificationTime { get; } = DateTime.Today;
}
}

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

@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Products.ProductDetails.Dtos
{
[Serializable]
public class ProductDetailDto : ExtensibleEntityDto<Guid>
public class ProductDetailDto : ExtensibleFullAuditedEntityDto<Guid>
{
public Guid? StoreId { get; set; }

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

@ -18,7 +18,7 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public string ProductGroupName { get; set; }
[DisplayName("ProductDetailId")]
public Guid ProductDetailId { get; set; }
public Guid? ProductDetailId { get; set; }
[DisplayName("ProductCategory")]
public ICollection<Guid> CategoryIds { get; set; }

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

@ -14,7 +14,7 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public string ProductGroupDisplayName { get; set; }
public Guid ProductDetailId { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }

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

@ -11,7 +11,7 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public string ProductGroupName { get; set; }
public Guid ProductDetailId { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }

15
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductDetails/ProductDetailAppService.cs

@ -19,14 +19,11 @@ namespace EasyAbp.EShop.Products.ProductDetails
protected override string GetPolicyName { get; set; } = null;
protected override string GetListPolicyName { get; set; } = ProductsPermissions.Products.Default;
private readonly IProductRepository _productRepository;
private readonly IProductDetailRepository _repository;
public ProductDetailAppService(
IProductRepository productRepository,
IProductDetailRepository repository) : base(repository)
{
_productRepository = productRepository;
_repository = repository;
}
@ -75,19 +72,17 @@ namespace EasyAbp.EShop.Products.ProductDetails
{
await CheckUpdatePolicyAsync();
var product = await _productRepository.GetAsync(x => x.ProductDetailId == id);
var detail = await GetEntityByIdAsync(id);
await AuthorizationService.CheckMultiStorePolicyAsync(product.StoreId, UpdatePolicyName,
await AuthorizationService.CheckMultiStorePolicyAsync(detail.StoreId, UpdatePolicyName,
ProductsPermissions.Products.CrossStore);
if (input.StoreId != product.StoreId)
if (input.StoreId != detail.StoreId)
{
await AuthorizationService.CheckMultiStorePolicyAsync(input.StoreId, UpdatePolicyName,
ProductsPermissions.Products.CrossStore);
}
var detail = await GetEntityByIdAsync(id);
MapToEntity(input, detail);
await Repository.UpdateAsync(detail, autoSave: true);
@ -99,9 +94,9 @@ namespace EasyAbp.EShop.Products.ProductDetails
{
await CheckDeletePolicyAsync();
var product = await _productRepository.GetAsync(x => x.ProductDetailId == id);
var detail = await GetEntityByIdAsync(id);
await AuthorizationService.CheckMultiStorePolicyAsync(product.StoreId, DeletePolicyName,
await AuthorizationService.CheckMultiStorePolicyAsync(detail.StoreId, DeletePolicyName,
ProductsPermissions.Products.CrossStore);
await Repository.DeleteAsync(id);

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

@ -14,7 +14,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -35,8 +34,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -86,7 +85,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -15,7 +15,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -36,8 +35,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -87,7 +86,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -14,7 +14,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -35,8 +34,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -86,7 +85,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -14,7 +14,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -35,8 +34,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -86,7 +85,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -15,7 +15,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -36,8 +35,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -87,7 +86,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -15,7 +15,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -36,8 +35,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -87,7 +86,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -14,7 +14,6 @@
"ProductDisplayName": "Display name",
"ProductDisplayOrder": "Display order",
"ProductSold": "Sold",
"ProductDetailStoreId": "Store ID",
"ProductDetailDescription": "Description",
"ProductCategory": "Categories",
"ProductAttribute": "Attribute",
@ -35,8 +34,8 @@
"ProductSkuOrderMaxQuantity": "Maximum quantity per purchase",
"ProductSkuPaymentExpireIn": "Payment expire in",
"ProductSkuMediaResources": "Media resources",
"ProductSkuProductDetailId": "Product detail ID",
"ProductSkuContentDescription": "SKU content",
"ProductSkuProductDetailDescriptionPlaceholder": "Fallback to details of the product if you keep null here.",
"CreateProductSku": "New",
"EditProductSku": "Edit",
"ProductSkuDeletionConfirmationMessage": "Are you sure to delete the product SKU {0}?",
@ -86,7 +85,6 @@
"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:ProductDetailHasBeenUsed": "ProductDetail {productDetailId} has been used.",
"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:NotAllowedToGetCategoryListWithShowHidden": "You have no permission to get category list with hidden categories.",

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

@ -15,8 +15,7 @@
"ProductDisplayName": "商品名称",
"ProductDisplayOrder": "展示顺序",
"ProductSold": "已售",
"ProductDetailStoreId": "店铺 ID",
"ProductDetailDescription": "内容",
"ProductDetailDescription": "详情介绍",
"ProductCategory": "所属类目",
"ProductAttribute": "商品属性",
"ProductAttributeNames": "商品属性",
@ -36,8 +35,8 @@
"ProductSkuOrderMaxQuantity": "单次最大购买数量",
"ProductSkuPaymentExpireIn": "支付限时",
"ProductSkuMediaResources": "多媒体资源",
"ProductSkuProductDetailId": "商品详情 ID",
"ProductSkuContentDescription": "SKU 描述",
"ProductSkuProductDetailDescriptionPlaceholder": "如果此处不填,将使用商品自身的详情介绍。",
"CreateProductSku": "新建",
"EditProductSku": "编辑",
"ProductSkuDeletionConfirmationMessage": "确认删除 SKU {0}?",
@ -84,7 +83,6 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的产品名称'{uniqueName}'重复",
"EasyAbp.EShop.Products:InventoryChangeFailed": "产品{productId} (SKU: {productSkuId})的库存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的产品组({productGroupName})不存在",
"EasyAbp.EShop.Products:ProductDetailHasBeenUsed": "商品详情{productDetailId}已被使用",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代码{code}重复",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重复",
"EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "您没有权限获取带有隐藏类别的类别列表",

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

@ -15,8 +15,7 @@
"ProductDisplayName": "商品名稱",
"ProductDisplayOrder": "展示順序",
"ProductSold": "已售",
"ProductDetailStoreId": "店铺 ID",
"ProductDetailDescription": "內容",
"ProductDetailDescription": "詳情介紹",
"ProductCategory": "所屬類目",
"ProductAttribute": "商品屬性",
"ProductAttributeNames": "商品屬性",
@ -36,8 +35,8 @@
"ProductSkuOrderMaxQuantity": "單次最大購買數量",
"ProductSkuPaymentExpireIn": "支付限時",
"ProductSkuMediaResources": "多媒體資源",
"ProductSkuProductDetailId": "商品詳情 ID",
"ProductSkuContentDescription": "SKU 描述",
"ProductSkuProductDetailDescriptionPlaceholder": "如果此處不填,將使用商品自身的詳情介紹。",
"CreateProductSku": "新建",
"EditProductSku": "編輯",
"ProductSkuDeletionConfirmationMessage": "確認刪除 SKU {0}?",
@ -84,7 +83,6 @@
"EasyAbp.EShop.Products:DuplicatedProductUniqueName": "唯一的產品名稱'{uniqueName}'重復",
"EasyAbp.EShop.Products:InventoryChangeFailed": "產品{productId} (SKU: {productSkuId})的庫存不能由{originalInventory}中的{changedInventory}更改",
"EasyAbp.EShop.Products:NonexistentProductGroup": "指定的產品組({productGroupName})不存在",
"EasyAbp.EShop.Products:ProductDetailHasBeenUsed": "商品詳情{productDetailId}已被使用",
"EasyAbp.EShop.Products:ProductSkuCodeDuplicated": "商品{productId}的Sku代碼{code}重復",
"EasyAbp.EShop.Products:ProductSkuDuplicated": "商品{productId}的Sku{serializedAttributeOptionIds}重復",
"EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden": "您沒有權限獲取帶有隱藏類別的類別列表",

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

@ -8,7 +8,7 @@ namespace EasyAbp.EShop.Products.Products
{
string ProductGroupName { get; }
Guid ProductDetailId { get; }
Guid? ProductDetailId { get; }
string UniqueName { get; }

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

@ -16,7 +16,7 @@ namespace EasyAbp.EShop.Products.Products
public string ProductGroupName { get; set; }
public Guid ProductDetailId { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }

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

@ -6,7 +6,6 @@
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 ProductDetailHasBeenUsed = "EasyAbp.EShop.Products:ProductDetailHasBeenUsed";
public const string ProductSkuCodeDuplicated = "EasyAbp.EShop.Products:ProductSkuCodeDuplicated";
public const string ProductSkuDuplicated = "EasyAbp.EShop.Products:ProductSkuDuplicated";
public const string NotAllowedToGetCategoryListWithShowHidden = "EasyAbp.EShop.Products:NotAllowedToGetCategoryListWithShowHidden";

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

@ -15,7 +15,7 @@ namespace EasyAbp.EShop.Products.Products
[NotNull]
public virtual string ProductGroupName { get; protected set; }
public virtual Guid ProductDetailId { get; protected set; }
public virtual Guid? ProductDetailId { get; protected set; }
[CanBeNull]
public virtual string UniqueName { get; protected set; }
@ -51,7 +51,7 @@ namespace EasyAbp.EShop.Products.Products
Guid? tenantId,
Guid storeId,
[NotNull] string productGroupName,
Guid productDetailId,
Guid? productDetailId,
[CanBeNull] string uniqueName,
[NotNull] string displayName,
InventoryStrategy inventoryStrategy,

13
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDetailHasBeenUsedException.cs

@ -1,13 +0,0 @@
using System;
using Volo.Abp;
namespace EasyAbp.EShop.Products.Products
{
public class ProductDetailHasBeenUsedException : BusinessException
{
public ProductDetailHasBeenUsedException(Guid productDetailId) : base(ProductsErrorCodes.ProductDetailHasBeenUsed)
{
WithData(nameof(productDetailId), productDetailId);
}
}
}

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

@ -4,7 +4,9 @@ using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Options.ProductGroups;
using EasyAbp.EShop.Products.ProductCategories;
using EasyAbp.EShop.Products.ProductDetails;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Services;
using Volo.Abp.Uow;
@ -14,6 +16,7 @@ namespace EasyAbp.EShop.Products.Products
{
private readonly IProductRepository _productRepository;
private readonly IProductPriceProvider _productPriceProvider;
private readonly IProductDetailRepository _productDetailRepository;
private readonly IProductCategoryRepository _productCategoryRepository;
private readonly IProductInventoryProvider _productInventoryProvider;
private readonly IProductGroupConfigurationProvider _productGroupConfigurationProvider;
@ -21,12 +24,14 @@ namespace EasyAbp.EShop.Products.Products
public ProductManager(
IProductRepository productRepository,
IProductPriceProvider productPriceProvider,
IProductDetailRepository productDetailRepository,
IProductCategoryRepository productCategoryRepository,
IProductInventoryProvider productInventoryProvider,
IProductGroupConfigurationProvider productGroupConfigurationProvider)
{
_productRepository = productRepository;
_productPriceProvider = productPriceProvider;
_productDetailRepository = productDetailRepository;
_productCategoryRepository = productCategoryRepository;
_productInventoryProvider = productInventoryProvider;
_productGroupConfigurationProvider = productGroupConfigurationProvider;
@ -43,7 +48,7 @@ namespace EasyAbp.EShop.Products.Products
await _productRepository.InsertAsync(product, autoSave: true);
await CheckProductDetailAvailableAsync(product.Id, product.ProductDetailId);
await CheckProductDetailAsync(product);
await UpdateProductCategoriesAsync(product.Id, categoryIds);
@ -69,7 +74,7 @@ namespace EasyAbp.EShop.Products.Products
await _productRepository.UpdateAsync(product, autoSave: true);
await CheckProductDetailAvailableAsync(product.Id, product.ProductDetailId);
await CheckProductDetailAsync(product);
await UpdateProductCategoriesAsync(product.Id, categoryIds);
@ -101,10 +106,12 @@ namespace EasyAbp.EShop.Products.Products
await CheckProductSkuNameUniqueAsync(product, productSku);
productSku.TrimCode();
productSku.TrimName();
product.ProductSkus.AddIfNotContains(productSku);
await CheckProductDetailAsync(product);
return await _productRepository.UpdateAsync(product, true);
}
@ -140,6 +147,8 @@ namespace EasyAbp.EShop.Products.Products
{
await CheckProductSkuNameUniqueAsync(product, productSku);
await CheckProductDetailAsync(product);
return await _productRepository.UpdateAsync(product, true);
}
@ -157,17 +166,27 @@ namespace EasyAbp.EShop.Products.Products
await _productRepository.CheckUniqueNameAsync(product);
}
protected virtual async Task CheckProductDetailAsync(Product product)
{
if (product.ProductDetailId.HasValue)
{
await CheckProductDetailExistAsync(product.ProductDetailId.Value, product.StoreId);
}
foreach (var sku in product.ProductSkus.Where(x => x.ProductDetailId.HasValue))
{
await CheckProductDetailExistAsync(sku.ProductDetailId!.Value, product.StoreId);
}
}
[UnitOfWork]
protected virtual async Task CheckProductDetailAvailableAsync(Guid currentProductId, Guid desiredProductDetailId)
protected virtual async Task CheckProductDetailExistAsync(Guid productDetailId, Guid storeId)
{
var otherOwner = await _productRepository.FindAsync(x =>
x.ProductDetailId == desiredProductDetailId && x.Id != currentProductId);
var productDetail = await _productDetailRepository.GetAsync(productDetailId);
// Todo: should also check ProductSku owner
if (otherOwner != null)
if (productDetail.StoreId.HasValue && productDetail.StoreId.Value != storeId)
{
throw new ProductDetailHasBeenUsedException(desiredProductDetailId);
throw new EntityNotFoundException(typeof(ProductDetail), productDetailId);
}
}

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs

@ -70,7 +70,7 @@ namespace EasyAbp.EShop.Products.Products
this.SetDefaultsForExtraProperties();
}
internal void TrimCode()
internal void TrimName()
{
Name = Name?.Trim();
}

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

@ -14,7 +14,7 @@ namespace EasyAbp.EShop.Products.Products
public virtual string ProductGroupName { get; protected set; }
public virtual Guid ProductDetailId { get; protected set; }
public virtual Guid? ProductDetailId { get; protected set; }
public virtual string UniqueName { get; protected set; }
@ -51,7 +51,7 @@ namespace EasyAbp.EShop.Products.Products
Guid? tenantId,
Guid storeId,
string productGroupName,
Guid productDetailId,
Guid? productDetailId,
string uniqueName,
string displayName,
InventoryStrategy inventoryStrategy,

15
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/CreateModal.cshtml.cs

@ -73,15 +73,18 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product
public virtual async Task<IActionResult> OnPostAsync()
{
var detail = await _productDetailAppService.CreateAsync(
ObjectMapper
.Map<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>(Product.ProductDetail));
var createDto = ObjectMapper.Map<CreateEditProductViewModel, CreateUpdateProductDto>(Product);
createDto.ProductDetailId = detail.Id;
if (Product.ProductDetail.HasContent())
{
var detail = await _productDetailAppService.CreateAsync(
ObjectMapper
.Map<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>(Product.ProductDetail));
createDto.ProductDetailId = detail.Id;
}
var product = await _service.CreateAsync(createDto);
await _service.CreateAsync(createDto);
return NoContent();
}

49
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/Product/EditModal.cshtml.cs

@ -59,8 +59,6 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product
.Select(dto => new SelectListItem(dto.DisplayName, dto.Id.ToString())).ToList();
var productDto = await _service.GetAsync(Id);
var detailDto = await _productDetailAppService.GetAsync(productDto.ProductDetailId);
Product = ObjectMapper.Map<ProductDto, CreateEditProductViewModel>(productDto);
@ -70,26 +68,51 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product
MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount
})).Items.Select(x => x.CategoryId).ToList();
Product.ProductDetail = new CreateEditProductDetailViewModel
if (productDto.ProductDetailId.HasValue)
{
StoreId = detailDto.StoreId,
Description = detailDto.Description
};
var detailDto = await _productDetailAppService.GetAsync(productDto.ProductDetailId.Value);
Product.ProductDetail = new CreateEditProductDetailViewModel
{
StoreId = detailDto.StoreId,
Description = detailDto.Description
};
}
}
public virtual async Task<IActionResult> OnPostAsync()
{
var product = await _service.GetAsync(Id);
var originalProduct = await _service.GetAsync(Id);
var detail = await _productDetailAppService.GetAsync(product.ProductDetailId);
var updateProductDto = ObjectMapper.Map<CreateEditProductViewModel, CreateUpdateProductDto>(Product);
await _productDetailAppService.UpdateAsync(detail.Id,
ObjectMapper
.Map<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>(Product.ProductDetail));
if (Product.ProductDetail.HasContent())
{
if (originalProduct.ProductDetailId.HasValue)
{
var detail = await _productDetailAppService.GetAsync(originalProduct.ProductDetailId.Value);
var updateProductDto = ObjectMapper.Map<CreateEditProductViewModel, CreateUpdateProductDto>(Product);
await _productDetailAppService.UpdateAsync(detail.Id,
ObjectMapper.Map<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>(
Product.ProductDetail));
updateProductDto.ProductDetailId = detail.Id;
}
else
{
var detail = await _productDetailAppService.CreateAsync(
ObjectMapper.Map<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>(
Product.ProductDetail));
updateProductDto.ProductDetailId = detail.Id;
updateProductDto.ProductDetailId = detail.Id;
}
}
else if (originalProduct.ProductDetailId.HasValue)
{
await _productDetailAppService.DeleteAsync(originalProduct.ProductDetailId.Value);
updateProductDto.ProductDetailId = null;
}
await _service.UpdateAsync(Id, updateProductDto);
return NoContent();

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

@ -12,11 +12,15 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewM
public class CreateEditProductDetailViewModel
{
[HiddenInput]
[Display(Name = "ProductDetailStoreId")]
public Guid? StoreId { get; set; }
[TextArea(Rows = 4)]
[Display(Name = "ProductDetailDescription")]
public string Description { get; set; }
public bool HasContent()
{
return !Description.IsNullOrEmpty();
}
}
}

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

@ -34,8 +34,8 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.Product.ViewM
[Required]
[Display(Name = "ProductDisplayName")]
public string DisplayName { get; set; }
public CreateEditProductDetailViewModel ProductDetail { get; set; }
public CreateEditProductDetailViewModel ProductDetail { get; set; } = new();
[Required]
[Placeholder("ProductAttributeNamesPlaceholder")]

14
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/CreateModal.cshtml.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.ProductInventories;
using EasyAbp.EShop.Products.ProductInventories.Dtos;
using EasyAbp.EShop.Products.Products;
@ -27,13 +29,16 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
public Dictionary<string, ICollection<SelectListItem>> Attributes { get; set; }
private readonly IProductInventoryAppService _productInventoryAppService;
private readonly IProductDetailAppService _productDetailAppService;
private readonly IProductAppService _productAppService;
public CreateModalModel(
IProductInventoryAppService productInventoryAppService,
IProductDetailAppService productDetailAppService,
IProductAppService productAppService)
{
_productInventoryAppService = productInventoryAppService;
_productDetailAppService = productDetailAppService;
_productAppService = productAppService;
}
@ -57,6 +62,15 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
createDto.AttributeOptionIds = SelectedAttributeOptionIdDict.Values.ToList();
if (ProductSku.ProductDetail.HasContent())
{
var detail = await _productDetailAppService.CreateAsync(
ObjectMapper.Map<CreateEditSkuProductDetailViewModel, CreateUpdateProductDetailDto>(ProductSku
.ProductDetail));
createDto.ProductDetailId = detail.Id;
}
var skuDto = await _productAppService.CreateSkuAsync(ProductId, createDto);
await _productInventoryAppService.UpdateAsync(new UpdateProductInventoryDto

62
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/EditModal.cshtml.cs

@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
{
@ -24,25 +24,71 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku
public EditProductSkuViewModel ProductSku { get; set; }
private readonly IProductAppService _productAppService;
private readonly IProductDetailAppService _productDetailAppService;
public EditModalModel(IProductAppService productAppService)
public EditModalModel(
IProductAppService productAppService,
IProductDetailAppService productDetailAppService)
{
_productAppService = productAppService;
_productDetailAppService = productDetailAppService;
}
public virtual async Task OnGetAsync()
{
var product = await _productAppService.GetAsync(ProductId);
var sku = product.ProductSkus.Single(x => x.Id == ProductSkuId);
ProductSku =
ObjectMapper.Map<ProductSkuDto, EditProductSkuViewModel>(
product.ProductSkus.Single(x => x.Id == ProductSkuId));
ProductSku = ObjectMapper.Map<ProductSkuDto, EditProductSkuViewModel>(sku);
if (sku.ProductDetailId.HasValue)
{
var detailDto = await _productDetailAppService.GetAsync(sku.ProductDetailId.Value);
ProductSku.ProductDetail = new CreateEditSkuProductDetailViewModel
{
StoreId = detailDto.StoreId,
Description = detailDto.Description
};
}
}
public virtual async Task<IActionResult> OnPostAsync()
{
await _productAppService.UpdateSkuAsync(ProductId, ProductSkuId,
ObjectMapper.Map<EditProductSkuViewModel, UpdateProductSkuDto>(ProductSku));
var originalProduct = await _productAppService.GetAsync(ProductId);
var originalSku = originalProduct.ProductSkus.Single(x => x.Id == ProductSkuId);
var updateProductSkuDto = ObjectMapper.Map<EditProductSkuViewModel, UpdateProductSkuDto>(ProductSku);
if (ProductSku.ProductDetail.HasContent())
{
if (originalSku.ProductDetailId.HasValue)
{
var detail = await _productDetailAppService.GetAsync(originalSku.ProductDetailId.Value);
await _productDetailAppService.UpdateAsync(detail.Id,
ObjectMapper.Map<CreateEditSkuProductDetailViewModel, CreateUpdateProductDetailDto>(
ProductSku.ProductDetail));
updateProductSkuDto.ProductDetailId = detail.Id;
}
else
{
var detail = await _productDetailAppService.CreateAsync(
ObjectMapper.Map<CreateEditSkuProductDetailViewModel, CreateUpdateProductDetailDto>(
ProductSku.ProductDetail));
updateProductSkuDto.ProductDetailId = detail.Id;
}
}
else if (originalSku.ProductDetailId.HasValue)
{
await _productDetailAppService.DeleteAsync(originalSku.ProductDetailId.Value);
updateProductSkuDto.ProductDetailId = null;
}
await _productAppService.UpdateSkuAsync(ProductId, ProductSkuId, updateProductSkuDto);
return NoContent();
}

23
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/CreateEditSkuProductDetailViewModel.cs

@ -0,0 +1,23 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.ViewModels
{
public class CreateEditSkuProductDetailViewModel
{
[HiddenInput]
public Guid? StoreId { get; set; }
[TextArea(Rows = 4)]
[Display(Name = "ProductDetailDescription")]
[Placeholder("ProductSkuProductDetailDescriptionPlaceholder")]
public string Description { get; set; }
public bool HasContent()
{
return !Description.IsNullOrEmpty();
}
}
}

6
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/Pages/EShop/Products/Products/ProductSku/ViewModels/EditProductSkuViewModel.cs

@ -42,9 +42,7 @@ namespace EasyAbp.EShop.Products.Web.Pages.EShop.Products.Products.ProductSku.Vi
[Display(Name = "ProductSkuMediaResources")]
public string MediaResources { get; set; }
[HiddenInput]
[Display(Name = "ProductSkuProductDetailId")]
public Guid? ProductDetailId { get; set; }
public CreateEditSkuProductDetailViewModel ProductDetail { get; set; } = new();
}
}

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Web/ProductsWebAutoMapperProfile.cs

@ -51,16 +51,23 @@ namespace EasyAbp.EShop.Products.Web
CreateMap<ProductDetailDto, CreateEditProductDetailViewModel>();
CreateMap<CreateEditProductDetailViewModel, CreateUpdateProductDetailDto>()
.Ignore(dto => dto.ExtraProperties);
CreateMap<CreateEditSkuProductDetailViewModel, CreateUpdateProductDetailDto>()
.Ignore(dto => dto.ExtraProperties);
CreateMap<ProductAttributeDto, CreateEditProductAttributeViewModel>();
CreateMap<CreateEditProductAttributeViewModel, CreateUpdateProductAttributeDto>()
.Ignore(dto => dto.ExtraProperties);
CreateMap<CreateProductSkuViewModel, CreateProductSkuDto>()
.Ignore(dto => dto.ExtraProperties)
.Ignore(dto => dto.ProductDetailId)
.ForSourceMember(model => model.ProductDetail, opt => opt.DoNotValidate())
.ForSourceMember(model => model.Inventory, opt => opt.DoNotValidate())
.Ignore(dto => dto.AttributeOptionIds);
CreateMap<EditProductSkuViewModel, UpdateProductSkuDto>()
.Ignore(dto => dto.ProductDetailId)
.ForSourceMember(model => model.ProductDetail, opt => opt.DoNotValidate())
.Ignore(dto => dto.ExtraProperties);
CreateMap<ProductSkuDto, EditProductSkuViewModel>()
.Ignore(x => x.ProductDetail)
.ForSourceMember(dto => dto.AttributeOptionIds, opt => opt.DoNotValidate())
.ForSourceMember(dto => dto.Inventory, opt => opt.DoNotValidate())
.ForSourceMember(dto => dto.Sold, opt => opt.DoNotValidate());

74
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs

@ -4,9 +4,11 @@ using System.Linq;
using Shouldly;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Options;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities;
using Xunit;
namespace EasyAbp.EShop.Products.Products
@ -20,18 +22,19 @@ namespace EasyAbp.EShop.Products.Products
{
_productAppService = GetRequiredService<IProductAppService>();
_eShopProductsOptions = GetRequiredService<IOptions<EShopProductsOptions>>().Value;
}
[Fact]
public async Task Should_Create_Product()
{
// Arrange
_eShopProductsOptions.Groups.Configure("Default Group Name", x =>
{
x.DisplayName = "Default Group Name";
x.Description = "Default Description";
});
}
[Fact]
public async Task Should_Create_Product()
{
// Arrange
var requestDto = new CreateUpdateProductDto
{
ProductGroupName = "Default Group Name",
@ -144,5 +147,66 @@ namespace EasyAbp.EShop.Products.Products
getResult.MinimumPrice.ShouldBe(1m);
getResult.MaximumPrice.ShouldBe(3m);
}
[Fact]
public async Task Should_Check_ProductDetailId()
{
var wrongProductDetailId = Guid.NewGuid();
var requestDto = new CreateUpdateProductDto
{
ProductGroupName = "Default Group Name",
StoreId = ProductsTestData.Store1Id,
UniqueName = "Unique Pencil",
DisplayName = "Pencil",
ProductDetailId = wrongProductDetailId,
InventoryStrategy = InventoryStrategy.NoNeed,
DisplayOrder = 0,
IsPublished = true,
ProductAttributes = new List<CreateUpdateProductAttributeDto>
{
new CreateUpdateProductAttributeDto
{
DisplayName = "Default Attribute 1",
Description = "Default Description 1",
DisplayOrder = 1,
ProductAttributeOptions = new List<CreateUpdateProductAttributeOptionDto>
{
new CreateUpdateProductAttributeOptionDto
{
DisplayName = "Option 1"
}
}
}
}
};
// Act
(await Should.ThrowAsync<EntityNotFoundException>(async () =>
{
await _productAppService.CreateAsync(requestDto);
})).EntityType.ShouldBe(typeof(ProductDetail));
}
[Fact]
public async Task Should_Check_Sku_ProductDetailId()
{
await Should_Create_Product();
var wrongProductDetailId = Guid.NewGuid();
(await Should.ThrowAsync<EntityNotFoundException>(async () =>
{
await _productAppService.CreateSkuAsync(ProductsTestData.Product1Id, new CreateProductSkuDto
{
AttributeOptionIds = new List<Guid> {ProductsTestData.Product1Attribute1Option4Id},
ProductDetailId = wrongProductDetailId,
Currency = "CNY",
Price = 10m,
OrderMinQuantity = 1,
OrderMaxQuantity = 10
});
})).EntityType.ShouldBe(typeof(ProductDetail));
}
}
}

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

@ -1,4 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
@ -6,18 +9,117 @@ namespace EasyAbp.EShop.Products.Products
{
public class ProductDomainTests : ProductsDomainTestBase
{
private IProductRepository ProductRepository { get; }
private IProductManager ProductManager { get; }
public ProductDomainTests()
{
ProductRepository = ServiceProvider.GetRequiredService<IProductRepository>();
ProductManager = ServiceProvider.GetRequiredService<IProductManager>();
}
[Fact]
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);
await ProductManager.CreateAsync(product2);
product2 = await ProductRepository.GetAsync(product2.Id);
product2.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails2Id);
}
[Fact]
public async Task Should_Set_Sku_ProductDetailId()
{
await WithUnitOfWorkAsync(async () =>
{
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);
await ProductManager.UpdateAsync(product1);
product1 = await ProductRepository.GetAsync(product1.Id);
sku1 = product1.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
sku1.ProductDetailId.ShouldBe(ProductsTestData.ProductDetails1Id);
});
}
[Fact]
public async Task Test1()
public async Task Should_Reuse_ProductDetail()
{
// Arrange
var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
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);
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);
}
[Fact]
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();
}
[Fact]
public async Task Should_Remove_Sku_ProductDetailId()
{
await WithUnitOfWorkAsync(async () =>
{
await Should_Set_Sku_ProductDetailId();
var product1 = await ProductRepository.GetAsync(ProductsTestData.Product1Id);
var sku1 = product1.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
sku1.ProductDetailId.ShouldNotBeNull();
typeof(ProductSku).GetProperty(nameof(Product.ProductDetailId))!.SetValue(sku1, null);
await ProductManager.UpdateAsync(product1);
// Assert
product1 = await ProductRepository.GetAsync(product1.Id);
sku1 = product1.ProductSkus.Single(x => x.Id == ProductsTestData.Product1Sku1Id);
// Assert
sku1.ProductDetailId.ShouldBeNull();
});
}
}
}

4
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs

@ -12,6 +12,8 @@ namespace EasyAbp.EShop.Products
public static Guid Product1Id { get; } = Guid.NewGuid();
public static Guid Product2Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Option1Id { get; } = Guid.NewGuid();
@ -20,6 +22,8 @@ namespace EasyAbp.EShop.Products
public static Guid Product1Attribute1Option3Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Option4Id { get; } = Guid.NewGuid();
public static Guid Product1Sku1Id { get; } = Guid.NewGuid();
public static Guid Product1Sku2Id { get; } = Guid.NewGuid();

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

@ -53,7 +53,8 @@ namespace EasyAbp.EShop.Products
{
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option1Id, "S", null),
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option2Id, "M", null),
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option3Id, "L", null)
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option3Id, "L", null),
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option4Id, "XL", null)
});
product.ProductAttributes.Add(attribute);

5131
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411111127_MadeProductDetailIdNullable.Designer.cs

File diff suppressed because it is too large

70
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411111127_MadeProductDetailIdNullable.cs

@ -0,0 +1,70 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
public partial class MadeProductDetailIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Guid>(
name: "ProductDetailId",
table: "EasyAbpEShopProductsProductViews",
type: "uniqueidentifier",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uniqueidentifier");
migrationBuilder.AlterColumn<Guid>(
name: "ProductDetailId",
table: "EasyAbpEShopProductsProducts",
type: "uniqueidentifier",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uniqueidentifier");
migrationBuilder.AlterColumn<DateTime>(
name: "ProductDetailModificationTime",
table: "EasyAbpEShopOrdersOrderLines",
type: "datetime2",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "datetime2");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Guid>(
name: "ProductDetailId",
table: "EasyAbpEShopProductsProductViews",
type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uniqueidentifier",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "ProductDetailId",
table: "EasyAbpEShopProductsProducts",
type: "uniqueidentifier",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uniqueidentifier",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "ProductDetailModificationTime",
table: "EasyAbpEShopOrdersOrderLines",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "datetime2",
oldNullable: true);
}
}
}

5134
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411204916_AddedProductDetailIdToOrderLine.Designer.cs

File diff suppressed because it is too large

26
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20220411204916_AddedProductDetailIdToOrderLine.cs

@ -0,0 +1,26 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
public partial class AddedProductDetailIdToOrderLine : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "ProductDetailId",
table: "EasyAbpEShopOrdersOrderLines",
type: "uniqueidentifier",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ProductDetailId",
table: "EasyAbpEShopOrdersOrderLines");
}
}
}

11
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.2")
.HasAnnotation("ProductVersion", "6.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
@ -214,7 +214,10 @@ namespace EShopSample.Migrations
b.Property<Guid?>("OrderId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("ProductDetailModificationTime")
b.Property<Guid?>("ProductDetailId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("ProductDetailModificationTime")
.HasColumnType("datetime2");
b.Property<string>("ProductDisplayName")
@ -1398,7 +1401,7 @@ namespace EShopSample.Migrations
b.Property<TimeSpan?>("PaymentExpireIn")
.HasColumnType("time");
b.Property<Guid>("ProductDetailId")
b.Property<Guid?>("ProductDetailId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ProductGroupName")
@ -1670,7 +1673,7 @@ namespace EShopSample.Migrations
b.Property<decimal?>("MinimumPrice")
.HasColumnType("decimal(20,8)");
b.Property<Guid>("ProductDetailId")
b.Property<Guid?>("ProductDetailId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ProductGroupDisplayName")

Loading…
Cancel
Save