Browse Source

Merge branch 'dev' into plugins-promotions

pull/240/head
gdlcf88 3 years ago
parent
commit
bbe7f1e07c
  1. 3
      integration/EasyAbp.EShop/test/EasyAbp.EShop.EntityFrameworkCore.Tests/EasyAbp.EShop.EntityFrameworkCore.Tests.csproj
  2. 6
      integration/EasyAbp.EShop/test/EasyAbp.EShop.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopEntityFrameworkCoreTestModule.cs
  3. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/en.json
  4. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderDiscountInfoModel.cs
  5. 3
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests.csproj
  6. 6
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopOrdersEntityFrameworkCoreTestModule.cs
  7. 3
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests.csproj
  8. 6
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopPaymentsEntityFrameworkCoreTestModule.cs
  9. 3
      modules/EasyAbp.EShop.Plugins/test/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests.csproj
  10. 6
      modules/EasyAbp.EShop.Plugins/test/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopPluginsEntityFrameworkCoreTestModule.cs
  11. 30
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductSkuDto.cs
  12. 15
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
  13. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
  14. 50
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductViewAppService.cs
  15. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs
  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/zh-Hans.json
  18. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Localization/Products/zh-Hant.json
  19. 10
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountAmountOverflowException.cs
  20. 45
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountInfoModel.cs
  21. 23
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountsInfoModel.cs
  22. 108
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/HasDiscountsInfoExtensions.cs
  23. 16
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IHasDiscountsInfo.cs
  24. 9
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IHasFullDiscountsInfo.cs
  25. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductSku.cs
  26. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductView.cs
  27. 10
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/InvalidTimePeriodException.cs
  28. 67
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/OrderDiscountPreviewInfoModel.cs
  29. 59
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductDiscountInfoModel.cs
  30. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/ProductsErrorCodes.cs
  31. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductDiscountProvider.cs
  32. 38
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/PriceDataModel.cs
  33. 19
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDiscountContext.cs
  34. 10
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs
  35. 36
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
  36. 20
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/EShopProductsEntityTypeBuilderExtensions.cs
  37. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ProductsDbContext.cs
  38. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ProductsDbContextModelCreatingExtensions.cs
  39. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/AttributeOptionIdsValueComparer.cs
  40. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/AttributeOptionIdsValueConverter.cs
  41. 29
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/DiscountsInfoValueComparer.cs
  42. 24
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/DiscountsInfoValueConverter.cs
  43. 31
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/EShopProductsEntityTypeBuilderExtensions.cs
  44. 45
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/DemoProductDiscountProvider.cs
  45. 43
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductDiscountTests.cs
  46. 8
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductViewAppServiceTests.cs
  47. 3
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.EntityFrameworkCore.Tests/EasyAbp.EShop.Products.EntityFrameworkCore.Tests.csproj
  48. 6
      modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopProductsEntityFrameworkCoreTestModule.cs
  49. 3
      modules/EasyAbp.EShop.Stores/test/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests.csproj
  50. 6
      modules/EasyAbp.EShop.Stores/test/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopStoresEntityFrameworkCoreTestModule.cs
  51. 26
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/BasketItemDto.cs
  52. 59
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs
  53. 29
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/ProductDataModel.cs
  54. 10
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs
  55. 13
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs
  56. 4
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp.EShop.Plugins.Baskets.Domain.Shared.csproj
  57. 23
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs
  58. 28
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItemInfo.cs
  59. 22
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IProductData.cs
  60. 8
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IServerSideBasketItemInfo.cs
  61. 4
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/EShopPluginsBasketsDomainSharedModule.cs
  62. 7
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json
  63. 7
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json
  64. 7
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json
  65. 59
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItem.cs
  66. 2
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/ProductUpdateRecorder.cs
  67. 18
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/BasketsDbContext.cs
  68. 16
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/BasketsDbContextModelCreatingExtensions.cs
  69. 29
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/DiscountsInfoValueComparer.cs
  70. 24
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/DiscountsInfoValueConverter.cs
  71. 21
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/EShopProductsEntityTypeBuilderExtensions.cs
  72. 5
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/Index.cshtml
  73. 5
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/index.js
  74. 6421
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230408062955_ImplementedProductDiscounts.Designer.cs
  75. 109
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230408062955_ImplementedProductDiscounts.cs
  76. 29
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs
  77. 3
      samples/EShopSample/aspnet-core/test/EShopSample.EntityFrameworkCore.Tests/EShopSample.EntityFrameworkCore.Tests.csproj
  78. 6
      samples/EShopSample/aspnet-core/test/EShopSample.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopSampleEntityFrameworkCoreTestModule.cs

3
integration/EasyAbp.EShop/test/EasyAbp.EShop.EntityFrameworkCore.Tests/EasyAbp.EShop.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.EntityFrameworkCore\EasyAbp.EShop.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.TestBase\EasyAbp.EShop.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
integration/EasyAbp.EShop/test/EasyAbp.EShop.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.EntityFrameworkCore
{
[DependsOn(
typeof(EShopTestBaseModule),
typeof(EShopEntityFrameworkCoreModule)
)]
typeof(EShopEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/en.json

@ -61,7 +61,7 @@
"OrderExtraFeeRefundAmount": "Refund amount",
"EasyAbp.EShop.Orders:UnexpectedCurrency": "Only the specified currency {expectedCurrency} is allowed.",
"EasyAbp.EShop.Orders:OrderLineInvalidQuantity": "Invalid quantity {quantity} for product {productId} (SKU: {productSkuId}).",
"EasyAbp.EShop.Orders:DiscountAmountOverflow": "The discount amount overflow.",
"EasyAbp.EShop.Orders:DiscountAmountOverflow": "The discount amount overflows.",
"EasyAbp.EShop.Orders:DuplicateOrderDiscount": "The discount {discountName} (key: {discountKey}) of order line {orderLineId} already exists.",
"EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "The extra fee {extraFeeName} (key: {extraFeeKey}) already exists.",
"EasyAbp.EShop.Orders:InvalidOrderExtraFee": "The extra fee {extraFee} is invalid.",

11
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscountInfoModel.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderDiscountInfoModel.cs

@ -1,4 +1,5 @@
using System;
using EasyAbp.EShop.Products.Products;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Orders.Orders;
@ -18,16 +19,12 @@ public class OrderDiscountInfoModel
public decimal DiscountedAmount { get; set; }
public OrderDiscountInfoModel(
Guid orderLineId,
[NotNull] string name,
[CanBeNull] string key,
[CanBeNull] string displayName,
decimal discountedAmount)
public OrderDiscountInfoModel(Guid orderLineId, [NotNull] string name, [CanBeNull] string key,
[CanBeNull] string displayName, decimal discountedAmount)
{
OrderLineId = orderLineId;
Name = name;
Key = key ?? string.Empty;
Key = key;
DisplayName = displayName;
DiscountedAmount = discountedAmount;
}

3
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.Orders.EntityFrameworkCore\EasyAbp.EShop.Orders.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Orders.TestBase\EasyAbp.EShop.Orders.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopOrdersEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Orders.EntityFrameworkCore
{
[DependsOn(
typeof(EShopOrdersTestBaseModule),
typeof(EShopOrdersEntityFrameworkCoreModule)
)]
typeof(EShopOrdersEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopOrdersEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

3
modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.Payments.EntityFrameworkCore\EasyAbp.EShop.Payments.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Payments.TestBase\EasyAbp.EShop.Payments.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopPaymentsEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Payments.EntityFrameworkCore
{
[DependsOn(
typeof(EShopPaymentsTestBaseModule),
typeof(EShopPaymentsEntityFrameworkCoreModule)
)]
typeof(EShopPaymentsEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopPaymentsEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

3
modules/EasyAbp.EShop.Plugins/test/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.Plugins.EntityFrameworkCore\EasyAbp.EShop.Plugins.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.TestBase\EasyAbp.EShop.Plugins.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
modules/EasyAbp.EShop.Plugins/test/EasyAbp.EShop.Plugins.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopPluginsEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Plugins.EntityFrameworkCore
{
[DependsOn(
typeof(EShopPluginsTestBaseModule),
typeof(EShopPluginsEntityFrameworkCoreModule)
)]
typeof(EShopPluginsEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopPluginsEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

30
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductSkuDto.cs

@ -1,36 +1,40 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Products.Products.Dtos
{
[Serializable]
public class ProductSkuDto : ExtensibleFullAuditedEntityDto<Guid>, IProductSku
public class ProductSkuDto : ExtensibleFullAuditedEntityDto<Guid>, IProductSku, IHasFullDiscountsInfo
{
public List<Guid> AttributeOptionIds { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public decimal? OriginalPrice { get; set; }
public decimal PriceWithoutDiscount { get; set; }
public decimal Price { get; set; }
public decimal DiscountedPrice { get; set; }
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; } = new();
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; } = new();
public int Inventory { get; set; }
public long Sold { get; set; }
public int OrderMinQuantity { get; set; }
public int OrderMaxQuantity { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public string MediaResources { get; set; }
public Guid? ProductDetailId { get; set; }
}
}

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

@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Products.Products.Dtos
{
[Serializable]
public class ProductViewDto : ExtensibleCreationAuditedEntityDto<Guid>, IProductBase, IHasProductGroupDisplayName
public class ProductViewDto : ExtensibleCreationAuditedEntityDto<Guid>, IProductView
{
public Guid StoreId { get; set; }
public string ProductGroupName { get; set; }
public string ProductGroupDisplayName { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }
@ -38,8 +41,16 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public decimal? MaximumPrice { get; set; }
public decimal? MinimumPriceWithoutDiscount { get; set; }
public decimal? MaximumPriceWithoutDiscount { get; set; }
public long Sold { get; set; }
public string ProductGroupDisplayName { get; set; }
public decimal PriceWithoutDiscount { get; set; }
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; } = new();
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; } = new();
}
}

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

@ -312,8 +312,10 @@ namespace EasyAbp.EShop.Products.Products
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku);
productSkuDto.Price = priceDataModel.Price;
productSkuDto.DiscountedPrice = priceDataModel.DiscountedPrice;
productSkuDto.PriceWithoutDiscount = priceDataModel.PriceWithoutDiscount;
productSkuDto.Price = priceDataModel.DiscountedPrice;
productSkuDto.ProductDiscounts = priceDataModel.ProductDiscounts;
productSkuDto.OrderDiscountPreviews = priceDataModel.OrderDiscountPreviews;
}
if (productDto.ProductSkus.Count > 0)

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

@ -9,7 +9,6 @@ using EasyAbp.EShop.Products.Settings;
using EasyAbp.EShop.Stores.Stores;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Uow;
@ -150,23 +149,62 @@ namespace EasyAbp.EShop.Products.Products
}
decimal? min = null, max = null;
decimal? minWithoutDiscount = null, maxWithoutDiscount = null;
var discounts = new DiscountsInfoModel();
foreach (var productSku in product.ProductSkus)
{
var overrideProductDiscounts = false;
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku);
if (min is null || priceDataModel.Price < min.Value)
if (min is null || priceDataModel.DiscountedPrice < min.Value)
{
min = priceDataModel.DiscountedPrice;
overrideProductDiscounts = true;
}
if (max is null || priceDataModel.DiscountedPrice > max.Value)
{
max = priceDataModel.DiscountedPrice;
}
if (minWithoutDiscount is null || priceDataModel.PriceWithoutDiscount < minWithoutDiscount.Value)
{
min = productSku.Price;
minWithoutDiscount = priceDataModel.PriceWithoutDiscount;
}
if (maxWithoutDiscount is null || priceDataModel.PriceWithoutDiscount > maxWithoutDiscount.Value)
{
maxWithoutDiscount = priceDataModel.PriceWithoutDiscount;
}
foreach (var model in priceDataModel.ProductDiscounts)
{
var discount = discounts.FindProductDiscount(model.Name, model.Key);
if (discount is null || overrideProductDiscounts)
{
discounts.AddOrUpdateProductDiscount(new ProductDiscountInfoModel(model.Name, model.Key,
model.DisplayName, model.DiscountedAmount, model.FromTime, model.ToTime));
}
}
if (max is null || priceDataModel.Price > max.Value)
foreach (var model in priceDataModel.OrderDiscountPreviews)
{
max = productSku.Price;
var discount = discounts.FindOrderDiscount(model.Name, model.Key);
if (discount is null)
{
discounts.AddOrUpdateOrderDiscountPreview(new OrderDiscountPreviewInfoModel(model.Name,
model.Key, model.DisplayName, model.MinDiscountedAmount, model.MaxDiscountedAmount,
model.FromTime, model.ToTime));
}
}
}
productView.SetPrices(min, max);
productView.SetPrices(min, max, minWithoutDiscount, maxWithoutDiscount);
productView.SetDiscounts(discounts);
}
}
}

1
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/ProductsApplicationAutoMapperProfile.cs

@ -34,7 +34,6 @@ namespace EasyAbp.EShop.Products
CreateMap<ProductAttributeOption, ProductAttributeOptionDto>();
CreateMap<ProductSku, ProductSkuDto>()
.Ignore(dto => dto.Price)
.Ignore(dto => dto.DiscountedPrice)
.Ignore(dto => dto.Inventory)
.Ignore(dto => dto.Sold);
CreateMap<CreateUpdateProductDetailDto, ProductDetail>(MemberList.Source)

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

@ -98,6 +98,8 @@
"EasyAbp.EShop.Products:ProductAttributesModificationFailed": "Should ensure SKUs are empty if you want to modify attributes of a product.",
"EasyAbp.EShop.Products:StaticProductCannotBeModified": "Cannot modify the static product: {productId}",
"EasyAbp.EShop.Products:StoreIsNotProductOwner": "Store {storeId} is not a owner of the product {productId}",
"EasyAbp.EShop.Products:InventoryInsufficient": "The inventory of the product {productId} (SKU: {productSkuId}) is insufficient, {quantity} are needed, but only {inventory}."
"EasyAbp.EShop.Products:InventoryInsufficient": "The inventory of the product {productId} (SKU: {productSkuId}) is insufficient, {quantity} are needed, but only {inventory}.",
"EasyAbp.EShop.Products:DiscountAmountOverflow": "The discount amount overflows.",
"EasyAbp.EShop.Products:InvalidTimePeriod": "Invalid time period."
}
}

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

@ -95,6 +95,8 @@
"EasyAbp.EShop.Products:ProductAttributesModificationFailed": "请先确保Sku为空再修改商品的属性",
"EasyAbp.EShop.Products:StaticProductCannotBeModified": "不能修改静态商品{productId}",
"EasyAbp.EShop.Products:StoreIsNotProductOwner": "商店{storeId}不是商品{productId}的所有者 ",
"EasyAbp.EShop.Products:InventoryInsufficient": "产品{productId} (SKU: {productSkuId})的库存不足,需要数量{quantity},但是只有数量{inventory}"
"EasyAbp.EShop.Products:InventoryInsufficient": "产品{productId} (SKU: {productSkuId})的库存不足,需要数量{quantity},但是只有数量{inventory}",
"EasyAbp.EShop.Products:DiscountAmountOverflow": "折扣金额溢出",
"EasyAbp.EShop.Products:InvalidTimePeriod": "不正确的时间区间"
}
}

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

@ -95,6 +95,8 @@
"EasyAbp.EShop.Products:ProductAttributesModificationFailed": "請先確保Sku為空再修改商品的屬性",
"EasyAbp.EShop.Products:StaticProductCannotBeModified": "不能修改靜態商品{productId}",
"EasyAbp.EShop.Products:StoreIsNotProductOwner": "商店{storeId}不是商品{productId}的所有者 ",
"EasyAbp.EShop.Products:InventoryInsufficient": "產品{productId} (SKU: {productSkuId})的庫存不足,需要數量{quantity},但是只有數量{inventory}"
"EasyAbp.EShop.Products:InventoryInsufficient": "產品{productId} (SKU: {productSkuId})的庫存不足,需要數量{quantity},但是只有數量{inventory}",
"EasyAbp.EShop.Products:DiscountAmountOverflow": "折扣金額溢出",
"EasyAbp.EShop.Products:InvalidTimePeriod": "不正確的時間區間"
}
}

10
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountAmountOverflowException.cs

@ -0,0 +1,10 @@
using Volo.Abp;
namespace EasyAbp.EShop.Products.Products;
public class DiscountAmountOverflowException : BusinessException
{
public DiscountAmountOverflowException() : base(ProductsErrorCodes.DiscountAmountOverflow)
{
}
}

45
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountInfoModel.cs

@ -0,0 +1,45 @@
using System;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
public abstract class DiscountInfoModel
{
[NotNull]
public string Name { get; set; }
[CanBeNull]
public string Key { get; set; }
[CanBeNull]
public string DisplayName { get; set; }
/// <summary>
/// When the discount begins to take effect.
/// </summary>
public DateTime? FromTime { get; set; }
/// <summary>
/// When the discount ends.
/// </summary>
public DateTime? ToTime { get; set; }
public DiscountInfoModel()
{
}
public DiscountInfoModel([NotNull] string name, [CanBeNull] string key, [CanBeNull] string displayName,
DateTime? fromTime, DateTime? toTime)
{
if (fromTime > toTime)
{
throw new InvalidTimePeriodException();
}
Name = name;
Key = key;
DisplayName = displayName;
FromTime = fromTime;
ToTime = toTime;
}
}

23
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/DiscountsInfoModel.cs

@ -0,0 +1,23 @@
using System.Collections.Generic;
namespace EasyAbp.EShop.Products.Products;
public class DiscountsInfoModel : IHasDiscountsInfo
{
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; }
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; }
public DiscountsInfoModel()
{
ProductDiscounts = new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = new List<OrderDiscountPreviewInfoModel>();
}
public DiscountsInfoModel(List<ProductDiscountInfoModel> productDiscounts,
List<OrderDiscountPreviewInfoModel> orderDiscountPreviews)
{
ProductDiscounts = productDiscounts ?? new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = orderDiscountPreviews ?? new List<OrderDiscountPreviewInfoModel>();
}
}

108
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/HasDiscountsInfoExtensions.cs

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
public static class HasDiscountsInfoExtensions
{
public static decimal GetProductDiscountsDiscountedAmount(this IHasDiscountsInfo hasDiscountsInfo, DateTime now)
{
return hasDiscountsInfo.ProductDiscounts
.Where(x => !x.FromTime.HasValue || x.FromTime <= now)
.Where(x => !x.ToTime.HasValue || now <= x.ToTime)
.Sum(x => x.DiscountedAmount);
}
public static void AddOrUpdateProductDiscount(this IHasDiscountsInfo hasDiscountsInfo,
ProductDiscountInfoModel model)
{
var found = hasDiscountsInfo.FindProductDiscount(model.Name, model.Key);
if (found is null)
{
hasDiscountsInfo.ProductDiscounts.Add(model);
}
else
{
hasDiscountsInfo.ProductDiscounts.ReplaceOne(found, model);
}
hasDiscountsInfo.CheckDiscountedAmount();
}
public static ProductDiscountInfoModel FindProductDiscount(this IHasDiscountsInfo hasDiscountsInfo,
[NotNull] string name, [CanBeNull] string key)
{
return hasDiscountsInfo.ProductDiscounts.Find(x => x.Name == name && x.Key == key);
}
public static bool TryRemoveProductDiscount(this IHasDiscountsInfo hasDiscountsInfo, [NotNull] string name,
[CanBeNull] string key)
{
var found = hasDiscountsInfo.FindProductDiscount(name, key);
if (found is null)
{
return false;
}
hasDiscountsInfo.ProductDiscounts.Remove(found);
hasDiscountsInfo.CheckDiscountedAmount();
return true;
}
public static void AddOrUpdateOrderDiscountPreview(this IHasDiscountsInfo hasDiscountsInfo,
OrderDiscountPreviewInfoModel model)
{
var found = hasDiscountsInfo.FindOrderDiscount(model.Name, model.Key);
if (found is null)
{
hasDiscountsInfo.OrderDiscountPreviews.Add(model);
}
else
{
hasDiscountsInfo.OrderDiscountPreviews.ReplaceOne(found, model);
}
hasDiscountsInfo.CheckDiscountedAmount();
}
public static OrderDiscountPreviewInfoModel FindOrderDiscount(this IHasDiscountsInfo hasDiscountsInfo,
[NotNull] string name, [CanBeNull] string key)
{
return hasDiscountsInfo.OrderDiscountPreviews.Find(x => x.Name == name && x.Key == key);
}
public static bool TryRemoveOrderDiscountPreview(this IHasDiscountsInfo hasDiscountsInfo, [NotNull] string name,
[CanBeNull] string key)
{
var found = hasDiscountsInfo.FindOrderDiscount(name, key);
if (found is null)
{
return false;
}
hasDiscountsInfo.OrderDiscountPreviews.Remove(found);
hasDiscountsInfo.CheckDiscountedAmount();
return true;
}
private static void CheckDiscountedAmount(this IHasDiscountsInfo hasDiscountsInfo)
{
if (hasDiscountsInfo.ProductDiscounts.Any(x => x.DiscountedAmount < decimal.Zero) ||
hasDiscountsInfo.OrderDiscountPreviews.Any(x =>
x.MinDiscountedAmount < decimal.Zero || x.MaxDiscountedAmount < decimal.Zero ||
x.MinDiscountedAmount > x.MaxDiscountedAmount))
{
throw new DiscountAmountOverflowException();
}
}
}

16
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IHasDiscountsInfo.cs

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace EasyAbp.EShop.Products.Products;
public interface IHasDiscountsInfo
{
/// <summary>
/// The Price of the ProductSku has been subtracted from these product discounts.
/// </summary>
List<ProductDiscountInfoModel> ProductDiscounts { get; }
/// <summary>
/// These order discount previews do not change the Price. They will be effective after placing an order.
/// </summary>
List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; }
}

9
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IHasFullDiscountsInfo.cs

@ -0,0 +1,9 @@
namespace EasyAbp.EShop.Products.Products;
public interface IHasFullDiscountsInfo : IHasDiscountsInfo
{
/// <summary>
/// The realtime price without subtracting the discount amount.
/// </summary>
decimal PriceWithoutDiscount { get; }
}

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductSku.cs

@ -14,8 +14,15 @@ namespace EasyAbp.EShop.Products.Products
[NotNull]
string Currency { get; }
/// <summary>
/// The official pricing value.
/// This property is only used for UI.
/// </summary>
decimal? OriginalPrice { get; }
/// <summary>
/// The realtime price.
/// </summary>
decimal Price { get; }
int OrderMinQuantity { get; }

14
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductView.cs

@ -0,0 +1,14 @@
namespace EasyAbp.EShop.Products.Products;
public interface IProductView : IProductBase, IHasDiscountsInfo, IHasProductGroupDisplayName
{
decimal? MinimumPrice { get; }
decimal? MaximumPrice { get; }
decimal? MinimumPriceWithoutDiscount { get; }
decimal? MaximumPriceWithoutDiscount { get; }
long Sold { get; }
}

10
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/InvalidTimePeriodException.cs

@ -0,0 +1,10 @@
using Volo.Abp;
namespace EasyAbp.EShop.Products.Products;
public class InvalidTimePeriodException : BusinessException
{
public InvalidTimePeriodException() : base(ProductsErrorCodes.InvalidTimePeriod)
{
}
}

67
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/OrderDiscountPreviewInfoModel.cs

@ -0,0 +1,67 @@
using System;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
[Serializable]
public class OrderDiscountPreviewInfoModel : DiscountInfoModel, ICloneable
{
public decimal MinDiscountedAmount { get; set; }
public decimal MaxDiscountedAmount { get; set; }
public OrderDiscountPreviewInfoModel()
{
}
public OrderDiscountPreviewInfoModel([NotNull] string name, [CanBeNull] string key, [CanBeNull] string displayName,
decimal minDiscountedAmount, decimal maxDiscountedAmount, DateTime? fromTime, DateTime? toTime) : base(name,
key, displayName, fromTime, toTime)
{
if (minDiscountedAmount < decimal.Zero || maxDiscountedAmount < decimal.Zero ||
minDiscountedAmount > maxDiscountedAmount)
{
throw new DiscountAmountOverflowException();
}
MinDiscountedAmount = minDiscountedAmount;
MaxDiscountedAmount = maxDiscountedAmount;
}
public object Clone()
{
return new OrderDiscountPreviewInfoModel(Name, Key, DisplayName, MinDiscountedAmount, MaxDiscountedAmount,
FromTime, ToTime);
}
public override bool Equals(object obj)
{
return obj is OrderDiscountPreviewInfoModel other && Equals(other);
}
protected bool Equals(OrderDiscountPreviewInfoModel other)
{
return Name == other.Name &&
Key == other.Key &&
DisplayName == other.DisplayName &&
MinDiscountedAmount == other.MinDiscountedAmount &&
MaxDiscountedAmount == other.MaxDiscountedAmount &&
Nullable.Equals(FromTime, other.FromTime) &&
Nullable.Equals(ToTime, other.ToTime);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Name.GetHashCode();
hashCode = (hashCode * 397) ^ (Key != null ? Key.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (DisplayName != null ? DisplayName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ MinDiscountedAmount.GetHashCode();
hashCode = (hashCode * 397) ^ MaxDiscountedAmount.GetHashCode();
hashCode = (hashCode * 397) ^ FromTime.GetHashCode();
hashCode = (hashCode * 397) ^ ToTime.GetHashCode();
return hashCode;
}
}
}

59
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductDiscountInfoModel.cs

@ -0,0 +1,59 @@
using System;
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
[Serializable]
public class ProductDiscountInfoModel : DiscountInfoModel, ICloneable
{
public decimal DiscountedAmount { get; set; }
public ProductDiscountInfoModel()
{
}
public ProductDiscountInfoModel([NotNull] string name, [CanBeNull] string key, [CanBeNull] string displayName,
decimal discountedAmount, DateTime? fromTime, DateTime? toTime) : base(name, key, displayName, fromTime, toTime)
{
if (discountedAmount < decimal.Zero)
{
throw new DiscountAmountOverflowException();
}
DiscountedAmount = discountedAmount;
}
public object Clone()
{
return new ProductDiscountInfoModel(Name, Key, DisplayName, DiscountedAmount, FromTime, ToTime);
}
public override bool Equals(object obj)
{
return obj is ProductDiscountInfoModel other && Equals(other);
}
protected bool Equals(ProductDiscountInfoModel other)
{
return Name == other.Name &&
Key == other.Key &&
DisplayName == other.DisplayName &&
DiscountedAmount == other.DiscountedAmount &&
Nullable.Equals(FromTime, other.FromTime) &&
Nullable.Equals(ToTime, other.ToTime);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Name.GetHashCode();
hashCode = (hashCode * 397) ^ (Key != null ? Key.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (DisplayName != null ? DisplayName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ DiscountedAmount.GetHashCode();
hashCode = (hashCode * 397) ^ FromTime.GetHashCode();
hashCode = (hashCode * 397) ^ ToTime.GetHashCode();
return hashCode;
}
}
}

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

@ -16,5 +16,7 @@
public const string StaticProductCannotBeModified = "EasyAbp.EShop.Products:StaticProductCannotBeModified";
public const string StoreIsNotProductOwner = "EasyAbp.EShop.Products:StoreIsNotProductOwner";
public const string InventoryInsufficient = "EasyAbp.EShop.Products:InventoryInsufficient";
public const string DiscountAmountOverflow = "EasyAbp.EShop.Products:DiscountAmountOverflow";
public const string InvalidTimePeriod = "EasyAbp.EShop.Products:InvalidTimePeriod";
}
}

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

@ -1,10 +1,8 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace EasyAbp.EShop.Products.Products
namespace EasyAbp.EShop.Products.Products;
public interface IProductDiscountProvider
{
public interface IProductDiscountProvider
{
Task<decimal> GetDiscountedPriceAsync(Product product, ProductSku productSku, decimal currentPrice);
}
Task DiscountAsync(ProductDiscountContext context);
}

38
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/PriceDataModel.cs

@ -1,9 +1,37 @@
namespace EasyAbp.EShop.Products.Products
using System;
using System.Collections.Generic;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Products.Products;
public class PriceDataModel : IHasFullDiscountsInfo
{
public class PriceDataModel
public DateTime Now { get; }
public decimal PriceWithoutDiscount { get; }
/// <summary>
/// It's a sum of the amount of product discounts which in effect at the current time (this.<see cref="Now"/>).
/// </summary>
public decimal DiscountedAmount => this.GetProductDiscountsDiscountedAmount(Now);
/// <summary>
/// It has been subtracted from the product discounts which in effect at the current time (this.<see cref="Now"/>).
/// </summary>
public decimal DiscountedPrice => PriceWithoutDiscount - DiscountedAmount;
public List<ProductDiscountInfoModel> ProductDiscounts { get; } = new();
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; } = new();
public PriceDataModel(decimal priceWithoutDiscount, IClock clock)
{
public decimal Price { get; set; }
public decimal DiscountedPrice { get; set; }
if (PriceWithoutDiscount < decimal.Zero)
{
throw new OverflowException();
}
Now = clock.Now;
PriceWithoutDiscount = priceWithoutDiscount;
}
}

19
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDiscountContext.cs

@ -0,0 +1,19 @@
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Products.Products;
public class ProductDiscountContext
{
public Product Product { get; }
public ProductSku ProductSku { get; }
public PriceDataModel PriceDataModel { get; }
public ProductDiscountContext(Product product, ProductSku productSku, decimal priceFromPriceProvider, IClock clock)
{
Product = product;
ProductSku = productSku;
PriceDataModel = new PriceDataModel(priceFromPriceProvider, clock);
}
}

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

@ -272,19 +272,15 @@ namespace EasyAbp.EShop.Products.Products
{
var price = await _productPriceProvider.GetPriceAsync(product, productSku);
var discountedPrice = price;
var context = new ProductDiscountContext(product, productSku, price, Clock);
// Todo: provider execution ordering.
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IProductDiscountProvider>>())
{
discountedPrice = await provider.GetDiscountedPriceAsync(product, productSku, discountedPrice);
await provider.DiscountAsync(context);
}
return new PriceDataModel
{
Price = price,
DiscountedPrice = discountedPrice
};
return context.PriceDataModel;
}
}
}

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

@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Products.Products
{
public class ProductView : CreationAuditedAggregateRoot<Guid>,
IProductBase, IHasProductGroupDisplayName, IMultiTenant
public class ProductView : CreationAuditedAggregateRoot<Guid>, IProductView, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
#region Properties of IProduct
#region Properties of IProductBase
public virtual Guid StoreId { get; protected set; }
@ -43,10 +43,18 @@ namespace EasyAbp.EShop.Products.Products
public virtual string ProductGroupDisplayName { get; protected set; }
public virtual List<ProductDiscountInfoModel> ProductDiscounts { get; protected set; }
public virtual List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; protected set; }
public virtual decimal? MinimumPrice { get; protected set; }
public virtual decimal? MaximumPrice { get; protected set; }
public virtual decimal? MinimumPriceWithoutDiscount { get; protected set; }
public virtual decimal? MaximumPriceWithoutDiscount { get; protected set; }
public virtual long Sold { get; protected set; }
protected ProductView()
@ -71,8 +79,12 @@ namespace EasyAbp.EShop.Products.Products
string mediaResources,
int displayOrder,
string productGroupDisplayName,
List<ProductDiscountInfoModel> productDiscounts,
List<OrderDiscountPreviewInfoModel> orderDiscountPreviews,
decimal? minimumPrice,
decimal? maximumPrice,
decimal? minimumPriceWithoutDiscount,
decimal? maximumPriceWithoutDiscount,
long sold
) : base(id)
{
@ -93,8 +105,12 @@ namespace EasyAbp.EShop.Products.Products
DisplayOrder = displayOrder;
ProductGroupDisplayName = productGroupDisplayName;
ProductDiscounts = productDiscounts ?? new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = orderDiscountPreviews ?? new List<OrderDiscountPreviewInfoModel>();
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
MinimumPriceWithoutDiscount = minimumPriceWithoutDiscount;
MaximumPriceWithoutDiscount = maximumPriceWithoutDiscount;
Sold = sold;
}
@ -103,10 +119,18 @@ namespace EasyAbp.EShop.Products.Products
Sold = sold;
}
public void SetPrices(decimal? minimumPrice, decimal? maximumPrice)
public void SetPrices(decimal? min, decimal? max, decimal? minWithoutDiscount, decimal? maxWithoutDiscount)
{
MinimumPrice = minimumPrice;
MaximumPrice = maximumPrice;
MinimumPrice = min;
MaximumPrice = max;
MinimumPriceWithoutDiscount = minWithoutDiscount;
MaximumPriceWithoutDiscount = maxWithoutDiscount;
}
public void SetDiscounts(IHasDiscountsInfo discountsInfo)
{
ProductDiscounts = discountsInfo.ProductDiscounts ?? new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = discountsInfo.OrderDiscountPreviews ?? new List<OrderDiscountPreviewInfoModel>();
}
}
}

20
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/EShopProductsEntityTypeBuilderExtensions.cs

@ -1,20 +0,0 @@
using System;
using EasyAbp.EShop.Products.EntityFrameworkCore.AttributeOptionIds;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace EasyAbp.EShop.Products.EntityFrameworkCore;
public static class EShopProductsEntityTypeBuilderExtensions
{
public static void TryConfigureAttributeOptionIds(this EntityTypeBuilder b)
{
if (b.Metadata.ClrType.IsAssignableTo<IHasAttributeOptionIds>())
{
b.Property(nameof(IHasAttributeOptionIds.AttributeOptionIds))
.HasConversion<AttributeOptionIdsValueConverter>()
.Metadata.SetValueComparer(new AttributeOptionIdsValueComparer());
}
}
}

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

@ -1,8 +1,10 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Categories;
using EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
using EasyAbp.EShop.Products.ProductCategories;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductHistories;
@ -41,5 +43,15 @@ namespace EasyAbp.EShop.Products.EntityFrameworkCore
builder.ConfigureEShopProducts();
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
configurationBuilder.Properties<List<ProductDiscountInfoModel>>()
.HaveConversion<ProductDiscountsInfoValueConverter>();
configurationBuilder.Properties<List<OrderDiscountPreviewInfoModel>>()
.HaveConversion<OrderDiscountPreviewsInfoValueConverter>();
}
}
}

6
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ProductsDbContextModelCreatingExtensions.cs

@ -5,6 +5,7 @@ using EasyAbp.EShop.Products.ProductCategories;
using EasyAbp.EShop.Products.Categories;
using EasyAbp.EShop.Products.Products;
using System;
using EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
using EasyAbp.EShop.Products.ProductDetails;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
@ -133,7 +134,8 @@ namespace EasyAbp.EShop.Products.EntityFrameworkCore
builder.Entity<ProductView>(b =>
{
b.ToTable(options.TablePrefix + "ProductViews", options.Schema);
b.ConfigureByConvention();
b.ConfigureByConvention();
b.TryConfigureDiscountsInfo();
/* Configure more properties here */
@ -141,6 +143,8 @@ namespace EasyAbp.EShop.Products.EntityFrameworkCore
b.Property(x => x.MinimumPrice).HasColumnType("decimal(20,8)");
b.Property(x => x.MaximumPrice).HasColumnType("decimal(20,8)");
b.Property(x => x.MinimumPriceWithoutDiscount).HasColumnType("decimal(20,8)");
b.Property(x => x.MaximumPriceWithoutDiscount).HasColumnType("decimal(20,8)");
});
}
}

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/AttributeOptionIds/AttributeOptionIdsValueComparer.cs → modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/AttributeOptionIdsValueComparer.cs

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.AttributeOptionIds;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
public class AttributeOptionIdsValueComparer : ValueComparer<List<Guid>>
{

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/AttributeOptionIds/AttributeOptionIdsValueConverter.cs → modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/AttributeOptionIdsValueConverter.cs

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.AttributeOptionIds;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
public class AttributeOptionIdsValueConverter : ValueConverter<List<Guid>, string>
{

29
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/DiscountsInfoValueComparer.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
public class ProductDiscountsInfoValueComparer : ValueComparer<List<ProductDiscountInfoModel>>
{
public ProductDiscountsInfoValueComparer()
: base(
(d1, d2) => d1.SequenceEqual(d2),
d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())),
d => d.Select(x => (ProductDiscountInfoModel)x.Clone()).ToList())
{
}
}
public class OrderDiscountPreviewsInfoValueComparer : ValueComparer<List<OrderDiscountPreviewInfoModel>>
{
public OrderDiscountPreviewsInfoValueComparer()
: base(
(d1, d2) => d1.SequenceEqual(d2),
d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())),
d => new List<OrderDiscountPreviewInfoModel>(d))
{
}
}

24
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/DiscountsInfoValueConverter.cs

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Text.Json;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
public class ProductDiscountsInfoValueConverter : ValueConverter<List<ProductDiscountInfoModel>, string>
{
public ProductDiscountsInfoValueConverter() : base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<ProductDiscountInfoModel>>(v, (JsonSerializerOptions)null))
{
}
}
public class OrderDiscountPreviewsInfoValueConverter : ValueConverter<List<OrderDiscountPreviewInfoModel>, string>
{
public OrderDiscountPreviewsInfoValueConverter() : base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<OrderDiscountPreviewInfoModel>>(v, (JsonSerializerOptions)null))
{
}
}

31
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.EntityFrameworkCore/EasyAbp/EShop/Products/EntityFrameworkCore/ValueMappings/EShopProductsEntityTypeBuilderExtensions.cs

@ -0,0 +1,31 @@
using System;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace EasyAbp.EShop.Products.EntityFrameworkCore.ValueMappings;
public static class EShopProductsEntityTypeBuilderExtensions
{
public static void TryConfigureAttributeOptionIds(this EntityTypeBuilder b)
{
if (b.Metadata.ClrType.IsAssignableTo<IHasAttributeOptionIds>())
{
b.Property(nameof(IHasAttributeOptionIds.AttributeOptionIds))
.HasConversion<AttributeOptionIdsValueConverter>()
.Metadata.SetValueComparer(new AttributeOptionIdsValueComparer());
}
}
public static void TryConfigureDiscountsInfo(this EntityTypeBuilder b)
{
if (b.Metadata.ClrType.IsAssignableTo<IHasDiscountsInfo>())
{
b.Property(nameof(IHasDiscountsInfo.ProductDiscounts))
.HasConversion<ProductDiscountsInfoValueConverter>()
.Metadata.SetValueComparer(new ProductDiscountsInfoValueComparer());
b.Property(nameof(IHasDiscountsInfo.OrderDiscountPreviews))
.HasConversion<OrderDiscountPreviewsInfoValueConverter>()
.Metadata.SetValueComparer(new OrderDiscountPreviewsInfoValueComparer());
}
}
}

45
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/DemoProductDiscountProvider.cs

@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Products.Products;
public class DemoProductDiscountProvider : IProductDiscountProvider
{
private readonly IClock _clock;
public DemoProductDiscountProvider(IClock clock)
{
_clock = clock;
}
public Task DiscountAsync(ProductDiscountContext context)
{
if (context.Product.Id != ProductsTestData.Product1Id ||
context.ProductSku.Id != ProductsTestData.Product1Sku1Id)
{
return Task.CompletedTask;
}
context.PriceDataModel.ProductDiscounts.AddRange(new List<ProductDiscountInfoModel>
{
// These should affect:
new("DemoDiscount", "1", "Demo Discount 1", 0.01m, null, null),
new("DemoDiscount", "2", "Demo Discount 2", 0.01m, _clock.Now.AddDays(-1), null),
new("DemoDiscount", "3", "Demo Discount 3", 0.01m, null, _clock.Now.AddDays(1)),
new("DemoDiscount", "4", "Demo Discount 4", 0.01m, _clock.Now.AddDays(-1), _clock.Now.AddDays(1)),
// These should not affect: 0.01m,
new("DemoDiscount", "5", "Demo Discount 5", 0.01m, null, _clock.Now.AddDays(-1)),
new("DemoDiscount", "6", "Demo Discount 6", 0.01m, _clock.Now.AddDays(1), null),
new("DemoDiscount", "7", "Demo Discount 7", 0.01m, _clock.Now.AddDays(1), _clock.Now.AddDays(2)),
});
context.PriceDataModel.OrderDiscountPreviews.AddRange(new List<OrderDiscountPreviewInfoModel>
{
new("DemoDiscount", "1", "Demo Discount 1", 0.01m, 0.01m, null, null),
new("DemoDiscount", "2", "Demo Discount 2", 0.01m, 0.01m, _clock.Now.AddDays(-1), _clock.Now.AddDays(1)),
});
return Task.CompletedTask;
}
}

43
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Application.Tests/Products/ProductDiscountTests.cs

@ -0,0 +1,43 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
namespace EasyAbp.EShop.Products.Products;
public class ProductDiscountTests : ProductsApplicationTestBase
{
protected override void AfterAddApplication(IServiceCollection services)
{
services.AddTransient<IProductDiscountProvider, DemoProductDiscountProvider>();
base.AfterAddApplication(services);
}
[Fact]
public async Task Should_Get_Product_With_Discount()
{
var productAppService = GetRequiredService<IProductAppService>();
var product1 = await productAppService.GetAsync(ProductsTestData.Product1Id);
var sku1 = (ProductSkuDto)product1.GetSkuById(ProductsTestData.Product1Sku1Id);
var sku2 = (ProductSkuDto)product1.GetSkuById(ProductsTestData.Product1Sku2Id);
sku1.Price.ShouldBe(1m - 0.01m * 4);
sku1.ProductDiscounts.Count.ShouldBe(7);
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "1");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "2");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "3");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "4");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "5");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "6");
sku1.ProductDiscounts.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "7");
sku1.OrderDiscountPreviews.Count.ShouldBe(2);
sku1.OrderDiscountPreviews.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "1");
sku1.OrderDiscountPreviews.ShouldContain(x => x.Name == "DemoDiscount" && x.Key == "2");
sku2.Price.ShouldBe(2m);
sku2.ProductDiscounts.ShouldBeEmpty();
sku2.OrderDiscountPreviews.ShouldBeEmpty();
}
}

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

@ -31,14 +31,14 @@ namespace EasyAbp.EShop.Products.Products
var productDto = getListResult.Items.FirstOrDefault(x => x.Id == ProductsTestData.Product1Id);
productDto.ShouldNotBeNull();
productDto.MinimumPrice.ShouldBe(1m);
productDto.MaximumPrice.ShouldBe(3m);
productDto.MinimumPriceWithoutDiscount.ShouldBe(1m);
productDto.MaximumPriceWithoutDiscount.ShouldBe(3m);
var getResult = await _productViewAppService.GetAsync(ProductsTestData.Product1Id);
getResult.ShouldNotBeNull();
getResult.MinimumPrice.ShouldBe(1m);
getResult.MaximumPrice.ShouldBe(3m);
getResult.MinimumPriceWithoutDiscount.ShouldBe(1m);
getResult.MaximumPriceWithoutDiscount.ShouldBe(3m);
}

3
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.EntityFrameworkCore.Tests/EasyAbp.EShop.Products.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.Products.EntityFrameworkCore\EasyAbp.EShop.Products.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Products.TestBase\EasyAbp.EShop.Products.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopProductsEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Products.EntityFrameworkCore
{
[DependsOn(
typeof(ProductsTestBaseModule),
typeof(EShopProductsEntityFrameworkCoreModule)
)]
typeof(EShopProductsEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopProductsEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

3
modules/EasyAbp.EShop.Stores/test/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests.csproj

@ -7,11 +7,10 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
<ProjectReference Include="..\..\src\EasyAbp.EShop.Stores.EntityFrameworkCore\EasyAbp.EShop.Stores.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Stores.TestBase\EasyAbp.EShop.Stores.TestBase.csproj" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
modules/EasyAbp.EShop.Stores/test/EasyAbp.EShop.Stores.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopStoresEntityFrameworkCoreTestModule.cs

@ -3,14 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Stores.EntityFrameworkCore
{
[DependsOn(
typeof(EShopStoresTestBaseModule),
typeof(EShopStoresEntityFrameworkCoreModule)
)]
typeof(EShopStoresEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopStoresEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

26
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/BasketItemDto.cs

@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems.Dtos
{
[Serializable]
public class BasketItemDto : ExtensibleAuditedEntityDto<Guid>
public class BasketItemDto : ExtensibleAuditedEntityDto<Guid>, IServerSideBasketItemInfo
{
public string BasketName { get; set; }
public Guid UserId { get; set; }
public Guid StoreId { get; set; }
public Guid ProductId { get; set; }
@ -18,26 +20,28 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems.Dtos
public int Quantity { get; set; }
public decimal PriceWithoutDiscount { get; set; }
public decimal TotalPriceWithoutDiscount { get; set; }
public string MediaResources { get; set; }
public string ProductUniqueName { get; set; }
public string ProductDisplayName { get; set; }
public string SkuName { get; set; }
public string SkuDescription { get; set; }
public string Currency { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
public decimal TotalDiscount { get; set; }
public int Inventory { get; set; }
public bool IsInvalid { get; set; }
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; }
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; }
}
}

59
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/Dtos/ClientSideBasketItemModel.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
using JetBrains.Annotations;
using Volo.Abp.ObjectExtending;
@ -8,71 +10,80 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems.Dtos;
public class ClientSideBasketItemModel : ExtensibleObject, IBasketItem
{
public Guid Id { get; set; }
public string BasketName { get; set; }
public int Quantity { get; set; }
public Guid StoreId { get; set; }
public Guid ProductId { get; set; }
public string ProductUniqueName { get; set; }
public string ProductDisplayName { get; set; }
public Guid ProductSkuId { get; set; }
public string SkuName { get; set; }
public string SkuDescription { get; set; }
public string MediaResources { get; set; }
public string Currency { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
public decimal TotalDiscount { get; set; }
public decimal PriceWithoutDiscount { get; set; }
public decimal TotalPriceWithoutDiscount { get; set; }
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; }
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; }
public int Inventory { get; set; }
public bool IsInvalid { get; set; }
public ClientSideBasketItemModel()
{
}
public ClientSideBasketItemModel(
Guid id,
[NotNull] string basketName,
Guid storeId,
Guid productId,
Guid productSkuId)
Guid productSkuId,
List<ProductDiscountInfoModel> productDiscounts,
List<OrderDiscountPreviewInfoModel> orderDiscountPreviews)
{
Id = id;
BasketName = basketName;
StoreId = storeId;
ProductId = productId;
ProductSkuId = productSkuId;
ProductDiscounts = productDiscounts ?? new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = orderDiscountPreviews ?? new List<OrderDiscountPreviewInfoModel>();
}
public void SetIsInvalid(bool isInvalid)
{
IsInvalid = isInvalid;
}
public void UpdateProductData(int quantity, IProductData productData)
public void Update(int quantity, IProductData productData)
{
Quantity = quantity;
MediaResources = productData.MediaResources;
ProductUniqueName = productData.ProductUniqueName;
ProductDisplayName = productData.ProductDisplayName;
SkuName = productData.SkuName;
SkuDescription = productData.SkuDescription;
Currency = productData.Currency;
UnitPrice = productData.UnitPrice;
TotalPrice = productData.TotalPrice;
TotalDiscount = productData.TotalDiscount;
PriceWithoutDiscount = productData.PriceWithoutDiscount;
TotalPriceWithoutDiscount = productData.PriceWithoutDiscount * quantity;
Inventory = productData.Inventory;
}
}

29
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application.Contracts/EasyAbp/EShop/Plugins/Baskets/BasketItems/ProductDataModel.cs

@ -1,25 +1,28 @@
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
{
public class ProductDataModel : IProductData
{
public string MediaResources { get; set; }
public string ProductUniqueName { get; set; }
public string ProductDisplayName { get; set; }
public string SkuName { get; set; }
public string SkuDescription { get; set; }
public string Currency { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
public decimal TotalDiscount { get; set; }
public decimal PriceWithoutDiscount { get; set; }
public List<ProductDiscountInfoModel> ProductDiscounts { get; set; } = new();
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; set; } = new();
public int Inventory { get; set; }
}
}

10
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
@ -35,7 +37,7 @@ public class BasicBasketItemProductInfoUpdater : IBasketItemProductInfoUpdater,
return;
}
item.UpdateProductData(targetQuantity, new ProductDataModel
item.Update(targetQuantity, new ProductDataModel
{
MediaResources = productSkuDto.MediaResources ?? productDto.MediaResources,
ProductUniqueName = productDto.UniqueName,
@ -43,9 +45,9 @@ public class BasicBasketItemProductInfoUpdater : IBasketItemProductInfoUpdater,
SkuName = productSkuDto.Name,
SkuDescription = await ProductSkuDescriptionProvider.GenerateAsync(productDto, productSkuDto),
Currency = productSkuDto.Currency,
UnitPrice = productSkuDto.DiscountedPrice,
TotalPrice = productSkuDto.DiscountedPrice * item.Quantity,
TotalDiscount = (productSkuDto.Price - productSkuDto.DiscountedPrice) * item.Quantity,
PriceWithoutDiscount = productSkuDto.PriceWithoutDiscount,
ProductDiscounts = productSkuDto.ProductDiscounts,
OrderDiscountPreviews = productSkuDto.OrderDiscountPreviews,
Inventory = productSkuDto.Inventory
});

13
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItemAppService.cs

@ -179,7 +179,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
return await MapToGetOutputDtoAsync(item);
}
var productSkuDto = productDto.FindSkuById(input.ProductSkuId);
var productSkuDto = (ProductSkuDto)productDto.FindSkuById(input.ProductSkuId);
if (productSkuDto == null)
{
@ -187,8 +187,9 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
}
item = new BasketItem(GuidGenerator.Create(), CurrentTenant.Id, input.BasketName, CurrentUser.GetId(),
productDto.StoreId, input.ProductId, input.ProductSkuId);
productDto.StoreId, input.ProductId, input.ProductSkuId, productSkuDto.ProductDiscounts,
productSkuDto.OrderDiscountPreviews);
input.MapExtraPropertiesTo(item);
await UpdateProductDataAsync(input.Quantity, item, productDto);
@ -269,7 +270,7 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
var productDto = products[dto.ProductId];
var productSkuDto = productDto.FindSkuById(dto.ProductSkuId);
var productSkuDto = (ProductSkuDto)productDto.FindSkuById(dto.ProductSkuId);
if (productSkuDto == null)
{
@ -278,8 +279,8 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
var id = dto.Id ?? GuidGenerator.Create();
var item = new ClientSideBasketItemModel(id, dto.BasketName, productDto.StoreId,
dto.ProductId, dto.ProductSkuId);
var item = new ClientSideBasketItemModel(id, dto.BasketName, productDto.StoreId, dto.ProductId,
dto.ProductSkuId, productSkuDto.ProductDiscounts, productSkuDto.OrderDiscountPreviews);
await UpdateProductDataAsync(dto.Quantity, item, productDto);

4
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp.EShop.Plugins.Baskets.Domain.Shared.csproj

@ -20,5 +20,9 @@
<EmbeddedResource Include="EasyAbp\EShop\Plugins\Baskets\Localization\*.json" />
<Content Remove="EasyAbp\EShop\Plugins\Baskets\Localization\*.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Domain.Shared\EasyAbp.EShop.Products.Domain.Shared.csproj" />
</ItemGroup>
</Project>

23
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItem.cs

@ -1,27 +1,8 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems;
public interface IBasketItem : IProductData, IHasExtraProperties
public interface IBasketItem : IBasketItemInfo
{
Guid Id { get; }
[NotNull]
string BasketName { get; }
Guid StoreId { get; }
Guid ProductId { get; }
Guid ProductSkuId { get; }
int Quantity { get; }
bool IsInvalid { get; }
void SetIsInvalid(bool isInvalid);
void UpdateProductData(int quantity, IProductData productData);
void Update(int quantity, IProductData productData);
}

28
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IBasketItemInfo.cs

@ -0,0 +1,28 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems;
public interface IBasketItemInfo : IProductData, IHasExtraProperties
{
Guid Id { get; }
[NotNull]
string BasketName { get; }
Guid StoreId { get; }
Guid ProductId { get; }
Guid ProductSkuId { get; }
int Quantity { get; }
/// <summary>
/// PriceWithoutDiscount * Quantity
/// </summary>
decimal TotalPriceWithoutDiscount { get; }
bool IsInvalid { get; }
}

22
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IProductData.cs

@ -1,25 +1,21 @@
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
{
public interface IProductData
public interface IProductData : IHasFullDiscountsInfo
{
string MediaResources { get; }
string ProductUniqueName { get; }
string ProductDisplayName { get; }
string SkuName { get; }
string SkuDescription { get; }
string Currency { get; }
decimal UnitPrice { get; }
decimal TotalPrice { get; }
decimal TotalDiscount { get; }
int Inventory { get; }
}
}

8
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/BasketItems/IServerSideBasketItemInfo.cs

@ -0,0 +1,8 @@
using System;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems;
public interface IServerSideBasketItemInfo : IBasketItemInfo
{
Guid UserId { get; }
}

4
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/EShopPluginsBasketsDomainSharedModule.cs

@ -1,6 +1,7 @@
using Volo.Abp.Modularity;
using Volo.Abp.Localization;
using EasyAbp.EShop.Plugins.Baskets.Localization;
using EasyAbp.EShop.Products;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Validation;
using Volo.Abp.Validation.Localization;
@ -9,7 +10,8 @@ using Volo.Abp.VirtualFileSystem;
namespace EasyAbp.EShop.Plugins.Baskets
{
[DependsOn(
typeof(AbpValidationModule)
typeof(AbpValidationModule),
typeof(EShopProductsDomainSharedModule)
)]
public class EShopPluginsBasketsDomainSharedModule : AbpModule
{

7
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/en.json

@ -12,17 +12,18 @@
"BasketItemProductId": "Product ID",
"BasketItemProductSkuId": "Product SKU ID",
"BasketItemQuantity": "Quantity",
"PriceWithoutDiscount": "Price without discount",
"TotalPriceWithoutDiscount": "Total price without discount",
"BasketItemMediaResources": "Media resources",
"BasketItemProductUniqueName": "Product unique name",
"BasketItemProductDisplayName": "Product display name",
"BasketItemSkuName": "SKU name",
"BasketItemSkuDescription": "SKU description",
"BasketItemCurrency": "Currency",
"BasketItemUnitPrice": "Unit price",
"BasketItemTotalPrice": "Total price",
"BasketItemTotalDiscount": "Total discount",
"BasketItemInventory": "Inventory",
"BasketItemIsInvalid": "Invalid",
"ProductDiscounts": "Product discounts",
"OrderDiscountPreviews": "Order discount previews",
"CreateBasketItem": "New",
"EditBasketItem": "Edit",
"BasketItemDeletionConfirmationMessage": "Are you sure to delete the basket item {0}?",

7
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hans.json

@ -12,17 +12,18 @@
"BasketItemProductId": "商品 ID",
"BasketItemProductSkuId": "商品 SKU ID",
"BasketItemQuantity": "数量",
"PriceWithoutDiscount": "单价(不含折扣)",
"TotalPriceWithoutDiscount": "总价(不含折扣)",
"BasketItemMediaResources": "多媒体资源",
"BasketItemProductUniqueName": "商品编号",
"BasketItemProductDisplayName": "商品名称",
"BasketItemSkuName": "SKU 编号",
"BasketItemSkuDescription": "SKU 描述",
"BasketItemCurrency": "币种",
"BasketItemUnitPrice": "单价",
"BasketItemTotalPrice": "总价",
"BasketItemTotalDiscount": "总折扣",
"BasketItemInventory": "库存数",
"BasketItemIsInvalid": "是否不可用",
"ProductDiscounts": "商品折扣",
"OrderDiscountPreviews": "订单折扣(预览)",
"CreateBasketItem": "新建",
"EditBasketItem": "编辑",
"BasketItemDeletionConfirmationMessage": "确认删除购物车项 {0}?",

7
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain.Shared/EasyAbp/EShop/Plugins/Baskets/Localization/zh-Hant.json

@ -12,17 +12,18 @@
"BasketItemProductId": "商品 ID",
"BasketItemProductSkuId": "商品 SKU ID",
"BasketItemQuantity": "數量",
"PriceWithoutDiscount": "單價(不含折扣)",
"TotalPriceWithoutDiscount": "總價(不含折扣)",
"BasketItemMediaResources": "多媒體資源",
"BasketItemProductUniqueName": "商品編號",
"BasketItemProductDisplayName": "商品名稱",
"BasketItemSkuName": "SKU 編號",
"BasketItemSkuDescription": "SKU 描述",
"BasketItemCurrency": "幣種",
"BasketItemUnitPrice": "單價",
"BasketItemTotalPrice": "總價",
"BasketItemTotalDiscount": "總折扣",
"BasketItemInventory": "庫存數",
"BasketItemIsInvalid": "是否不可用",
"ProductDiscounts": "商品折扣",
"OrderDiscountPreviews": "訂單折扣(預覽)",
"CreateBasketItem": "新建",
"EditBasketItem": "編輯",
"BasketItemDeletionConfirmationMessage": "確認刪除購物車項 {0}?",

59
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasketItem.cs

@ -1,32 +1,38 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
{
public class BasketItem : AuditedAggregateRoot<Guid>, IBasketItem, IMultiTenant
public class BasketItem : AuditedAggregateRoot<Guid>, IBasketItem, IServerSideBasketItemInfo, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual string BasketName { get; protected set; }
public virtual Guid UserId { get; protected set; }
public virtual Guid StoreId { get; protected set; }
public virtual Guid ProductId { get; protected set; }
public virtual Guid ProductSkuId { get; protected set; }
public virtual int Quantity { get; protected set; }
public virtual decimal PriceWithoutDiscount { get; protected set; }
public virtual decimal TotalPriceWithoutDiscount { get; protected set; }
[CanBeNull]
public virtual string MediaResources { get; protected set; }
[CanBeNull]
public virtual string ProductUniqueName { get; protected set; }
[NotNull]
public virtual string ProductDisplayName { get; protected set; }
@ -38,21 +44,19 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
[NotNull]
public virtual string Currency { get; protected set; }
public virtual decimal UnitPrice { get; protected set; }
public virtual decimal TotalPrice { get; protected set; }
public virtual decimal TotalDiscount { get; protected set; }
public virtual int Inventory { get; protected set; }
public virtual bool IsInvalid { get; protected set; }
public virtual List<ProductDiscountInfoModel> ProductDiscounts { get; protected set; }
public virtual List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; protected set; }
protected BasketItem()
{
}
public BasketItem(
Guid id,
Guid? tenantId,
@ -60,7 +64,9 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
Guid userId,
Guid storeId,
Guid productId,
Guid productSkuId) : base(id)
Guid productSkuId,
List<ProductDiscountInfoModel> productDiscounts,
List<OrderDiscountPreviewInfoModel> orderDiscountPreviews) : base(id)
{
TenantId = tenantId;
BasketName = basketName;
@ -68,21 +74,22 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
StoreId = storeId;
ProductId = productId;
ProductSkuId = productSkuId;
ProductDiscounts = productDiscounts ?? new List<ProductDiscountInfoModel>();
OrderDiscountPreviews = orderDiscountPreviews ?? new List<OrderDiscountPreviewInfoModel>();
}
public void UpdateProductData(int quantity, IProductData productData)
public void Update(int quantity, IProductData productData)
{
Quantity = quantity;
MediaResources = productData.MediaResources;
ProductUniqueName = productData.ProductUniqueName;
ProductDisplayName = productData.ProductDisplayName;
SkuName = productData.SkuName;
SkuDescription = productData.SkuDescription;
Currency = productData.Currency;
UnitPrice = productData.UnitPrice;
TotalPrice = productData.TotalPrice;
TotalDiscount = productData.TotalDiscount;
PriceWithoutDiscount = productData.PriceWithoutDiscount;
TotalPriceWithoutDiscount = productData.PriceWithoutDiscount * quantity;
Inventory = productData.Inventory;
}
@ -91,4 +98,4 @@ namespace EasyAbp.EShop.Plugins.Baskets.BasketItems
IsInvalid = isInvalid;
}
}
}
}

2
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Domain/EasyAbp/EShop/Plugins/Baskets/ProductUpdates/ProductUpdateRecorder.cs

@ -16,6 +16,8 @@ namespace EasyAbp.EShop.Plugins.Baskets.ProductUpdates
IProductUpdateRecorder,
IDistributedEventHandler<EntityUpdatedEto<ProductEto>>,
IDistributedEventHandler<ProductInventoryChangedEto>,
// todo: while discount changed
// todo: manually refresh event
ITransientDependency
{
private readonly IGuidGenerator _guidGenerator;

18
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/BasketsDbContext.cs

@ -1,8 +1,11 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using EasyAbp.EShop.Plugins.Baskets.BasketItems;
using EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore.ValueMappings;
using EasyAbp.EShop.Plugins.Baskets.ProductUpdates;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore
{
@ -15,10 +18,9 @@ namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore
public DbSet<BasketItem> BasketItems { get; set; }
public DbSet<ProductUpdate> ProductUpdates { get; set; }
public BasketsDbContext(DbContextOptions<BasketsDbContext> options)
public BasketsDbContext(DbContextOptions<BasketsDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
@ -27,5 +29,15 @@ namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore
builder.ConfigureEShopPluginsBaskets();
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
configurationBuilder.Properties<List<ProductDiscountInfoModel>>()
.HaveConversion<ProductDiscountsInfoValueConverter>();
configurationBuilder.Properties<List<OrderDiscountPreviewInfoModel>>()
.HaveConversion<OrderDiscountPreviewsInfoValueConverter>();
}
}
}
}

16
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/BasketsDbContextModelCreatingExtensions.cs

@ -1,6 +1,8 @@
using EasyAbp.EShop.Plugins.Baskets.BasketItems;
using System;
using EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore.ValueMappings;
using EasyAbp.EShop.Plugins.Baskets.ProductUpdates;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
@ -46,26 +48,26 @@ namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore
builder.Entity<BasketItem>(b =>
{
b.ToTable(options.TablePrefix + "BasketItems", options.Schema);
b.ConfigureByConvention();
b.ConfigureByConvention();
b.TryConfigureDiscountsInfo();
/* Configure more properties here */
b.HasIndex(x => x.UserId);
b.Property(x => x.UnitPrice).HasColumnType("decimal(20,8)");
b.Property(x => x.TotalPrice).HasColumnType("decimal(20,8)");
b.Property(x => x.TotalDiscount).HasColumnType("decimal(20,8)");
b.Property(x => x.PriceWithoutDiscount).HasColumnType("decimal(20,8)");
b.Property(x => x.TotalPriceWithoutDiscount).HasColumnType("decimal(20,8)");
});
builder.Entity<ProductUpdate>(b =>
{
b.ToTable(options.TablePrefix + "ProductUpdates", options.Schema);
b.ConfigureByConvention();
b.ConfigureByConvention();
/* Configure more properties here */
b.HasIndex(x => x.ProductSkuId);
});
}
}
}
}

29
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/DiscountsInfoValueComparer.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore.ValueMappings;
public class ProductDiscountsInfoValueComparer : ValueComparer<List<ProductDiscountInfoModel>>
{
public ProductDiscountsInfoValueComparer()
: base(
(d1, d2) => d1.SequenceEqual(d2),
d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())),
d => d.Select(x => (ProductDiscountInfoModel)x.Clone()).ToList())
{
}
}
public class OrderDiscountPreviewsInfoValueComparer : ValueComparer<List<OrderDiscountPreviewInfoModel>>
{
public OrderDiscountPreviewsInfoValueComparer()
: base(
(d1, d2) => d1.SequenceEqual(d2),
d => d.Aggregate(0, (k, v) => HashCode.Combine(k, v.GetHashCode())),
d => new List<OrderDiscountPreviewInfoModel>(d))
{
}
}

24
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/DiscountsInfoValueConverter.cs

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Text.Json;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore.ValueMappings;
public class ProductDiscountsInfoValueConverter : ValueConverter<List<ProductDiscountInfoModel>, string>
{
public ProductDiscountsInfoValueConverter() : base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<ProductDiscountInfoModel>>(v, (JsonSerializerOptions)null))
{
}
}
public class OrderDiscountPreviewsInfoValueConverter : ValueConverter<List<OrderDiscountPreviewInfoModel>, string>
{
public OrderDiscountPreviewsInfoValueConverter() : base(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<OrderDiscountPreviewInfoModel>>(v, (JsonSerializerOptions)null))
{
}
}

21
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore/EasyAbp/EShop/Plugins/Baskets/EntityFrameworkCore/ValueMappings/EShopProductsEntityTypeBuilderExtensions.cs

@ -0,0 +1,21 @@
using System;
using EasyAbp.EShop.Products.Products;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace EasyAbp.EShop.Plugins.Baskets.EntityFrameworkCore.ValueMappings;
public static class EShopProductsEntityTypeBuilderExtensions
{
public static void TryConfigureDiscountsInfo(this EntityTypeBuilder b)
{
if (b.Metadata.ClrType.IsAssignableTo<IHasDiscountsInfo>())
{
b.Property(nameof(IHasDiscountsInfo.ProductDiscounts))
.HasConversion<ProductDiscountsInfoValueConverter>()
.Metadata.SetValueComparer(new ProductDiscountsInfoValueComparer());
b.Property(nameof(IHasDiscountsInfo.OrderDiscountPreviews))
.HasConversion<OrderDiscountPreviewsInfoValueConverter>()
.Metadata.SetValueComparer(new OrderDiscountPreviewsInfoValueComparer());
}
}
}

5
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/Index.cshtml

@ -54,15 +54,14 @@
<th>@L["BasketItemProductId"]</th>
<th>@L["BasketItemProductSkuId"]</th>
<th>@L["BasketItemQuantity"]</th>
<th>@L["PriceWithoutDiscount"]</th>
<th>@L["TotalPriceWithoutDiscount"]</th>
<th>@L["BasketItemMediaResources"]</th>
<th>@L["BasketItemProductUniqueName"]</th>
<th>@L["BasketItemProductDisplayName"]</th>
<th>@L["BasketItemSkuName"]</th>
<th>@L["BasketItemSkuDescription"]</th>
<th>@L["BasketItemCurrency"]</th>
<th>@L["BasketItemUnitPrice"]</th>
<th>@L["BasketItemTotalPrice"]</th>
<th>@L["BasketItemTotalDiscount"]</th>
<th>@L["BasketItemInventory"]</th>
<th>@L["BasketItemIsInvalid"]</th>
</tr>

5
plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Web/Pages/EShop/Plugins/Baskets/BasketItems/BasketItem/index.js

@ -57,15 +57,14 @@ $(function () {
{ data: "productId" },
{ data: "productSkuId" },
{ data: "quantity" },
{ data: "priceWithoutDiscount" },
{ data: "totalPriceWithoutDiscount" },
{ data: "mediaResources" },
{ data: "productUniqueName" },
{ data: "productDisplayName" },
{ data: "skuName" },
{ data: "skuDescription" },
{ data: "currency" },
{ data: "unitPrice" },
{ data: "totalPrice" },
{ data: "totalDiscount" },
{ data: "inventory" },
{ data: "isInvalid" },
]

6421
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230408062955_ImplementedProductDiscounts.Designer.cs

File diff suppressed because it is too large

109
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230408062955_ImplementedProductDiscounts.cs

@ -0,0 +1,109 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
/// <inheritdoc />
public partial class ImplementedProductDiscounts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TotalDiscount",
table: "EasyAbpEShopPluginsBasketsBasketItems");
migrationBuilder.RenameColumn(
name: "UnitPrice",
table: "EasyAbpEShopPluginsBasketsBasketItems",
newName: "TotalPriceWithoutDiscount");
migrationBuilder.RenameColumn(
name: "TotalPrice",
table: "EasyAbpEShopPluginsBasketsBasketItems",
newName: "PriceWithoutDiscount");
migrationBuilder.AddColumn<decimal>(
name: "MaximumPriceWithoutDiscount",
table: "EasyAbpEShopProductsProductViews",
type: "decimal(20,8)",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "MinimumPriceWithoutDiscount",
table: "EasyAbpEShopProductsProductViews",
type: "decimal(20,8)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "OrderDiscountPreviews",
table: "EasyAbpEShopProductsProductViews",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ProductDiscounts",
table: "EasyAbpEShopProductsProductViews",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "OrderDiscountPreviews",
table: "EasyAbpEShopPluginsBasketsBasketItems",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ProductDiscounts",
table: "EasyAbpEShopPluginsBasketsBasketItems",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MaximumPriceWithoutDiscount",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "MinimumPriceWithoutDiscount",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "OrderDiscountPreviews",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "ProductDiscounts",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "OrderDiscountPreviews",
table: "EasyAbpEShopPluginsBasketsBasketItems");
migrationBuilder.DropColumn(
name: "ProductDiscounts",
table: "EasyAbpEShopPluginsBasketsBasketItems");
migrationBuilder.RenameColumn(
name: "TotalPriceWithoutDiscount",
table: "EasyAbpEShopPluginsBasketsBasketItems",
newName: "UnitPrice");
migrationBuilder.RenameColumn(
name: "PriceWithoutDiscount",
table: "EasyAbpEShopPluginsBasketsBasketItems",
newName: "TotalPrice");
migrationBuilder.AddColumn<decimal>(
name: "TotalDiscount",
table: "EasyAbpEShopPluginsBasketsBasketItems",
type: "decimal(20,8)",
nullable: false,
defaultValue: 0m);
}
}
}

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

@ -1188,6 +1188,15 @@ namespace EShopSample.Migrations
b.Property<string>("MediaResources")
.HasColumnType("nvarchar(max)");
b.Property<string>("OrderDiscountPreviews")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("PriceWithoutDiscount")
.HasColumnType("decimal(20,8)");
b.Property<string>("ProductDiscounts")
.HasColumnType("nvarchar(max)");
b.Property<string>("ProductDisplayName")
.HasColumnType("nvarchar(max)");
@ -1216,13 +1225,7 @@ namespace EShopSample.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<decimal>("TotalDiscount")
.HasColumnType("decimal(20,8)");
b.Property<decimal>("TotalPrice")
.HasColumnType("decimal(20,8)");
b.Property<decimal>("UnitPrice")
b.Property<decimal>("TotalPriceWithoutDiscount")
.HasColumnType("decimal(20,8)");
b.Property<Guid>("UserId")
@ -2590,12 +2593,21 @@ namespace EShopSample.Migrations
b.Property<decimal?>("MaximumPrice")
.HasColumnType("decimal(20,8)");
b.Property<decimal?>("MaximumPriceWithoutDiscount")
.HasColumnType("decimal(20,8)");
b.Property<string>("MediaResources")
.HasColumnType("nvarchar(max)");
b.Property<decimal?>("MinimumPrice")
.HasColumnType("decimal(20,8)");
b.Property<decimal?>("MinimumPriceWithoutDiscount")
.HasColumnType("decimal(20,8)");
b.Property<string>("OrderDiscountPreviews")
.HasColumnType("nvarchar(max)");
b.Property<string>("Overview")
.HasColumnType("nvarchar(max)");
@ -2605,6 +2617,9 @@ namespace EShopSample.Migrations
b.Property<Guid?>("ProductDetailId")
.HasColumnType("uniqueidentifier");
b.Property<string>("ProductDiscounts")
.HasColumnType("nvarchar(max)");
b.Property<string>("ProductGroupDisplayName")
.HasColumnType("nvarchar(max)");

3
samples/EShopSample/aspnet-core/test/EShopSample.EntityFrameworkCore.Tests/EShopSample.EntityFrameworkCore.Tests.csproj

@ -12,8 +12,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.Sqlite" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

6
samples/EShopSample/aspnet-core/test/EShopSample.EntityFrameworkCore.Tests/EntityFrameworkCore/EShopSampleEntityFrameworkCoreTestModule.cs

@ -5,14 +5,16 @@ using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity;
namespace EShopSample.EntityFrameworkCore
{
[DependsOn(
typeof(EShopSampleEntityFrameworkCoreModule),
typeof(EShopSampleTestBaseModule)
)]
typeof(EShopSampleTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class EShopSampleEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;

Loading…
Cancel
Save