Browse Source

Fix bug of prices of ProductView are always null

Fix #133
pull/142/head
gdlcf88 4 years ago
parent
commit
4d2db2d816
  1. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
  2. 44
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductViewAppService.cs
  3. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductPriceProvider.cs
  4. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs
  5. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPriceProvider.cs
  6. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductRepository.cs
  7. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductViewRepository.cs
  8. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
  9. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
  10. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/Products/ProductRepository.cs
  11. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/Products/ProductViewRepository.cs
  12. 31
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductAppServiceTests.cs
  13. 46
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductViewAppServiceTests.cs
  14. 18
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestData.cs
  15. 60
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.TestBase/ProductsTestDataBuilder.cs

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

@ -53,7 +53,7 @@ namespace EasyAbp.EShop.Products.Products
protected override async Task<IQueryable<Product>> CreateFilteredQueryAsync(GetProductListInput input)
{
var query = input.CategoryId.HasValue
? _repository.WithDetails(input.CategoryId.Value)
? await _repository.WithDetailsAsync(input.CategoryId.Value)
: (await _repository.WithDetailsAsync());
return query
@ -329,7 +329,7 @@ namespace EasyAbp.EShop.Products.Products
{
var productSkuDto = productDto.ProductSkus.First(x => x.Id == productSku.Id);
var priceDataModel = await _productManager.GetProductPriceAsync(product, productSku);
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku);
productSkuDto.Price = priceDataModel.Price;
productSkuDto.DiscountedPrice = priceDataModel.DiscountedPrice;

44
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductViewAppService.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.CacheItems;
@ -20,17 +21,20 @@ namespace EasyAbp.EShop.Products.Products
private readonly IProductViewCacheKeyProvider _productViewCacheKeyProvider;
private readonly IDistributedCache<ProductViewCacheItem> _cache;
private readonly IProductManager _productManager;
private readonly IProductRepository _productRepository;
private readonly IProductViewRepository _repository;
public ProductViewAppService(
IProductViewCacheKeyProvider productViewCacheKeyProvider,
IDistributedCache<ProductViewCacheItem> cache,
IProductManager productManager,
IProductRepository productRepository,
IProductViewRepository repository) : base(repository)
{
_productViewCacheKeyProvider = productViewCacheKeyProvider;
_cache = cache;
_productManager = productManager;
_productRepository = productRepository;
_repository = repository;
}
@ -38,8 +42,8 @@ namespace EasyAbp.EShop.Products.Products
protected override async Task<IQueryable<ProductView>> CreateFilteredQueryAsync(GetProductListInput input)
{
var query = input.CategoryId.HasValue
? _repository.WithDetails(input.CategoryId.Value)
: (await _repository.WithDetailsAsync());
? await _repository.WithDetailsAsync(input.CategoryId.Value)
: await _repository.WithDetailsAsync();
return query
.Where(x => x.StoreId == input.StoreId)
@ -96,7 +100,7 @@ namespace EasyAbp.EShop.Products.Products
protected virtual async Task BuildStoreProductViewsAsync(Guid storeId)
{
var products = await _productRepository.GetListAsync(x => x.StoreId == storeId);
var products = await _productRepository.GetListAsync(x => x.StoreId == storeId, true);
using var uow = UnitOfWorkManager.Begin(true, true);
@ -104,9 +108,14 @@ namespace EasyAbp.EShop.Products.Products
foreach (var product in products)
{
await _repository.InsertAsync(ObjectMapper.Map<Product, ProductView>(product));
var productView = ObjectMapper.Map<Product, ProductView>(product);
await FillPriceInfoWithRealPriceAsync(product, productView);
await _repository.InsertAsync(productView);
}
await uow.SaveChangesAsync();
await uow.CompleteAsync();
await _cache.SetAsync(await GetCacheKeyAsync(storeId), new ProductViewCacheItem(),
@ -116,5 +125,32 @@ namespace EasyAbp.EShop.Products.Products
await SettingProvider.GetOrNullAsync(ProductsSettings.ProductView.CacheDurationSeconds)))
});
}
protected virtual async Task FillPriceInfoWithRealPriceAsync(Product product, ProductView productView)
{
if (product.ProductSkus.IsNullOrEmpty())
{
return;
}
decimal? min = null, max = null;
foreach (var productSku in product.ProductSkus)
{
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku);
if (min is null || priceDataModel.Price < min.Value)
{
min = productSku.Price;
}
if (max is null || priceDataModel.Price > max.Value)
{
max = productSku.Price;
}
}
productView.SetPrices(min, max);
}
}
}

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/DefaultProductPriceProvider.cs

@ -1,12 +1,11 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products
{
public class DefaultProductPriceProvider : IProductPriceProvider, ITransientDependency
{
public virtual Task<decimal> GetPriceAsync(Product product, ProductSku productSku)
public virtual Task<decimal> GetPriceAsync(IProduct product, IProductSku productSku)
{
return Task.FromResult(productSku.Price);
}

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

@ -29,6 +29,6 @@ namespace EasyAbp.EShop.Products.Products
Task<bool> TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold);
Task<PriceDataModel> GetProductPriceAsync(Product product, ProductSku productSku);
Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku);
}
}

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductPriceProvider.cs

@ -1,10 +1,9 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace EasyAbp.EShop.Products.Products
{
public interface IProductPriceProvider
{
Task<decimal> GetPriceAsync(Product product, ProductSku productSku);
Task<decimal> GetPriceAsync(IProduct product, IProductSku productSku);
}
}

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

@ -8,9 +8,9 @@ namespace EasyAbp.EShop.Products.Products
{
public interface IProductRepository : IRepository<Product, Guid>
{
IQueryable<Product> GetQueryable(Guid categoryId);
Task<IQueryable<Product>> GetQueryableAsync(Guid categoryId);
IQueryable<Product> WithDetails(Guid categoryId);
Task<IQueryable<Product>> WithDetailsAsync(Guid categoryId);
Task CheckUniqueNameAsync(Product entity, CancellationToken cancellationToken = new CancellationToken());
}

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductViewRepository.cs

@ -1,13 +1,14 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace EasyAbp.EShop.Products.Products
{
public interface IProductViewRepository : IRepository<ProductView, Guid>
{
IQueryable<ProductView> GetQueryable(Guid categoryId);
Task<IQueryable<ProductView>> GetQueryableAsync(Guid categoryId);
IQueryable<ProductView> WithDetails(Guid categoryId);
Task<IQueryable<ProductView>> WithDetailsAsync(Guid categoryId);
}
}

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

@ -210,13 +210,13 @@ namespace EasyAbp.EShop.Products.Products
return await _productInventoryProvider.TryReduceInventoryAsync(product, productSku, quantity, increaseSold);
}
[UnitOfWork]
public virtual async Task<PriceDataModel> GetProductPriceAsync(Product product, ProductSku productSku)
public virtual async Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku)
{
var price = await _productPriceProvider.GetPriceAsync(product, productSku);
var discountedPrice = price;
// Todo: provider execution ordering.
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IProductDiscountProvider>>())
{
discountedPrice = await provider.GetDiscountedPriceAsync(product, productSku, discountedPrice);

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

@ -89,5 +89,11 @@ namespace EasyAbp.EShop.Products.Products
{
Sold = sold;
}
public void SetPrices(decimal? minimumPrice, decimal? maximumPrice)
{
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
}
}
}

12
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/Products/ProductRepository.cs

@ -51,20 +51,20 @@ namespace EasyAbp.EShop.Products.Products
.Include(x => x.ProductSkus);
}
public IQueryable<Product> GetQueryable(Guid categoryId)
public virtual async Task<IQueryable<Product>> GetQueryableAsync(Guid categoryId)
{
return JoinProductCategories(DbSet, categoryId);
return await JoinProductCategoriesAsync(await GetDbSetAsync(), categoryId);
}
public IQueryable<Product> WithDetails(Guid categoryId)
public virtual async Task<IQueryable<Product>> WithDetailsAsync(Guid categoryId)
{
return JoinProductCategories(WithDetails(), categoryId);
return await JoinProductCategoriesAsync(await WithDetailsAsync(), categoryId);
}
protected virtual IQueryable<Product> JoinProductCategories(IQueryable<Product> queryable, Guid categoryId)
protected virtual async Task<IQueryable<Product>> JoinProductCategoriesAsync(IQueryable<Product> queryable, Guid categoryId)
{
return queryable.Join(
DbContext.ProductCategories.Where(productCategory => productCategory.CategoryId == categoryId),
(await GetDbContextAsync()).ProductCategories.Where(productCategory => productCategory.CategoryId == categoryId),
product => product.Id,
productCategory => productCategory.ProductId,
(product, productCategory) => product

12
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/Products/ProductViewRepository.cs

@ -18,20 +18,20 @@ namespace EasyAbp.EShop.Products.Products
return (await base.GetQueryableAsync()).IncludeDetails();
}
public IQueryable<ProductView> GetQueryable(Guid categoryId)
public virtual async Task<IQueryable<ProductView>> GetQueryableAsync(Guid categoryId)
{
return JoinProductCategories(DbSet, categoryId);
return await JoinProductCategoriesAsync(DbSet, categoryId);
}
public IQueryable<ProductView> WithDetails(Guid categoryId)
public virtual async Task<IQueryable<ProductView>> WithDetailsAsync(Guid categoryId)
{
return JoinProductCategories(WithDetails(), categoryId);
return await JoinProductCategoriesAsync(await WithDetailsAsync(), categoryId);
}
protected virtual IQueryable<ProductView> JoinProductCategories(IQueryable<ProductView> queryable, Guid categoryId)
protected virtual async Task<IQueryable<ProductView>> JoinProductCategoriesAsync(IQueryable<ProductView> queryable, Guid categoryId)
{
return queryable.Join(
DbContext.ProductCategories.Where(productCategory => productCategory.CategoryId == categoryId),
(await GetDbContextAsync()).ProductCategories.Where(productCategory => productCategory.CategoryId == categoryId),
product => product.Id,
productCategory => productCategory.ProductId,
(product, productCategory) => product

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

@ -23,7 +23,7 @@ namespace EasyAbp.EShop.Products.Products
}
[Fact]
public async Task Should_Create_A_Product()
public async Task Should_Create_Product()
{
// Arrange
_eShopProductsOptions.Groups.Configure("Default Group Name", x =>
@ -35,7 +35,7 @@ namespace EasyAbp.EShop.Products.Products
var requestDto = new CreateUpdateProductDto
{
ProductGroupName = "Default Group Name",
ProductDetailId = ProductsTestData.ProductDetails1Id,
ProductDetailId = ProductsTestData.ProductDetails2Id,
StoreId = ProductsTestData.Store1Id,
UniqueName = "Unique Pencil",
DisplayName = "Pencil",
@ -78,9 +78,9 @@ namespace EasyAbp.EShop.Products.Products
}
[Fact]
public async Task Should_Create_A_Sku()
public async Task Should_Create_Skus()
{
await Should_Create_A_Product();
await Should_Create_Product();
Guid productId = default;
Guid productAttributeOptionId = default;
UsingDbContext(db =>
@ -110,6 +110,7 @@ namespace EasyAbp.EShop.Products.Products
response.ShouldNotBeNull();
response.MinimumPrice.ShouldBe(1m);
response.MaximumPrice.ShouldBe(1m);
response.ProductSkus.Count.ShouldBe(1);
var responseSku = response.ProductSkus.First();
@ -119,7 +120,29 @@ namespace EasyAbp.EShop.Products.Products
responseSku.AttributeOptionIds.First().ShouldBe(productAttributeOptionId);
responseSku.OrderMinQuantity.ShouldBe(1);
responseSku.OrderMaxQuantity.ShouldBe(10);
}
[Fact]
public async Task Should_Get_Product_Min_Max_Prices()
{
var getListResult = await _productAppService.GetListAsync(new GetProductListInput
{
StoreId = ProductsTestData.Store1Id
});
getListResult.Items.ShouldNotBeEmpty();
var productDto = getListResult.Items.FirstOrDefault(x => x.Id == ProductsTestData.Product1Id);
productDto.ShouldNotBeNull();
productDto.MinimumPrice.ShouldBe(1m);
productDto.MaximumPrice.ShouldBe(3m);
var getResult = await _productAppService.GetAsync(ProductsTestData.Product1Id);
getResult.ShouldNotBeNull();
getResult.MinimumPrice.ShouldBe(1m);
getResult.MaximumPrice.ShouldBe(3m);
}
}
}

46
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductViewAppServiceTests.cs

@ -0,0 +1,46 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
using Shouldly;
using Xunit;
namespace EasyAbp.EShop.Products.Products
{
public class ProductViewAppServiceTests : ProductsApplicationTestBase
{
private readonly IProductManager _productManager;
private readonly IProductViewAppService _productViewAppService;
public ProductViewAppServiceTests()
{
_productManager = GetRequiredService<IProductManager>();
_productViewAppService = GetRequiredService<IProductViewAppService>();
}
[Fact]
public async Task Should_Get_Product_Min_Max_Prices()
{
var getListResult = await _productViewAppService.GetListAsync(new GetProductListInput
{
StoreId = ProductsTestData.Store1Id
});
getListResult.Items.ShouldNotBeEmpty();
var productDto = getListResult.Items.FirstOrDefault(x => x.Id == ProductsTestData.Product1Id);
productDto.ShouldNotBeNull();
productDto.MinimumPrice.ShouldBe(1m);
productDto.MaximumPrice.ShouldBe(3m);
var getResult = await _productViewAppService.GetAsync(ProductsTestData.Product1Id);
getResult.ShouldNotBeNull();
getResult.MinimumPrice.ShouldBe(1m);
getResult.MaximumPrice.ShouldBe(3m);
}
}
}

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

@ -7,5 +7,23 @@ namespace EasyAbp.EShop.Products
public static Guid Store1Id { get; } = Guid.NewGuid();
public static Guid ProductDetails1Id { get; } = Guid.NewGuid();
public static Guid ProductDetails2Id { get; } = Guid.NewGuid();
public static Guid Product1Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Option1Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Option2Id { get; } = Guid.NewGuid();
public static Guid Product1Attribute1Option3Id { get; } = Guid.NewGuid();
public static Guid Product1Sku1Id { get; } = Guid.NewGuid();
public static Guid Product1Sku2Id { get; } = Guid.NewGuid();
public static Guid Product1Sku3Id { get; } = Guid.NewGuid();
}
}

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

@ -1,17 +1,30 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.Products;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Products
{
public class ProductsTestDataBuilder : ITransientDependency
{
private readonly IProductManager _productManager;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IProductDetailRepository _productDetailRepository;
private readonly IAttributeOptionIdsSerializer _attributeOptionIdsSerializer;
public ProductsTestDataBuilder(IProductDetailRepository productDetailRepository)
public ProductsTestDataBuilder(
IProductManager productManager,
IUnitOfWorkManager unitOfWorkManager,
IProductDetailRepository productDetailRepository,
IAttributeOptionIdsSerializer attributeOptionIdsSerializer)
{
_productManager = productManager;
_unitOfWorkManager = unitOfWorkManager;
_productDetailRepository = productDetailRepository;
_attributeOptionIdsSerializer = attributeOptionIdsSerializer;
}
public void Build()
@ -21,8 +34,49 @@ namespace EasyAbp.EShop.Products
public async Task BuildAsync()
{
await _productDetailRepository.InsertAsync(new ProductDetail(ProductsTestData.ProductDetails1Id, null,
ProductsTestData.Store1Id, "Product details for store 1"));
using var uow = _unitOfWorkManager.Begin();
var productDetail1 = await _productDetailRepository.InsertAsync(
new ProductDetail(ProductsTestData.ProductDetails1Id, null, ProductsTestData.Store1Id,
"Product details for store 1"), true);
var productDetail2 = await _productDetailRepository.InsertAsync(
new ProductDetail(ProductsTestData.ProductDetails2Id, null, ProductsTestData.Store1Id,
"Product details for store 1"), true);
var product = new Product(ProductsTestData.Product1Id, null, ProductsTestData.Store1Id, "Default",
productDetail1.Id, "Cake", "Cake", InventoryStrategy.NoNeed, true, false, false, null, null, 0);
var attribute = new ProductAttribute(ProductsTestData.Product1Attribute1Id, "Size", null);
attribute.ProductAttributeOptions.AddRange(new[]
{
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option1Id, "S", null),
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option2Id, "M", null),
new ProductAttributeOption(ProductsTestData.Product1Attribute1Option3Id, "L", null)
});
product.ProductAttributes.Add(attribute);
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);
var productSku2 = new ProductSku(ProductsTestData.Product1Sku2Id,
await _attributeOptionIdsSerializer.SerializeAsync(new[]
{ ProductsTestData.Product1Attribute1Option2Id }), 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);
await _productManager.CreateSkuAsync(product, productSku1);
await _productManager.CreateSkuAsync(product, productSku2);
await _productManager.CreateSkuAsync(product, productSku3);
await uow.CompleteAsync();
}
}
}
Loading…
Cancel
Save