Browse Source

Refactor Order aggregate and move business to domain layer

pull/242/head
gdlcf88 3 years ago
parent
commit
4524c33498
  1. 18
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderDto.cs
  2. 8
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderLineDto.cs
  3. 16
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDiscountDto.cs
  4. 6
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderExtraFeeDto.cs
  5. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/BasicOrderCreationAuthorizationHandler.cs
  6. 15
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/INewOrderGenerator.cs
  7. 12
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs
  8. 43
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderAppService.cs
  9. 3
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderCreationResource.cs
  10. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/OrdersApplicationAutoMapperProfile.cs
  11. 21
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/en.json
  12. 17
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/zh-Hans.json
  13. 17
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/zh-Hant.json
  14. 27
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/CreateOrderInfoModel.cs
  15. 25
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/CreateOrderLineInfoModel.cs
  16. 12
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/ICreateOrderInfo.cs
  17. 13
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/ICreateOrderLineInfo.cs
  18. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrder.cs
  19. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrderExtraFee.cs
  20. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrderLine.cs
  21. 2
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderExtraFeeEto.cs
  22. 3
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersErrorCodes.cs
  23. 16
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DuplicateOrderDiscountException.cs
  24. 13
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/INewOrderGenerator.cs
  25. 4
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs
  26. 7
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs
  27. 11
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs
  28. 104
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs
  29. 76
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs
  30. 48
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscount.cs
  31. 34
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscountInfoModel.cs
  32. 5
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs
  33. 9
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFeeInfoModel.cs
  34. 0
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLineInvalidQuantityException.cs
  35. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs
  36. 0
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnexpectedCurrencyException.cs
  37. 1
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContext.cs
  38. 9
      modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContextModelCreatingExtensions.cs
  39. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/EasyAbp.EShop.Orders.Application.Tests.csproj
  40. 26
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/OrderAppServiceTests.cs
  41. 9
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs
  42. 19
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/DemoOrderDiscountProvider.cs
  43. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs
  44. 65
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDiscountTests.cs
  45. 1
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs
  46. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests/EasyAbp.EShop.Orders.EntityFrameworkCore.Tests.csproj
  47. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.HttpApi.Client.ConsoleTestApp/EasyAbp.EShop.Orders.HttpApi.Client.ConsoleTestApp.csproj
  48. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.MongoDB.Tests/EasyAbp.EShop.Orders.MongoDB.Tests.csproj
  49. 2
      modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/EasyAbp.EShop.Orders.TestBase.csproj
  50. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs
  51. 6
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs
  52. 14
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs
  53. 2
      modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs
  54. 2
      modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs
  55. 20
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductDto.cs
  56. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ProductViewDto.cs
  57. 1
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp.EShop.Products.Domain.Shared.csproj
  58. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/EShopProductsDomainSharedModule.cs
  59. 9
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IHasProductGroupDisplayName.cs
  60. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductAttribute.cs
  61. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductAttributeOption.cs
  62. 12
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductBase.cs
  63. 4
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductSku.cs
  64. 3
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductSkuDescriptionProvider.cs
  65. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductEto.cs
  66. 24
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductExtensions.cs
  67. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductSkuDescriptionProvider.cs
  68. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductSkuEto.cs
  69. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/IProductGroup.cs
  70. 8
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/Product.cs
  71. 7
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductView.cs
  72. 2
      plugins/Baskets/src/EasyAbp.EShop.Plugins.Baskets.Application/EasyAbp/EShop/Plugins/Baskets/BasketItems/BasicBasketItemProductInfoUpdater.cs
  73. 10
      plugins/Booking/src/EasyAbp.EShop.Orders.Booking.Application/EasyAbp/EShop/Orders/Booking/Authorization/BookingOrderCreationAuthorizationHandler.cs
  74. 13
      plugins/Booking/src/EasyAbp.EShop.Orders.Booking.Application/EasyAbp/EShop/Orders/Booking/BookingOrderLinePriceOverrider.cs
  75. 35
      plugins/Booking/src/EasyAbp.EShop.Plugins.Booking.Application.Contracts/EasyAbp/EShop/Orders/CreateOrderLineInfoExtensions.cs
  76. 85
      plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponOrderDiscountProvider.cs
  77. 10
      plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/ICoupon.cs
  78. 48
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs
  79. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs
  80. 6
      plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs
  81. 6406
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230329111403_RefactordOrderAndSomeOthers.Designer.cs
  82. 74
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230329111403_RefactordOrderAndSomeOthers.cs
  83. 57
      samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/EShopSampleDbContextModelSnapshot.cs

18
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderDto.cs

@ -3,13 +3,16 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using EasyAbp.EShop.Orders.Localization;
using EasyAbp.EShop.Stores.Stores; using EasyAbp.EShop.Stores.Stores;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Orders.Orders.Dtos namespace EasyAbp.EShop.Orders.Orders.Dtos
{ {
[Serializable] [Serializable]
public class CreateOrderDto : ExtensibleObject, IMultiStore public class CreateOrderDto : ExtensibleObject, ICreateOrderInfo
{ {
[DisplayName("OrderStoreId")] [DisplayName("OrderStoreId")]
public Guid StoreId { get; set; } public Guid StoreId { get; set; }
@ -17,6 +20,8 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
[DisplayName("OrderCustomerRemark")] [DisplayName("OrderCustomerRemark")]
public string CustomerRemark { get; set; } public string CustomerRemark { get; set; }
IEnumerable<ICreateOrderLineInfo> ICreateOrderInfo.OrderLines => OrderLines;
[DisplayName("OrderLine")] [DisplayName("OrderLine")]
public List<CreateOrderLineDto> OrderLines { get; set; } public List<CreateOrderLineDto> OrderLines { get; set; }
@ -26,19 +31,20 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
{ {
yield return result; yield return result;
} }
var localizer = validationContext.GetRequiredService<IStringLocalizer<OrdersResource>>();
if (OrderLines.Count == 0) if (OrderLines.Count == 0)
{ {
yield return new ValidationResult( yield return new ValidationResult(
"OrderLines should not be empty.", localizer[OrdersErrorCodes.OrderLinesShouldNotBeEmpty],
new[] { "OrderLines" } new[] { "OrderLines" }
); );
} }
if (OrderLines.Any(orderLine => orderLine.Quantity <= 0)) if (OrderLines.Any(orderLine => orderLine.Quantity < 1))
{ {
yield return new ValidationResult( yield return new ValidationResult(
"Quantity should be greater than 0.", localizer[OrdersErrorCodes.QuantityShouldBeGreaterThanZero],
new[] { "OrderLines" } new[] { "OrderLines" }
); );
} }

8
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/CreateOrderLineDto.cs

@ -6,17 +6,17 @@ using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Orders.Orders.Dtos namespace EasyAbp.EShop.Orders.Orders.Dtos
{ {
[Serializable] [Serializable]
public class CreateOrderLineDto : ExtensibleObject public class CreateOrderLineDto : ExtensibleObject, ICreateOrderLineInfo
{ {
public const int MinimumQuantity = 1; public const int MinimumQuantity = 1;
public const int MaximumQuantity = int.MaxValue; public const int MaximumQuantity = int.MaxValue;
[DisplayName("OrderLineProductId")] [DisplayName("OrderLineProductId")]
public Guid ProductId { get; set; } public Guid ProductId { get; set; }
[DisplayName("OrderLineProductSkuId")] [DisplayName("OrderLineProductSkuId")]
public Guid ProductSkuId { get; set; } public Guid ProductSkuId { get; set; }
[DisplayName("OrderLineQuantity")] [DisplayName("OrderLineQuantity")]
[Range(MinimumQuantity, MaximumQuantity)] [Range(MinimumQuantity, MaximumQuantity)]
public int Quantity { get; set; } public int Quantity { get; set; }

16
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderDiscountDto.cs

@ -0,0 +1,16 @@
using System;
namespace EasyAbp.EShop.Orders.Orders.Dtos;
public class OrderDiscountDto
{
public Guid OrderLineId { get; set; }
public string Name { get; set; }
public string Key { get; set; }
public string DisplayName { get; set; }
public decimal DiscountedAmount { get; set; }
}

6
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application.Contracts/EasyAbp/EShop/Orders/Orders/Dtos/OrderExtraFeeDto.cs

@ -4,14 +4,16 @@ namespace EasyAbp.EShop.Orders.Orders.Dtos
{ {
public class OrderExtraFeeDto : IOrderExtraFee public class OrderExtraFeeDto : IOrderExtraFee
{ {
public Guid OrderId { get; } public Guid OrderId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string DisplayName { get; set; }
public decimal Fee { get; set; } public decimal Fee { get; set; }
public decimal RefundAmount { get; } public decimal RefundAmount { get; set; }
} }
} }

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

@ -44,7 +44,7 @@ namespace EasyAbp.EShop.Orders.Orders
context.Succeed(requirement); context.Succeed(requirement);
} }
protected virtual Task<bool> IsProductsPublishedAsync(CreateOrderDto input, protected virtual Task<bool> IsProductsPublishedAsync(ICreateOrderInfo input,
Dictionary<Guid, ProductDto> productDictionary) Dictionary<Guid, ProductDto> productDictionary)
{ {
return Task.FromResult( return Task.FromResult(
@ -53,7 +53,7 @@ namespace EasyAbp.EShop.Orders.Orders
); );
} }
protected virtual Task<bool> IsInventoriesSufficientAsync(CreateOrderDto input, protected virtual Task<bool> IsInventoriesSufficientAsync(ICreateOrderInfo input,
Dictionary<Guid, ProductDto> productDictionary) Dictionary<Guid, ProductDto> productDictionary)
{ {
return Task.FromResult( return Task.FromResult(

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

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

12
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs

@ -1,12 +0,0 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.Products.Dtos;
using NodaMoney;
namespace EasyAbp.EShop.Orders.Orders;
public interface IOrderLinePriceOverrider
{
Task<Money?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product,
ProductSkuDto productSku, Currency effectiveCurrency);
}

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

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Authorization; using EasyAbp.EShop.Orders.Authorization;
using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.ProductDetails; using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products; using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos; using EasyAbp.EShop.Products.Products.Dtos;
using EasyAbp.EShop.Stores.Stores; using EasyAbp.EShop.Stores.Stores;
@ -102,21 +101,12 @@ namespace EasyAbp.EShop.Orders.Orders
new OrderOperationAuthorizationRequirement(OrderOperation.Creation) new OrderOperationAuthorizationRequirement(OrderOperation.Creation)
); );
var productDetailIds = input.OrderLines var productDetailModificationTimeDict =
.Select(dto => await GetProductDetailModificationTimeDictionaryAsync(input, productDict);
productDict[dto.ProductId].GetSkuById(dto.ProductSkuId).ProductDetailId ??
productDict[dto.ProductId].ProductDetailId)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
var productDetailDict = await GetProductDetailDictionaryAsync(productDetailIds);
// Todo: Can we use IProductDataScopedCache/IProductDetailDataScopedCache instead of productDict/productDetailDict? // Todo: Can we use IProductDataScopedCache/IProductDetailDataScopedCache instead of productDict/productDetailDict?
var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input, productDict, var order = await _newOrderGenerator.GenerateAsync(CurrentUser.GetId(), input,
productDetailDict); productDict.ToDictionary(x => x.Key, x => (IProduct)x.Value), productDetailModificationTimeDict);
await DiscountOrderAsync(order, productDict);
await Repository.InsertAsync(order, autoSave: true); await Repository.InsertAsync(order, autoSave: true);
@ -131,14 +121,6 @@ namespace EasyAbp.EShop.Orders.Orders
} }
} }
protected virtual async Task DiscountOrderAsync(Order order, Dictionary<Guid, ProductDto> productDict)
{
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IOrderDiscountProvider>>())
{
await provider.DiscountAsync(order, productDict);
}
}
protected virtual async Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync( protected virtual async Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(
IEnumerable<Guid> productIds) IEnumerable<Guid> productIds)
{ {
@ -152,14 +134,23 @@ namespace EasyAbp.EShop.Orders.Orders
return dict; return dict;
} }
protected virtual async Task<Dictionary<Guid, ProductDetailDto>> GetProductDetailDictionaryAsync( protected virtual async Task<Dictionary<Guid, DateTime>> GetProductDetailModificationTimeDictionaryAsync(
IEnumerable<Guid> productDetailIds) ICreateOrderInfo input, Dictionary<Guid, ProductDto> productDict)
{ {
var dict = new Dictionary<Guid, ProductDetailDto>(); var productDetailIds = input.OrderLines
.Select(dto =>
productDict[dto.ProductId].GetSkuById(dto.ProductSkuId).ProductDetailId ??
productDict[dto.ProductId].ProductDetailId)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
var dict = new Dictionary<Guid, DateTime>();
foreach (var productDetailId in productDetailIds.Distinct()) foreach (var productDetailId in productDetailIds.Distinct())
{ {
dict.Add(productDetailId, await _productDetailAppService.GetAsync(productDetailId)); var product = await _productDetailAppService.GetAsync(productDetailId);
dict.Add(productDetailId, product.LastModificationTime ?? product.CreationTime);
} }
return dict; return dict;

3
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderCreationResource.cs

@ -1,13 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Products.Products.Dtos; using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Orders.Orders namespace EasyAbp.EShop.Orders.Orders
{ {
public class OrderCreationResource public class OrderCreationResource
{ {
public CreateOrderDto Input { get; set; } public ICreateOrderInfo Input { get; set; }
public Dictionary<Guid, ProductDto> ProductDictionary { get; set; } public Dictionary<Guid, ProductDto> ProductDictionary { get; set; }
} }

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/OrdersApplicationAutoMapperProfile.cs

@ -14,6 +14,7 @@ namespace EasyAbp.EShop.Orders
* into multiple profile classes for a better organization. */ * into multiple profile classes for a better organization. */
CreateMap<Order, OrderDto>(); CreateMap<Order, OrderDto>();
CreateMap<OrderLine, OrderLineDto>(); CreateMap<OrderLine, OrderLineDto>();
CreateMap<OrderDiscount, OrderDiscountDto>(MemberList.Destination);
CreateMap<OrderExtraFee, OrderExtraFeeDto>(MemberList.Destination); CreateMap<OrderExtraFee, OrderExtraFeeDto>(MemberList.Destination);
} }
} }

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

@ -45,16 +45,33 @@
"OrderLineTotalDiscount": "Total discount", "OrderLineTotalDiscount": "Total discount",
"OrderLineActualTotalPrice": "Actual total price", "OrderLineActualTotalPrice": "Actual total price",
"OrderLineQuantity": "Quantity", "OrderLineQuantity": "Quantity",
"OrderDiscount": "Order discount",
"OrderDiscountOrderId": "Order ID",
"OrderDiscountOrderLineId": "Order line ID",
"OrderDiscountName": "Name",
"OrderDiscountKey": "Key",
"OrderDiscountDisplayName": "Display name",
"OrderDiscountDiscountedAmount": "Discounted amount",
"OrderExtraFee": "Order extra fee",
"OrderExtraFeeOrderId": "Order ID",
"OrderExtraFeeName": "Extra fee name",
"OrderExtraFeeKey": "Extra fee key",
"OrderExtraFeeDisplayName": "Display name",
"OrderExtraFeeFee": "Fee",
"OrderExtraFeeRefundAmount": "Refund amount",
"EasyAbp.EShop.Orders:UnexpectedCurrency": "Only the specified currency {expectedCurrency} is allowed.", "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:OrderLineInvalidQuantity": "Invalid quantity {quantity} for product {productId} (SKU: {productSkuId}).",
"EasyAbp.EShop.Orders:DiscountAmountOverflow": "The discount amount overflow.", "EasyAbp.EShop.Orders:DiscountAmountOverflow": "The discount amount overflow.",
"EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "The extra fee {extraFeeName} (key: {extraFeeKey}) is existed.", "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.", "EasyAbp.EShop.Orders:InvalidOrderExtraFee": "The extra fee {extraFee} is invalid.",
"EasyAbp.EShop.Orders:InvalidPayment": "The payment {paymentId} has invalid configurations for the order {orderId}.", "EasyAbp.EShop.Orders:InvalidPayment": "The payment {paymentId} has invalid configurations for the order {orderId}.",
"EasyAbp.EShop.Orders:InvalidRefundAmount": "The refund amount ({amount}) is invalid.", "EasyAbp.EShop.Orders:InvalidRefundAmount": "The refund amount ({amount}) is invalid.",
"EasyAbp.EShop.Orders:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.", "EasyAbp.EShop.Orders:InvalidRefundQuantity": "The refund quantity ({quantity}) is invalid.",
"EasyAbp.EShop.Orders:OrderIsInWrongStage": "The order {orderId} is in the wrong stage.", "EasyAbp.EShop.Orders:OrderIsInWrongStage": "The order {orderId} is in the wrong stage.",
"EasyAbp.EShop.Orders:ExistFlashSalesProduct": "Exist unexpected flash-sales product", "EasyAbp.EShop.Orders:ExistFlashSalesProduct": "Exist unexpected flash-sales product.",
"EasyAbp.EShop.Orders:OrderLinesShouldNotBeEmpty": "OrderLines should not be empty.",
"EasyAbp.EShop.Orders:QuantityShouldBeGreaterThanZero": "Quantity should be greater than 0.",
"DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "Currency code", "DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "Currency code",
"Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 code (see https://en.wikipedia.org/wiki/ISO_4217)" "Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 code (see https://en.wikipedia.org/wiki/ISO_4217)"
} }

17
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/zh-Hans.json

@ -45,9 +45,24 @@
"OrderLineTotalDiscount": "总折扣", "OrderLineTotalDiscount": "总折扣",
"OrderLineActualTotalPrice": "折后总价", "OrderLineActualTotalPrice": "折后总价",
"OrderLineQuantity": "数量", "OrderLineQuantity": "数量",
"OrderDiscount": "订单折扣项",
"OrderDiscountOrderId": "订单 ID",
"OrderDiscountOrderLineId": "订单项 ID",
"OrderDiscountName": "折扣项名称",
"OrderDiscountKey": "折扣项 Key",
"OrderDiscountDisplayName": "显示名称",
"OrderDiscountDiscountedAmount": "折扣金额",
"OrderExtraFee": "订单额外费用",
"OrderExtraFeeOrderId": "订单 ID",
"OrderExtraFeeName": "额外费用名称",
"OrderExtraFeeKey": "额外费用 Key",
"OrderExtraFeeDisplayName": "显示名称",
"OrderExtraFeeFee": "金额",
"OrderExtraFeeRefundAmount": "已退款金额",
"EasyAbp.EShop.Orders:UnexpectedCurrency": "只允许指定的{expectedCurrency}货币", "EasyAbp.EShop.Orders:UnexpectedCurrency": "只允许指定的{expectedCurrency}货币",
"EasyAbp.EShop.Orders:OrderLineInvalidQuantity": "产品{productId}(SKU: {productSkuId})的{quantity}数量无效", "EasyAbp.EShop.Orders:OrderLineInvalidQuantity": "产品{productId}(SKU: {productSkuId})的{quantity}数量无效",
"EasyAbp.EShop.Orders:DiscountAmountOverflow": "折扣金额溢出", "EasyAbp.EShop.Orders:DiscountAmountOverflow": "折扣金额溢出",
"EasyAbp.EShop.Orders:DuplicateOrderDiscount": "订单项{orderLineId}的折扣项{discountName}(key: {discountKey})已经存在",
"EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "额外费用{extraFeeName}(key: {extraFeeKey})已经存在", "EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "额外费用{extraFeeName}(key: {extraFeeKey})已经存在",
"EasyAbp.EShop.Orders:InvalidOrderExtraFee": "额外费用{extraFee}无效", "EasyAbp.EShop.Orders:InvalidOrderExtraFee": "额外费用{extraFee}无效",
"EasyAbp.EShop.Orders:InvalidPayment": "付款{paymentId}有无效的订单配置{orderId}", "EasyAbp.EShop.Orders:InvalidPayment": "付款{paymentId}有无效的订单配置{orderId}",
@ -55,6 +70,8 @@
"EasyAbp.EShop.Orders:InvalidRefundQuantity": "退款数量({quantity})无效", "EasyAbp.EShop.Orders:InvalidRefundQuantity": "退款数量({quantity})无效",
"EasyAbp.EShop.Orders:OrderIsInWrongStage": "订单{orderId}处于错误的阶段", "EasyAbp.EShop.Orders:OrderIsInWrongStage": "订单{orderId}处于错误的阶段",
"EasyAbp.EShop.Orders:ExistFlashSalesProduct": "清单中不允许存在闪购产品", "EasyAbp.EShop.Orders:ExistFlashSalesProduct": "清单中不允许存在闪购产品",
"EasyAbp.EShop.Orders:OrderLinesShouldNotBeEmpty": "订单项不可为空",
"EasyAbp.EShop.Orders:QuantityShouldBeGreaterThanZero": "购买数量应大于0",
"DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "货币代码", "DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "货币代码",
"Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 货币代码 (详见 https://en.wikipedia.org/wiki/ISO_4217)" "Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 货币代码 (详见 https://en.wikipedia.org/wiki/ISO_4217)"
} }

17
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Localization/Orders/zh-Hant.json

@ -45,9 +45,24 @@
"OrderLineTotalDiscount": "總折扣", "OrderLineTotalDiscount": "總折扣",
"OrderLineActualTotalPrice": "折後總價", "OrderLineActualTotalPrice": "折後總價",
"OrderLineQuantity": "數量", "OrderLineQuantity": "數量",
"OrderDiscount": "訂單折扣項",
"OrderDiscountOrderId": "訂單 ID",
"OrderDiscountOrderLineId": "訂單項 ID",
"OrderDiscountName": "折扣項名稱",
"OrderDiscountKey": "折扣項 Key",
"OrderDiscountDisplayName": "顯示名稱",
"OrderDiscountDiscountedAmount": "折扣金額",
"OrderExtraFee": "訂單額外費用",
"OrderExtraFeeOrderId": "訂單 ID",
"OrderExtraFeeName": "額外費用名稱",
"OrderExtraFeeKey": "額外費用 Key",
"OrderExtraFeeDisplayName": "顯示名稱",
"OrderExtraFeeFee": "金額",
"OrderExtraFeeRefundAmount": "已退款金額",
"EasyAbp.EShop.Orders:UnexpectedCurrency": "只允許指定的{expectedCurrency}貨幣", "EasyAbp.EShop.Orders:UnexpectedCurrency": "只允許指定的{expectedCurrency}貨幣",
"EasyAbp.EShop.Orders:OrderLineInvalidQuantity": "產品{productId}(SKU: {productSkuId})的{quantity}數量無效", "EasyAbp.EShop.Orders:OrderLineInvalidQuantity": "產品{productId}(SKU: {productSkuId})的{quantity}數量無效",
"EasyAbp.EShop.Orders:DiscountAmountOverflow": "折扣金額溢出", "EasyAbp.EShop.Orders:DiscountAmountOverflow": "折扣金額溢出",
"EasyAbp.EShop.Orders:DuplicateOrderDiscount": "訂單項{orderLineId}的折扣項{discountName}(key: {discountKey})已經存在",
"EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "額外費用{extraFeeName}(key: {extraFeeKey})已經存在", "EasyAbp.EShop.Orders:DuplicateOrderExtraFee": "額外費用{extraFeeName}(key: {extraFeeKey})已經存在",
"EasyAbp.EShop.Orders:InvalidOrderExtraFee": "額外費用{extraFee}無效", "EasyAbp.EShop.Orders:InvalidOrderExtraFee": "額外費用{extraFee}無效",
"EasyAbp.EShop.Orders:InvalidPayment": "付款{paymentId}有無效的訂單配置{orderId}", "EasyAbp.EShop.Orders:InvalidPayment": "付款{paymentId}有無效的訂單配置{orderId}",
@ -55,6 +70,8 @@
"EasyAbp.EShop.Orders:InvalidRefundQuantity": "退款數量({quantity})無效", "EasyAbp.EShop.Orders:InvalidRefundQuantity": "退款數量({quantity})無效",
"EasyAbp.EShop.Orders:OrderIsInWrongStage": "訂單{orderId}處於錯誤的階段", "EasyAbp.EShop.Orders:OrderIsInWrongStage": "訂單{orderId}處於錯誤的階段",
"EasyAbp.EShop.Orders:ExistFlashSalesProduct": "清單中不允許存在閃購產品", "EasyAbp.EShop.Orders:ExistFlashSalesProduct": "清單中不允許存在閃購產品",
"EasyAbp.EShop.Orders:OrderLinesShouldNotBeEmpty": "訂單項不可為空",
"EasyAbp.EShop.Orders:QuantityShouldBeGreaterThanZero": "購買數量應大於0",
"DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "貨幣代碼", "DisplayName:EasyAbp.EShop.Orders.CurrencyCode": "貨幣代碼",
"Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 貨幣代碼 (詳見 https://en.wikipedia.org/wiki/ISO_4217)" "Description:EasyAbp.EShop.Orders.CurrencyCode": "ISO 4217 貨幣代碼 (詳見 https://en.wikipedia.org/wiki/ISO_4217)"
} }

27
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/CreateOrderInfoModel.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Orders.Orders;
[Serializable]
public class CreateOrderInfoModel : ExtensibleObject, ICreateOrderInfo
{
public Guid StoreId { get; set; }
public string CustomerRemark { get; set; }
IEnumerable<ICreateOrderLineInfo> ICreateOrderInfo.OrderLines => OrderLines;
public List<CreateOrderLineInfoModel> OrderLines { get; set; }
public CreateOrderInfoModel()
{
}
public CreateOrderInfoModel(Guid storeId, string customerRemark, List<CreateOrderLineInfoModel> orderLines)
{
StoreId = storeId;
CustomerRemark = customerRemark;
OrderLines = orderLines;
}
}

25
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/CreateOrderLineInfoModel.cs

@ -0,0 +1,25 @@
using System;
using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Orders.Orders;
[Serializable]
public class CreateOrderLineInfoModel : ExtensibleObject, ICreateOrderLineInfo
{
public Guid ProductId { get; set; }
public Guid ProductSkuId { get; set; }
public int Quantity { get; set; }
public CreateOrderLineInfoModel()
{
}
public CreateOrderLineInfoModel(Guid productId, Guid productSkuId, int quantity)
{
ProductId = productId;
ProductSkuId = productSkuId;
Quantity = quantity;
}
}

12
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/ICreateOrderInfo.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
using EasyAbp.EShop.Stores.Stores;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Orders.Orders;
public interface ICreateOrderInfo : IMultiStore, IHasExtraProperties
{
string CustomerRemark { get; }
IEnumerable<ICreateOrderLineInfo> OrderLines { get; }
}

13
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/ICreateOrderLineInfo.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Data;
namespace EasyAbp.EShop.Orders.Orders;
public interface ICreateOrderLineInfo : IHasExtraProperties
{
Guid ProductId { get; }
Guid ProductSkuId { get; }
int Quantity { get; }
}

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrder.cs

@ -8,6 +8,8 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
public interface IOrder : IMultiStore, IHasExtraProperties public interface IOrder : IMultiStore, IHasExtraProperties
{ {
Guid Id { get; }
[NotNull] [NotNull]
string OrderNumber { get; } string OrderNumber { get; }

11
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/IOrderExtraFee.cs

@ -6,15 +6,18 @@ namespace EasyAbp.EShop.Orders.Orders
public interface IOrderExtraFee public interface IOrderExtraFee
{ {
Guid OrderId { get; } Guid OrderId { get; }
[NotNull] [NotNull]
string Name { get; } string Name { get; }
[CanBeNull] [CanBeNull]
string Key { get; } string Key { get; }
[CanBeNull]
string DisplayName { get; }
decimal Fee { get; } decimal Fee { get; }
decimal RefundAmount { get; } decimal RefundAmount { get; }
} }
} }

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

@ -5,8 +5,10 @@ using Volo.Abp.Data;
namespace EasyAbp.EShop.Orders.Orders namespace EasyAbp.EShop.Orders.Orders
{ {
public interface IOrderLine : IHasExtraProperties public interface IOrderLine : IHasExtraProperties, IHasProductGroupDisplayName
{ {
Guid Id { get; }
Guid ProductId { get; } Guid ProductId { get; }
Guid ProductSkuId { get; } Guid ProductSkuId { get; }
@ -20,9 +22,6 @@ namespace EasyAbp.EShop.Orders.Orders
[NotNull] [NotNull]
string ProductGroupName { get; } string ProductGroupName { get; }
[NotNull]
string ProductGroupDisplayName { get; }
[CanBeNull] [CanBeNull]
string ProductUniqueName { get; } string ProductUniqueName { get; }

2
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/Orders/OrderExtraFeeEto.cs

@ -11,6 +11,8 @@ namespace EasyAbp.EShop.Orders.Orders
public string Key { get; set; } public string Key { get; set; }
public string DisplayName { get; set; }
public decimal Fee { get; set; } public decimal Fee { get; set; }
public decimal RefundAmount { get; set; } public decimal RefundAmount { get; set; }

3
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain.Shared/EasyAbp/EShop/Orders/OrdersErrorCodes.cs

@ -6,11 +6,14 @@
public const string OrderLineInvalidQuantity = "EasyAbp.EShop.Orders:OrderLineInvalidQuantity"; public const string OrderLineInvalidQuantity = "EasyAbp.EShop.Orders:OrderLineInvalidQuantity";
public const string DiscountAmountOverflow = "EasyAbp.EShop.Orders:DiscountAmountOverflow"; public const string DiscountAmountOverflow = "EasyAbp.EShop.Orders:DiscountAmountOverflow";
public const string DuplicateOrderExtraFee = "EasyAbp.EShop.Orders:DuplicateOrderExtraFee"; public const string DuplicateOrderExtraFee = "EasyAbp.EShop.Orders:DuplicateOrderExtraFee";
public const string DuplicateOrderDiscount = "EasyAbp.EShop.Orders:DuplicateOrderDiscount";
public const string InvalidOrderExtraFee = "EasyAbp.EShop.Orders:InvalidOrderExtraFee"; public const string InvalidOrderExtraFee = "EasyAbp.EShop.Orders:InvalidOrderExtraFee";
public const string InvalidPayment = "EasyAbp.EShop.Orders:InvalidPayment"; public const string InvalidPayment = "EasyAbp.EShop.Orders:InvalidPayment";
public const string InvalidRefundAmount = "EasyAbp.EShop.Orders:InvalidRefundAmount"; public const string InvalidRefundAmount = "EasyAbp.EShop.Orders:InvalidRefundAmount";
public const string InvalidRefundQuantity = "EasyAbp.EShop.Orders:InvalidRefundQuantity"; public const string InvalidRefundQuantity = "EasyAbp.EShop.Orders:InvalidRefundQuantity";
public const string OrderIsInWrongStage = "EasyAbp.EShop.Orders:OrderIsInWrongStage"; public const string OrderIsInWrongStage = "EasyAbp.EShop.Orders:OrderIsInWrongStage";
public const string ExistFlashSalesProduct = "EasyAbp.EShop.Orders:ExistFlashSalesProduct"; public const string ExistFlashSalesProduct = "EasyAbp.EShop.Orders:ExistFlashSalesProduct";
public const string OrderLinesShouldNotBeEmpty = "EasyAbp.EShop.Orders:OrderLinesShouldNotBeEmpty";
public const string QuantityShouldBeGreaterThanZero = "EasyAbp.EShop.Orders:QuantityShouldBeGreaterThanZero";
} }
} }

16
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/DuplicateOrderDiscountException.cs

@ -0,0 +1,16 @@
using System;
using Volo.Abp;
namespace EasyAbp.EShop.Orders.Orders
{
public class DuplicateOrderDiscountException : BusinessException
{
public DuplicateOrderDiscountException(Guid orderLineId, string discountName, string discountKey) : base(
OrdersErrorCodes.DuplicateOrderDiscount)
{
WithData(nameof(orderLineId), orderLineId);
WithData(nameof(discountName), discountName);
WithData(nameof(discountKey), discountKey);
}
}
}

13
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/INewOrderGenerator.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Orders.Orders
{
public interface INewOrderGenerator
{
Task<Order> GenerateAsync(Guid customerUserId, ICreateOrderInfo input, Dictionary<Guid, IProduct> productDict,
Dictionary<Guid, DateTime> productDetailModificationTimeDict);
}
}

4
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderDiscountProvider.cs

@ -1,12 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos; using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Orders.Orders namespace EasyAbp.EShop.Orders.Orders
{ {
public interface IOrderDiscountProvider public interface IOrderDiscountProvider
{ {
Task<Order> DiscountAsync(Order order, Dictionary<Guid, ProductDto> productDict); Task<List<OrderDiscountInfoModel>> GetAllAsync(Order order, Dictionary<Guid, IProduct> productDict);
} }
} }

7
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs

@ -1,15 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using NodaMoney; using NodaMoney;
namespace EasyAbp.EShop.Orders.Orders namespace EasyAbp.EShop.Orders.Orders
{ {
public interface IOrderExtraFeeProvider public interface IOrderExtraFeeProvider
{ {
Task<List<OrderExtraFeeInfoModel>> GetListAsync(Guid customerUserId, CreateOrderDto input, Task<List<OrderExtraFeeInfoModel>> GetListAsync(Guid customerUserId, ICreateOrderInfo input,
Dictionary<Guid, ProductDto> productDict, Currency effectiveCurrency); Dictionary<Guid, IProduct> productDict, Currency effectiveCurrency);
} }
} }

11
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products;
using NodaMoney;
namespace EasyAbp.EShop.Orders.Orders;
public interface IOrderLinePriceOverrider
{
Task<Money?> GetUnitPriceOrNullAsync(ICreateOrderInfo input, ICreateOrderLineInfo inputOrderLine,
IProduct product, IProductSku productSku, Currency effectiveCurrency);
}

104
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs

@ -2,57 +2,42 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Orders.Settings; using EasyAbp.EShop.Orders.Settings;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products; using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Microsoft.Extensions.DependencyInjection;
using NodaMoney; using NodaMoney;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids; using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Orders.Orders namespace EasyAbp.EShop.Orders.Orders
{ {
public class NewOrderGenerator : INewOrderGenerator, ITransientDependency public class NewOrderGenerator : DomainService, INewOrderGenerator, ITransientDependency
{ {
private readonly IClock _clock;
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly ISettingProvider _settingProvider; private readonly ISettingProvider _settingProvider;
private readonly IServiceProvider _serviceProvider;
private readonly IOrderNumberGenerator _orderNumberGenerator; private readonly IOrderNumberGenerator _orderNumberGenerator;
private readonly IProductSkuDescriptionProvider _productSkuDescriptionProvider; private readonly IProductSkuDescriptionProvider _productSkuDescriptionProvider;
private readonly IEnumerable<IOrderLinePriceOverrider> _orderLinePriceOverriders; private readonly IEnumerable<IOrderLinePriceOverrider> _orderLinePriceOverriders;
public NewOrderGenerator( public NewOrderGenerator(
IClock clock,
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
ISettingProvider settingProvider, ISettingProvider settingProvider,
IServiceProvider serviceProvider,
IOrderNumberGenerator orderNumberGenerator, IOrderNumberGenerator orderNumberGenerator,
IProductSkuDescriptionProvider productSkuDescriptionProvider, IProductSkuDescriptionProvider productSkuDescriptionProvider,
IEnumerable<IOrderLinePriceOverrider> orderLinePriceOverriders) IEnumerable<IOrderLinePriceOverrider> orderLinePriceOverriders)
{ {
_clock = clock;
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_settingProvider = settingProvider; _settingProvider = settingProvider;
_serviceProvider = serviceProvider;
_orderNumberGenerator = orderNumberGenerator; _orderNumberGenerator = orderNumberGenerator;
_productSkuDescriptionProvider = productSkuDescriptionProvider; _productSkuDescriptionProvider = productSkuDescriptionProvider;
_orderLinePriceOverriders = orderLinePriceOverriders; _orderLinePriceOverriders = orderLinePriceOverriders;
} }
public virtual async Task<Order> GenerateAsync(Guid customerUserId, CreateOrderDto input, public virtual async Task<Order> GenerateAsync(Guid customerUserId, ICreateOrderInfo input,
Dictionary<Guid, ProductDto> productDict, Dictionary<Guid, ProductDetailDto> productDetailDict) Dictionary<Guid, IProduct> productDict, Dictionary<Guid, DateTime> productDetailModificationTimeDict)
{ {
await ValidateInputAsync(input);
var effectiveCurrency = await GetEffectiveCurrencyAsync(); var effectiveCurrency = await GetEffectiveCurrencyAsync();
var orderLines = new List<OrderLine>(); var orderLines = new List<OrderLine>();
@ -60,7 +45,7 @@ namespace EasyAbp.EShop.Orders.Orders
foreach (var inputOrderLine in input.OrderLines) foreach (var inputOrderLine in input.OrderLines)
{ {
orderLines.Add(await GenerateOrderLineAsync( orderLines.Add(await GenerateOrderLineAsync(
input, inputOrderLine, productDict, productDetailDict, effectiveCurrency)); input, inputOrderLine, productDict, productDetailModificationTimeDict, effectiveCurrency));
} }
var productTotalPrice = orderLines.Select(x => x.TotalPrice).Sum(); var productTotalPrice = orderLines.Select(x => x.TotalPrice).Sum();
@ -72,8 +57,8 @@ namespace EasyAbp.EShop.Orders.Orders
var totalDiscount = orderLines.Select(x => x.TotalDiscount).Sum(); var totalDiscount = orderLines.Select(x => x.TotalDiscount).Sum();
var order = new Order( var order = new Order(
id: _guidGenerator.Create(), id: GuidGenerator.Create(),
tenantId: _currentTenant.Id, tenantId: CurrentTenant.Id,
storeId: input.StoreId, storeId: input.StoreId,
customerUserId: customerUserId, customerUserId: customerUserId,
currency: effectiveCurrency.Code, currency: effectiveCurrency.Code,
@ -82,7 +67,7 @@ namespace EasyAbp.EShop.Orders.Orders
totalPrice: totalPrice, totalPrice: totalPrice,
actualTotalPrice: totalPrice - totalDiscount, actualTotalPrice: totalPrice - totalDiscount,
customerRemark: input.CustomerRemark, customerRemark: input.CustomerRemark,
paymentExpiration: paymentExpireIn.HasValue ? _clock.Now.Add(paymentExpireIn.Value) : null paymentExpiration: paymentExpireIn.HasValue ? Clock.Now.Add(paymentExpireIn.Value) : null
); );
input.MapExtraPropertiesTo(order, MappingPropertyDefinitionChecks.Destination); input.MapExtraPropertiesTo(order, MappingPropertyDefinitionChecks.Destination);
@ -97,16 +82,47 @@ namespace EasyAbp.EShop.Orders.Orders
// see https://github.com/EasyAbp/EShop/issues/214 // see https://github.com/EasyAbp/EShop/issues/214
if (order.OrderLines.All(x => x.ProductInventoryStrategy != InventoryStrategy.ReduceAfterPlacing)) if (order.OrderLines.All(x => x.ProductInventoryStrategy != InventoryStrategy.ReduceAfterPlacing))
{ {
order.SetReducedInventoryAfterPlacingTime(_clock.Now); order.SetReducedInventoryAfterPlacingTime(Clock.Now);
} }
await DiscountOrderAsync(order, productDict);
return order; return order;
} }
protected virtual Task ValidateInputAsync(ICreateOrderInfo info)
{
if (!info.OrderLines.Any())
{
throw new BusinessException(OrdersErrorCodes.OrderLinesShouldNotBeEmpty);
}
if (info.OrderLines.Any(orderLine => orderLine.Quantity < 1))
{
throw new BusinessException(OrdersErrorCodes.QuantityShouldBeGreaterThanZero);
}
return Task.CompletedTask;
}
protected virtual async Task DiscountOrderAsync(Order order, Dictionary<Guid, IProduct> productDict)
{
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IOrderDiscountProvider>>())
{
var discounts = await provider.GetAllAsync(order, productDict);
foreach (var discount in discounts)
{
order.AddDiscount(discount.OrderLineId, discount.Name, discount.Key, discount.DisplayName,
discount.DiscountedAmount);
}
}
}
protected virtual async Task AddOrderExtraFeesAsync(Order order, Guid customerUserId, protected virtual async Task AddOrderExtraFeesAsync(Order order, Guid customerUserId,
CreateOrderDto input, Dictionary<Guid, ProductDto> productDict, Currency effectiveCurrency) ICreateOrderInfo input, Dictionary<Guid, IProduct> productDict, Currency effectiveCurrency)
{ {
var providers = _serviceProvider.GetServices<IOrderExtraFeeProvider>(); var providers = LazyServiceProvider.LazyGetService<IEnumerable<IOrderExtraFeeProvider>>();
foreach (var provider in providers) foreach (var provider in providers)
{ {
@ -115,25 +131,27 @@ namespace EasyAbp.EShop.Orders.Orders
foreach (var infoModel in infoModels) foreach (var infoModel in infoModels)
{ {
var fee = new Money(infoModel.Fee, effectiveCurrency); var fee = new Money(infoModel.Fee, effectiveCurrency);
order.AddOrderExtraFee(fee.Amount, infoModel.Name, infoModel.Key); order.AddOrderExtraFee(fee.Amount, infoModel.Name, infoModel.Key, infoModel.DisplayName);
} }
} }
} }
protected virtual async Task<OrderLine> GenerateOrderLineAsync(CreateOrderDto input, protected virtual async Task<OrderLine> GenerateOrderLineAsync(ICreateOrderInfo input,
CreateOrderLineDto inputOrderLine, Dictionary<Guid, ProductDto> productDict, ICreateOrderLineInfo inputOrderLine, Dictionary<Guid, IProduct> productDict,
Dictionary<Guid, ProductDetailDto> productDetailDict, Currency effectiveCurrency) Dictionary<Guid, DateTime> productDetailModificationTimeDict, Currency effectiveCurrency)
{ {
var product = productDict[inputOrderLine.ProductId]; var product = productDict[inputOrderLine.ProductId];
var productSku = product.GetSkuById(inputOrderLine.ProductSkuId); var productSku = product.GetSkuById(inputOrderLine.ProductSkuId);
if (productSku.Currency != effectiveCurrency.Code) if (productSku.Currency != effectiveCurrency.Code)
{ {
throw new UnexpectedCurrencyException(effectiveCurrency.Code); throw new UnexpectedCurrencyException(effectiveCurrency.Code);
} }
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId;
var productDetail = productDetailId.HasValue ? productDetailDict[productDetailId.Value] : null; var productDetailModificationTime = productDetailId.HasValue
? productDetailModificationTimeDict[productDetailId.Value]
: (DateTime?)null;
if (!inputOrderLine.Quantity.IsBetween(productSku.OrderMinQuantity, productSku.OrderMaxQuantity)) if (!inputOrderLine.Quantity.IsBetween(productSku.OrderMinQuantity, productSku.OrderMaxQuantity))
{ {
@ -145,14 +163,18 @@ namespace EasyAbp.EShop.Orders.Orders
var totalPrice = unitPrice * inputOrderLine.Quantity; var totalPrice = unitPrice * inputOrderLine.Quantity;
var orderLine = new OrderLine( var orderLine = new OrderLine(
id: _guidGenerator.Create(), id: GuidGenerator.Create(),
productId: product.Id, productId: product.Id,
productSkuId: productSku.Id, productSkuId: productSku.Id,
productDetailId: productDetailId, productDetailId: productDetailId,
productModificationTime: product.LastModificationTime ?? product.CreationTime, productModificationTime: product is IAuditedObject auditedProduct
productDetailModificationTime: productDetail?.LastModificationTime ?? productDetail?.CreationTime, ? auditedProduct.LastModificationTime ?? auditedProduct.CreationTime
: Clock.Now,
productDetailModificationTime: productDetailModificationTime,
productGroupName: product.ProductGroupName, productGroupName: product.ProductGroupName,
productGroupDisplayName: product.ProductGroupDisplayName, productGroupDisplayName: product is IHasProductGroupDisplayName hasProductGroupDisplayName
? hasProductGroupDisplayName.ProductGroupDisplayName
: product.ProductGroupName,
productUniqueName: product.UniqueName, productUniqueName: product.UniqueName,
productDisplayName: product.DisplayName, productDisplayName: product.DisplayName,
productInventoryStrategy: product.InventoryStrategy, productInventoryStrategy: product.InventoryStrategy,
@ -172,8 +194,8 @@ namespace EasyAbp.EShop.Orders.Orders
return orderLine; return orderLine;
} }
protected virtual async Task<Money> GetUnitPriceAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, protected virtual async Task<Money> GetUnitPriceAsync(ICreateOrderInfo input,
ProductDto product, ProductSkuDto productSku, Currency effectiveCurrency) ICreateOrderLineInfo inputOrderLine, IProduct product, IProductSku productSku, Currency effectiveCurrency)
{ {
foreach (var overrider in _orderLinePriceOverriders) foreach (var overrider in _orderLinePriceOverriders)
{ {

76
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/Order.cs

@ -10,51 +10,54 @@ namespace EasyAbp.EShop.Orders.Orders
public class Order : FullAuditedAggregateRoot<Guid>, IOrder, IMultiTenant public class Order : FullAuditedAggregateRoot<Guid>, IOrder, IMultiTenant
{ {
public virtual Guid? TenantId { get; protected set; } public virtual Guid? TenantId { get; protected set; }
public virtual Guid StoreId { get; protected set; } public virtual Guid StoreId { get; protected set; }
public virtual string OrderNumber { get; protected set; } public virtual string OrderNumber { get; protected set; }
public virtual Guid CustomerUserId { get; protected set; } public virtual Guid CustomerUserId { get; protected set; }
public virtual OrderStatus OrderStatus { get; protected set; } public virtual OrderStatus OrderStatus { get; protected set; }
public virtual string Currency { get; protected set; } public virtual string Currency { get; protected set; }
public virtual decimal ProductTotalPrice { get; protected set; } public virtual decimal ProductTotalPrice { get; protected set; }
public virtual decimal TotalDiscount { get; protected set; } public virtual decimal TotalDiscount { get; protected set; }
public virtual decimal TotalPrice { get; protected set; } public virtual decimal TotalPrice { get; protected set; }
public virtual decimal ActualTotalPrice { get; protected set; } public virtual decimal ActualTotalPrice { get; protected set; }
public virtual decimal RefundAmount { get; protected set; } public virtual decimal RefundAmount { get; protected set; }
public virtual string CustomerRemark { get; protected set; } public virtual string CustomerRemark { get; protected set; }
public virtual string StaffRemark { get; protected set; } public virtual string StaffRemark { get; protected set; }
public virtual Guid? PaymentId { get; protected set; } public virtual Guid? PaymentId { get; protected set; }
public virtual DateTime? PaidTime { get; protected set; } public virtual DateTime? PaidTime { get; protected set; }
public virtual DateTime? CompletionTime { get; protected set; } public virtual DateTime? CompletionTime { get; protected set; }
public virtual DateTime? CanceledTime { get; protected set; } public virtual DateTime? CanceledTime { get; protected set; }
public virtual string CancellationReason { get; protected set; } public virtual string CancellationReason { get; protected set; }
public virtual DateTime? ReducedInventoryAfterPlacingTime { get; protected set; } public virtual DateTime? ReducedInventoryAfterPlacingTime { get; protected set; }
public virtual DateTime? ReducedInventoryAfterPaymentTime { get; protected set; } public virtual DateTime? ReducedInventoryAfterPaymentTime { get; protected set; }
public virtual DateTime? PaymentExpiration { get; protected set; } public virtual DateTime? PaymentExpiration { get; protected set; }
IEnumerable<IOrderLine> IOrder.OrderLines => OrderLines; IEnumerable<IOrderLine> IOrder.OrderLines => OrderLines;
public virtual List<OrderLine> OrderLines { get; protected set; } public virtual List<OrderLine> OrderLines { get; protected set; }
IEnumerable<IOrderExtraFee> IOrder.OrderExtraFees => OrderExtraFees; IEnumerable<IOrderExtraFee> IOrder.OrderExtraFees => OrderExtraFees;
public virtual List<OrderDiscount> OrderDiscounts { get; protected set; }
public virtual List<OrderExtraFee> OrderExtraFees { get; protected set; } public virtual List<OrderExtraFee> OrderExtraFees { get; protected set; }
protected Order() protected Order()
@ -87,9 +90,10 @@ namespace EasyAbp.EShop.Orders.Orders
PaymentExpiration = paymentExpiration; PaymentExpiration = paymentExpiration;
RefundAmount = 0; RefundAmount = 0;
OrderStatus = OrderStatus.Pending; OrderStatus = OrderStatus.Pending;
OrderLines = new List<OrderLine>(); OrderLines = new List<OrderLine>();
OrderDiscounts = new List<OrderDiscount>();
OrderExtraFees = new List<OrderExtraFee>(); OrderExtraFees = new List<OrderExtraFee>();
} }
@ -182,35 +186,47 @@ namespace EasyAbp.EShop.Orders.Orders
return !(!PaymentId.HasValue || PaidTime.HasValue); return !(!PaymentId.HasValue || PaidTime.HasValue);
} }
public void AddDiscount(Guid orderLineId, decimal expectedDiscountAmount) public void AddDiscount(Guid orderLineId, [NotNull] string discountName, [CanBeNull] string discountKey,
[CanBeNull] string discountDisplayName, decimal discountedAmount)
{ {
var orderLine = OrderLines.Single(x => x.Id == orderLineId); var orderLine = OrderLines.Single(x => x.Id == orderLineId);
orderLine.AddDiscount(expectedDiscountAmount); orderLine.AddDiscount(discountedAmount);
TotalDiscount += expectedDiscountAmount; TotalDiscount += discountedAmount;
ActualTotalPrice -= expectedDiscountAmount; ActualTotalPrice -= discountedAmount;
if (ActualTotalPrice < decimal.Zero) if (ActualTotalPrice < decimal.Zero)
{ {
throw new DiscountAmountOverflowException(); throw new DiscountAmountOverflowException();
} }
if (OrderDiscounts.Any(x => x.OrderLineId == orderLineId && x.Name == discountName && x.Key == discountKey))
{
throw new DuplicateOrderDiscountException(orderLineId, discountName, discountKey);
}
var orderDiscount = new OrderDiscount(
Id, orderLineId, discountName, discountKey, discountDisplayName, discountedAmount);
OrderDiscounts.Add(orderDiscount);
} }
public void AddOrderExtraFee(decimal extraFee, [NotNull] string extraFeeName, [CanBeNull] string extraFeeKey) public void AddOrderExtraFee(decimal extraFee, [NotNull] string extraFeeName, [CanBeNull] string extraFeeKey,
[CanBeNull] string extraFeeDisplayName)
{ {
if (extraFee <= decimal.Zero) if (extraFee <= decimal.Zero)
{ {
throw new InvalidOrderExtraFeeException(extraFee); throw new InvalidOrderExtraFeeException(extraFee);
} }
var orderExtraFee = new OrderExtraFee(Id, extraFeeName, extraFeeKey, extraFee);
if (OrderExtraFees.Any(x => x.EntityEquals(orderExtraFee))) if (OrderExtraFees.Any(x => x.Name == extraFeeName && x.Key == extraFeeKey))
{ {
throw new DuplicateOrderExtraFeeException(extraFeeName, extraFeeKey); throw new DuplicateOrderExtraFeeException(extraFeeName, extraFeeKey);
} }
var orderExtraFee = new OrderExtraFee(Id, extraFeeName, extraFeeKey, extraFeeDisplayName, extraFee);
OrderExtraFees.Add(orderExtraFee); OrderExtraFees.Add(orderExtraFee);
TotalPrice += extraFee; TotalPrice += extraFee;
@ -228,4 +244,4 @@ namespace EasyAbp.EShop.Orders.Orders
PaidTime.HasValue && !ReducedInventoryAfterPaymentTime.HasValue; PaidTime.HasValue && !ReducedInventoryAfterPaymentTime.HasValue;
} }
} }
} }

48
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscount.cs

@ -0,0 +1,48 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities;
namespace EasyAbp.EShop.Orders.Orders;
public class OrderDiscount : Entity
{
public virtual Guid OrderId { get; protected set; }
public virtual Guid OrderLineId { get; protected set; }
[NotNull]
public virtual string Name { get; protected set; }
[CanBeNull]
public virtual string Key { get; protected set; }
[CanBeNull]
public virtual string DisplayName { get; protected set; }
public virtual decimal DiscountedAmount { get; protected set; }
protected OrderDiscount()
{
}
public OrderDiscount(
Guid orderId,
Guid orderLineId,
[NotNull] string name,
[CanBeNull] string key,
[CanBeNull] string displayName,
decimal discountedAmount)
{
OrderId = orderId;
OrderLineId = orderLineId;
Name = name;
Key = key;
DisplayName = displayName;
DiscountedAmount = discountedAmount;
}
public override object[] GetKeys()
{
return new object[] { OrderId, OrderLineId, Name, Key };
}
}

34
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderDiscountInfoModel.cs

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

5
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFee.cs

@ -13,6 +13,9 @@ namespace EasyAbp.EShop.Orders.Orders
[CanBeNull] [CanBeNull]
public virtual string Key { get; protected set; } public virtual string Key { get; protected set; }
[CanBeNull]
public virtual string DisplayName { get; protected set; }
public virtual decimal Fee { get; protected set; } public virtual decimal Fee { get; protected set; }
@ -26,11 +29,13 @@ namespace EasyAbp.EShop.Orders.Orders
Guid orderId, Guid orderId,
[NotNull] string name, [NotNull] string name,
[CanBeNull] string key, [CanBeNull] string key,
[CanBeNull] string displayName,
decimal fee) decimal fee)
{ {
OrderId = orderId; OrderId = orderId;
Name = name; Name = name;
Key = key; Key = key;
DisplayName = displayName;
Fee = fee; Fee = fee;
} }

9
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderExtraFeeInfoModel.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderExtraFeeInfoModel.cs

@ -6,18 +6,23 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
[NotNull] [NotNull]
public string Name { get; set; } public string Name { get; set; }
[CanBeNull] [CanBeNull]
public string Key { get; set; } public string Key { get; set; }
[CanBeNull]
public string DisplayName { get; set; }
public decimal Fee { get; set; } public decimal Fee { get; set; }
public OrderExtraFeeInfoModel( public OrderExtraFeeInfoModel(
[NotNull] string name, [NotNull] string name,
[CanBeNull] string key, [CanBeNull] string key,
[CanBeNull] string displayName,
decimal fee) decimal fee)
{ {
Name = name; Name = name;
DisplayName = displayName;
Key = key ?? string.Empty; Key = key ?? string.Empty;
Fee = fee; Fee = fee;
} }

0
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/OrderLineInvalidQuantityException.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/OrderLineInvalidQuantityException.cs

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/ProductInventoryReductionEventHandler.cs

@ -121,6 +121,7 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
Name = x.Name, Name = x.Name,
Key = x.Key, Key = x.Key,
DisplayName = x.DisplayName,
TotalAmount = x.Fee - x.RefundAmount TotalAmount = x.Fee - x.RefundAmount
})); }));

0
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/UnexpectedCurrencyException.cs → modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp/EShop/Orders/Orders/UnexpectedCurrencyException.cs

1
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContext.cs

@ -13,6 +13,7 @@ namespace EasyAbp.EShop.Orders.EntityFrameworkCore
*/ */
public DbSet<Order> Orders { get; set; } public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> OrderLines { get; set; } public DbSet<OrderLine> OrderLines { get; set; }
public DbSet<OrderDiscount> OrderDiscounts { get; set; }
public DbSet<OrderExtraFee> OrderExtraFees { get; set; } public DbSet<OrderExtraFee> OrderExtraFees { get; set; }
public OrdersDbContext(DbContextOptions<OrdersDbContext> options) public OrdersDbContext(DbContextOptions<OrdersDbContext> options)

9
modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.EntityFrameworkCore/EasyAbp/EShop/Orders/EntityFrameworkCore/OrdersDbContextModelCreatingExtensions.cs

@ -66,6 +66,15 @@ namespace EasyAbp.EShop.Orders.EntityFrameworkCore
b.Property(x => x.RefundAmount).HasColumnType("decimal(20,8)"); b.Property(x => x.RefundAmount).HasColumnType("decimal(20,8)");
}); });
builder.Entity<OrderDiscount>(b =>
{
b.ToTable(options.TablePrefix + "OrderDiscounts", options.Schema);
b.ConfigureByConvention();
/* Configure more properties here */
b.Property(x => x.DiscountedAmount).HasColumnType("decimal(20,8)");
b.HasKey(x => new {x.OrderId, x.OrderLineId, x.Name, x.Key});
});
builder.Entity<OrderExtraFee>(b => builder.Entity<OrderExtraFee>(b =>
{ {
b.ToTable(options.TablePrefix + "OrderExtraFees", options.Schema); b.ToTable(options.TablePrefix + "OrderExtraFees", options.Schema);

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/EasyAbp.EShop.Orders.Application.Tests.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace /> <RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

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

@ -119,10 +119,7 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>(); var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var orderCount = 0; var orderCount = 0;
await WithUnitOfWorkAsync(async () => await WithUnitOfWorkAsync(async () => { orderCount = await orderRepository.CountAsync(); });
{
orderCount = await orderRepository.CountAsync();
});
// Arrange // Arrange
var checkCreateOrderInput = new CheckCreateOrderInput var checkCreateOrderInput = new CheckCreateOrderInput
@ -131,7 +128,7 @@ namespace EasyAbp.EShop.Orders.Orders
StoreId = OrderTestData.Store1Id, StoreId = OrderTestData.Store1Id,
OrderLines = new List<CreateOrderLineDto> OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id, ProductSkuId = OrderTestData.ProductSku1Id,
@ -160,10 +157,7 @@ namespace EasyAbp.EShop.Orders.Orders
{ {
var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>(); var orderRepository = ServiceProvider.GetRequiredService<IOrderRepository>();
var orderCount = 0; var orderCount = 0;
await WithUnitOfWorkAsync(async () => await WithUnitOfWorkAsync(async () => { orderCount = await orderRepository.CountAsync(); });
{
orderCount = await orderRepository.CountAsync();
});
// Arrange // Arrange
var checkCreateOrderInput = new CheckCreateOrderInput var checkCreateOrderInput = new CheckCreateOrderInput
@ -172,7 +166,7 @@ namespace EasyAbp.EShop.Orders.Orders
StoreId = OrderTestData.Store1Id, StoreId = OrderTestData.Store1Id,
OrderLines = new List<CreateOrderLineDto> OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id, ProductSkuId = OrderTestData.ProductSku1Id,
@ -206,13 +200,13 @@ namespace EasyAbp.EShop.Orders.Orders
StoreId = OrderTestData.Store1Id, StoreId = OrderTestData.Store1Id,
OrderLines = new List<CreateOrderLineDto> OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id, ProductSkuId = OrderTestData.ProductSku1Id,
Quantity = 10 Quantity = 10
}, },
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku2Id, ProductSkuId = OrderTestData.ProductSku2Id,
@ -684,13 +678,13 @@ namespace EasyAbp.EShop.Orders.Orders
StoreId = OrderTestData.Store1Id, StoreId = OrderTestData.Store1Id,
OrderLines = new List<CreateOrderLineDto> OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id, ProductSkuId = OrderTestData.ProductSku1Id,
Quantity = 10 Quantity = 10
}, },
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku2Id, ProductSkuId = OrderTestData.ProductSku2Id,
@ -725,13 +719,13 @@ namespace EasyAbp.EShop.Orders.Orders
StoreId = OrderTestData.Store1Id, StoreId = OrderTestData.Store1Id,
OrderLines = new List<CreateOrderLineDto> OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku1Id, ProductSkuId = OrderTestData.ProductSku1Id,
Quantity = 10 Quantity = 10
}, },
new CreateOrderLineDto new()
{ {
ProductId = OrderTestData.Product1Id, ProductId = OrderTestData.Product1Id,
ProductSkuId = OrderTestData.ProductSku2Id, ProductSkuId = OrderTestData.ProductSku2Id,

9
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs

@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using NodaMoney; using NodaMoney;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -9,9 +8,9 @@ namespace EasyAbp.EShop.Orders.Orders;
public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientDependency public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientDependency
{ {
public static decimal Sku3UnitPrice { get; set; } = 100m; public static decimal Sku3UnitPrice { get; set; } = 100m;
public async Task<Money?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, public async Task<Money?> GetUnitPriceOrNullAsync(ICreateOrderInfo input, ICreateOrderLineInfo inputOrderLine,
ProductDto product, ProductSkuDto productSku, Currency effectiveCurrency) IProduct product, IProductSku productSku, Currency effectiveCurrency)
{ {
if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku3Id) if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku3Id)
{ {

19
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/DemoOrderDiscountProvider.cs

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Orders.Orders;
public class DemoOrderDiscountProvider : IOrderDiscountProvider
{
public Task<List<OrderDiscountInfoModel>> GetAllAsync(Order order, Dictionary<Guid, IProduct> productDict)
{
return Task.FromResult(new List<OrderDiscountInfoModel>
{
new(order.OrderLines.First().Id, "DemoDiscount1", "1", "Demo Discount 1", 0.01m),
new(order.OrderLines.First().Id, "DemoDiscount2", "2", "Demo Discount 2", 0.1m),
});
}
}

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/InventoryReductionResultTests.cs

@ -53,6 +53,7 @@ public class InventoryReductionResultTests : OrdersDomainTestBase
OrderTestData.Order1Id, OrderTestData.Order1Id,
"Name", "Name",
"Key", "Key",
"DisplayName",
0.3m 0.3m
)); ));
@ -153,6 +154,7 @@ public class InventoryReductionResultTests : OrdersDomainTestBase
var orderExtraFee = eventData.OrderExtraFees[0]; var orderExtraFee = eventData.OrderExtraFees[0];
orderExtraFee.Name.ShouldBe("Name"); orderExtraFee.Name.ShouldBe("Name");
orderExtraFee.Key.ShouldBe("Key"); orderExtraFee.Key.ShouldBe("Key");
orderExtraFee.DisplayName.ShouldBe("DisplayName");
orderExtraFee.TotalAmount.ShouldBe(0.3m); orderExtraFee.TotalAmount.ShouldBe(0.3m);
Order1.CanceledTime.ShouldNotBeNull(); Order1.CanceledTime.ShouldNotBeNull();

65
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDiscountTests.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
namespace EasyAbp.EShop.Orders.Orders;
public class OrderDiscountTests : OrdersDomainTestBase
{
protected override void AfterAddApplication(IServiceCollection services)
{
services.AddTransient<IOrderDiscountProvider, DemoOrderDiscountProvider>();
base.AfterAddApplication(services);
}
[Fact]
public async Task Should_Create_Order_With_Discount()
{
var orderGenerator = GetRequiredService<INewOrderGenerator>();
var createOrderInfoModel = new CreateOrderInfoModel(OrderTestData.Store1Id, null,
new List<CreateOrderLineInfoModel>
{
new(OrderTestData.Product1Id, OrderTestData.ProductSku1Id, 2)
}
);
var order = await orderGenerator.GenerateAsync(Guid.NewGuid(), createOrderInfoModel,
new Dictionary<Guid, IProduct>
{
{
OrderTestData.Product1Id, new ProductEto
{
Id = OrderTestData.Product1Id,
ProductSkus = new List<ProductSkuEto>
{
new()
{
Id = OrderTestData.ProductSku1Id,
AttributeOptionIds = new List<Guid>(),
Price = 1m,
Currency = "USD",
OrderMinQuantity = 1,
OrderMaxQuantity = 100,
}
}
}
}
}, new Dictionary<Guid, DateTime>());
order.ActualTotalPrice.ShouldBe(1.89m);
order.TotalDiscount.ShouldBe(0.11m);
order.OrderDiscounts.Count.ShouldBe(2);
order.OrderDiscounts.ShouldContain(x =>
x.OrderId == order.Id && x.OrderLineId == order.OrderLines.First().Id && x.Name == "DemoDiscount1" &&
x.Key == "1" && x.DisplayName == "Demo Discount 1" && x.DiscountedAmount == 0.01m);
order.OrderDiscounts.ShouldContain(x =>
x.OrderId == order.Id && x.OrderLineId == order.OrderLines.First().Id && x.Name == "DemoDiscount2" &&
x.Key == "2" && x.DisplayName == "Demo Discount 2" && x.DiscountedAmount == 0.1m);
}
}

1
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Domain.Tests/Orders/OrderDomainTests.cs

@ -62,6 +62,7 @@ namespace EasyAbp.EShop.Orders.Orders
OrderTestData.Order1Id, OrderTestData.Order1Id,
"Name", "Name",
"Key", "Key",
"DisplayName",
0.3m 0.3m
)); ));

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

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace /> <RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.HttpApi.Client.ConsoleTestApp/EasyAbp.EShop.Orders.HttpApi.Client.ConsoleTestApp.csproj

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace /> <RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.MongoDB.Tests/EasyAbp.EShop.Orders.MongoDB.Tests.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace /> <RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

2
modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.TestBase/EasyAbp.EShop.Orders.TestBase.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RootNamespace /> <RootNamespace>EasyAbp.EShop.Orders</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/OrderExtraFeeRefundInfoModel.cs

@ -6,9 +6,11 @@ namespace EasyAbp.EShop.Payments.Refunds
public class OrderExtraFeeRefundInfoModel public class OrderExtraFeeRefundInfoModel
{ {
public string Name { get; set; } public string Name { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string DisplayName { get; set; }
public decimal TotalAmount { get; set; } public decimal TotalAmount { get; set; }
} }
} }

6
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain.Shared/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFeeEto.cs

@ -6,9 +6,11 @@ namespace EasyAbp.EShop.Payments.Refunds
public class RefundItemOrderExtraFeeEto public class RefundItemOrderExtraFeeEto
{ {
public string Name { get; set; } public string Name { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string DisplayName { get; set; }
public decimal RefundAmount { get; set; } public decimal RefundAmount { get; set; }
} }
} }

14
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundItemOrderExtraFee.cs

@ -10,21 +10,29 @@ namespace EasyAbp.EShop.Payments.Refunds
{ {
[NotNull] [NotNull]
public virtual string Name { get; protected set; } public virtual string Name { get; protected set; }
[CanBeNull] [CanBeNull]
public virtual string Key { get; protected set; } public virtual string Key { get; protected set; }
[CanBeNull]
public virtual string DisplayName { get; protected set; }
public virtual decimal RefundAmount { get; protected set; } public virtual decimal RefundAmount { get; protected set; }
protected RefundItemOrderExtraFee() protected RefundItemOrderExtraFee()
{ {
} }
public RefundItemOrderExtraFee(Guid id, [NotNull] string name, [CanBeNull] string key, public RefundItemOrderExtraFee(
Guid id,
[NotNull] string name,
[CanBeNull] string key,
[CanBeNull] string displayName,
decimal refundAmount) : base(id) decimal refundAmount) : base(id)
{ {
Name = name; Name = name;
Key = key; Key = key;
DisplayName = displayName;
RefundAmount = refundAmount; RefundAmount = refundAmount;
} }
} }

2
modules/EasyAbp.EShop.Payments/src/EasyAbp.EShop.Payments.Domain/EasyAbp/EShop/Payments/Refunds/RefundSynchronizer.cs

@ -195,7 +195,7 @@ namespace EasyAbp.EShop.Payments.Refunds
{ {
refundItemOrderExtraFeeEntity = new RefundItemOrderExtraFee(_guidGenerator.Create(), refundItemOrderExtraFeeEntity = new RefundItemOrderExtraFee(_guidGenerator.Create(),
orderExtraFeeInfoModel.Name, orderExtraFeeInfoModel.Key, orderExtraFeeInfoModel.Name, orderExtraFeeInfoModel.Key,
orderExtraFeeInfoModel.TotalAmount); orderExtraFeeInfoModel.DisplayName, orderExtraFeeInfoModel.TotalAmount);
refundItem.OrderExtraFees.Add(refundItemOrderExtraFeeEntity); refundItem.OrderExtraFees.Add(refundItemOrderExtraFeeEntity);
} }

2
modules/EasyAbp.EShop.Payments/test/EasyAbp.EShop.Payments.Domain.Tests/Refunds/RefundOrderEventHandlerTests.cs

@ -82,6 +82,7 @@ public class RefundOrderEventHandlerTests : PaymentsDomainTestBase
{ {
Name = "Name", Name = "Name",
Key = "Key", Key = "Key",
DisplayName = "DisplayName",
TotalAmount = 0.6m TotalAmount = 0.6m
}); });
@ -111,6 +112,7 @@ public class RefundOrderEventHandlerTests : PaymentsDomainTestBase
orderExtraFees.Count.ShouldBe(1); orderExtraFees.Count.ShouldBe(1);
orderExtraFees[0].Name.ShouldBe("Name"); orderExtraFees[0].Name.ShouldBe("Name");
orderExtraFees[0].Key.ShouldBe("Key"); orderExtraFees[0].Key.ShouldBe("Key");
orderExtraFees[0].DisplayName.ShouldBe("DisplayName");
orderExtraFees[0].TotalAmount.ShouldBe(0.6m); orderExtraFees[0].TotalAmount.ShouldBe(0.6m);
} }
} }

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

@ -1,12 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Products.Products.Dtos namespace EasyAbp.EShop.Products.Products.Dtos
{ {
[Serializable] [Serializable]
public class ProductDto : ExtensibleFullAuditedEntityDto<Guid>, IProduct public class ProductDto : ExtensibleFullAuditedEntityDto<Guid>, IProduct, IHasProductGroupDisplayName
{ {
public Guid StoreId { get; set; } public Guid StoreId { get; set; }
@ -49,22 +48,5 @@ namespace EasyAbp.EShop.Products.Products.Dtos
IEnumerable<IProductSku> IProduct.ProductSkus => ProductSkus; IEnumerable<IProductSku> IProduct.ProductSkus => ProductSkus;
public List<ProductSkuDto> ProductSkus { get; set; } public List<ProductSkuDto> ProductSkus { get; set; }
public ProductSkuDto GetSkuById(Guid skuId)
{
return ProductSkus.Single(x => x.Id == skuId);
}
public ProductSkuDto FindSkuById(Guid skuId)
{
return ProductSkus.FirstOrDefault(x => x.Id == skuId);
}
public TimeSpan? GetSkuPaymentExpireIn(Guid skuId)
{
var sku = GetSkuById(skuId);
return sku.PaymentExpireIn ?? PaymentExpireIn;
}
} }
} }

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

@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos;
namespace EasyAbp.EShop.Products.Products.Dtos namespace EasyAbp.EShop.Products.Products.Dtos
{ {
[Serializable] [Serializable]
public class ProductViewDto : ExtensibleCreationAuditedEntityDto<Guid>, IProductBase public class ProductViewDto : ExtensibleCreationAuditedEntityDto<Guid>, IProductBase, IHasProductGroupDisplayName
{ {
public Guid StoreId { get; set; } public Guid StoreId { get; set; }
@ -32,6 +32,8 @@ namespace EasyAbp.EShop.Products.Products.Dtos
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public decimal? MinimumPrice { get; set; } public decimal? MinimumPrice { get; set; }
public decimal? MaximumPrice { get; set; } public decimal? MaximumPrice { get; set; }

1
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp.EShop.Products.Domain.Shared.csproj

@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EasyAbp.Abp.Trees.Domain.Shared" Version="$(EasyAbpAbpTreesModuleVersion)" /> <PackageReference Include="EasyAbp.Abp.Trees.Domain.Shared" Version="$(EasyAbpAbpTreesModuleVersion)" />
<PackageReference Include="Volo.Abp.Validation" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.Validation" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Json.Abstractions" Version="$(AbpVersion)" />
<ProjectReference Include="..\..\..\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Domain.Shared\EasyAbp.EShop.Stores.Domain.Shared.csproj" /> <ProjectReference Include="..\..\..\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Domain.Shared\EasyAbp.EShop.Stores.Domain.Shared.csproj" />
</ItemGroup> </ItemGroup>

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

@ -3,6 +3,7 @@ using Volo.Abp.Modularity;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using EasyAbp.EShop.Products.Localization; using EasyAbp.EShop.Products.Localization;
using EasyAbp.EShop.Stores; using EasyAbp.EShop.Stores;
using Volo.Abp.Json;
using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using Volo.Abp.Validation.Localization; using Volo.Abp.Validation.Localization;
@ -12,6 +13,7 @@ namespace EasyAbp.EShop.Products
{ {
[DependsOn( [DependsOn(
typeof(AbpValidationModule), typeof(AbpValidationModule),
typeof(AbpJsonAbstractionsModule),
typeof(AbpTreesDomainSharedModule), typeof(AbpTreesDomainSharedModule),
typeof(EShopStoresDomainSharedModule) typeof(EShopStoresDomainSharedModule)
)] )]

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

@ -0,0 +1,9 @@
using JetBrains.Annotations;
namespace EasyAbp.EShop.Products.Products;
public interface IHasProductGroupDisplayName
{
[NotNull]
string ProductGroupDisplayName { get; }
}

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductAttribute.cs

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Data; using Volo.Abp.Data;
@ -6,6 +7,8 @@ namespace EasyAbp.EShop.Products.Products
{ {
public interface IProductAttribute : IHasExtraProperties public interface IProductAttribute : IHasExtraProperties
{ {
Guid Id { get; }
[NotNull] [NotNull]
string DisplayName { get; } string DisplayName { get; }

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductAttributeOption.cs

@ -1,10 +1,13 @@
using JetBrains.Annotations; using System;
using JetBrains.Annotations;
using Volo.Abp.Data; using Volo.Abp.Data;
namespace EasyAbp.EShop.Products.Products namespace EasyAbp.EShop.Products.Products
{ {
public interface IProductAttributeOption : IHasExtraProperties public interface IProductAttributeOption : IHasExtraProperties
{ {
Guid Id { get; }
[NotNull] [NotNull]
string DisplayName { get; } string DisplayName { get; }

12
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductBase.cs

@ -7,14 +7,23 @@ namespace EasyAbp.EShop.Products.Products
{ {
public interface IProductBase : IHasExtraProperties, IMultiStore public interface IProductBase : IHasExtraProperties, IMultiStore
{ {
Guid Id { get; }
[NotNull]
string ProductGroupName { get; } string ProductGroupName { get; }
Guid? ProductDetailId { get; } Guid? ProductDetailId { get; }
[CanBeNull]
string UniqueName { get; } string UniqueName { get; }
[NotNull]
string DisplayName { get; } string DisplayName { get; }
/// <summary>
/// Tell your customer what the product is. It is usually shown in the product list.
/// </summary>
[CanBeNull]
string Overview { get; } string Overview { get; }
InventoryStrategy InventoryStrategy { get; } InventoryStrategy InventoryStrategy { get; }
@ -22,6 +31,7 @@ namespace EasyAbp.EShop.Products.Products
[CanBeNull] [CanBeNull]
string InventoryProviderName { get; } string InventoryProviderName { get; }
[CanBeNull]
string MediaResources { get; } string MediaResources { get; }
int DisplayOrder { get; } int DisplayOrder { get; }
@ -31,5 +41,7 @@ namespace EasyAbp.EShop.Products.Products
bool IsStatic { get; } bool IsStatic { get; }
bool IsHidden { get; } bool IsHidden { get; }
TimeSpan? PaymentExpireIn { get; }
} }
} }

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

@ -6,6 +6,8 @@ namespace EasyAbp.EShop.Products.Products
{ {
public interface IProductSku : IHasAttributeOptionIds, IHasExtraProperties public interface IProductSku : IHasAttributeOptionIds, IHasExtraProperties
{ {
Guid Id { get; }
[CanBeNull] [CanBeNull]
string Name { get; } string Name { get; }
@ -20,6 +22,8 @@ namespace EasyAbp.EShop.Products.Products
int OrderMaxQuantity { get; } int OrderMaxQuantity { get; }
TimeSpan? PaymentExpireIn { get; }
[CanBeNull] [CanBeNull]
string MediaResources { get; } string MediaResources { get; }

3
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/IProductSkuDescriptionProvider.cs → modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/IProductSkuDescriptionProvider.cs

@ -1,10 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Products.Products namespace EasyAbp.EShop.Products.Products
{ {
public interface IProductSkuDescriptionProvider public interface IProductSkuDescriptionProvider
{ {
Task<string> GenerateAsync(ProductDto productDto, ProductSkuDto productSkuDto); Task<string> GenerateAsync(IProduct product, IProductSku productSku);
} }
} }

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

@ -37,6 +37,8 @@ namespace EasyAbp.EShop.Products.Products
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
IEnumerable<IProductAttribute> IProduct.ProductAttributes => ProductAttributes; IEnumerable<IProductAttribute> IProduct.ProductAttributes => ProductAttributes;
public List<ProductAttributeEto> ProductAttributes { get; set; } public List<ProductAttributeEto> ProductAttributes { get; set; }

24
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductExtensions.cs

@ -0,0 +1,24 @@
using System;
using System.Linq;
namespace EasyAbp.EShop.Products.Products;
public static class ProductExtensions
{
public static IProductSku GetSkuById(this IProduct product, Guid skuId)
{
return product.ProductSkus.Single(x => x.Id == skuId);
}
public static IProductSku FindSkuById(this IProduct product, Guid skuId)
{
return product.ProductSkus.FirstOrDefault(x => x.Id == skuId);
}
public static TimeSpan? GetSkuPaymentExpireIn(this IProduct product, Guid skuId)
{
var sku = product.GetSkuById(skuId);
return sku.PaymentExpireIn ?? product.PaymentExpireIn;
}
}

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application.Contracts/EasyAbp/EShop/Products/Products/ProductSkuDescriptionProvider.cs → modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain.Shared/EasyAbp/EShop/Products/Products/ProductSkuDescriptionProvider.cs

@ -1,7 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Json; using Volo.Abp.Json;
@ -16,13 +15,13 @@ namespace EasyAbp.EShop.Products.Products
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
} }
public virtual Task<string> GenerateAsync(ProductDto productDto, ProductSkuDto productSkuDto) public virtual Task<string> GenerateAsync(IProduct product, IProductSku productSku)
{ {
var names = new Collection<string[]>(); var names = new Collection<string[]>();
foreach (var attributeOptionId in productSkuDto.AttributeOptionIds) foreach (var attributeOptionId in productSku.AttributeOptionIds)
{ {
names.Add(productDto.ProductAttributes.SelectMany( names.Add(product.ProductAttributes.SelectMany(
attribute => attribute.ProductAttributeOptions.Where(option => option.Id == attributeOptionId), attribute => attribute.ProductAttributeOptions.Where(option => option.Id == attributeOptionId),
(attribute, option) => new [] {attribute.DisplayName, option.DisplayName}).Single()); (attribute, option) => new [] {attribute.DisplayName, option.DisplayName}).Single());
} }

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

@ -23,6 +23,8 @@ namespace EasyAbp.EShop.Products.Products
public int OrderMaxQuantity { get; set; } public int OrderMaxQuantity { get; set; }
public TimeSpan? PaymentExpireIn { get; }
public string MediaResources { get; set; } public string MediaResources { get; set; }
public Guid? ProductDetailId { get; set; } public Guid? ProductDetailId { get; set; }

7
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Options/ProductGroups/IProductGroup.cs

@ -1,7 +0,0 @@
namespace EasyAbp.EShop.Products.Options.ProductGroups
{
public interface IProductGroup
{
}
}

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

@ -14,21 +14,14 @@ namespace EasyAbp.EShop.Products.Products
public virtual Guid StoreId { get; protected set; } public virtual Guid StoreId { get; protected set; }
[NotNull]
public virtual string ProductGroupName { get; protected set; } public virtual string ProductGroupName { get; protected set; }
public virtual Guid? ProductDetailId { get; protected set; } public virtual Guid? ProductDetailId { get; protected set; }
[CanBeNull]
public virtual string UniqueName { get; protected set; } public virtual string UniqueName { get; protected set; }
[NotNull]
public virtual string DisplayName { get; protected set; } public virtual string DisplayName { get; protected set; }
/// <summary>
/// Tell your customer what the product is. It is usually shown in the product list.
/// </summary>
[CanBeNull]
public virtual string Overview { get; protected set; } public virtual string Overview { get; protected set; }
public virtual InventoryStrategy InventoryStrategy { get; protected set; } public virtual InventoryStrategy InventoryStrategy { get; protected set; }
@ -39,7 +32,6 @@ namespace EasyAbp.EShop.Products.Products
/// </summary> /// </summary>
public virtual string InventoryProviderName { get; protected set; } public virtual string InventoryProviderName { get; protected set; }
[CanBeNull]
public virtual string MediaResources { get; protected set; } public virtual string MediaResources { get; protected set; }
public virtual int DisplayOrder { get; protected set; } public virtual int DisplayOrder { get; protected set; }

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

@ -4,7 +4,8 @@ using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Products.Products namespace EasyAbp.EShop.Products.Products
{ {
public class ProductView : CreationAuditedAggregateRoot<Guid>, IProductBase, IMultiTenant public class ProductView : CreationAuditedAggregateRoot<Guid>,
IProductBase, IHasProductGroupDisplayName, IMultiTenant
{ {
public virtual Guid? TenantId { get; protected set; } public virtual Guid? TenantId { get; protected set; }
@ -36,6 +37,8 @@ namespace EasyAbp.EShop.Products.Products
public virtual bool IsHidden { get; protected set; } public virtual bool IsHidden { get; protected set; }
public virtual TimeSpan? PaymentExpireIn { get; protected set; }
#endregion #endregion
public virtual string ProductGroupDisplayName { get; protected set; } public virtual string ProductGroupDisplayName { get; protected set; }
@ -64,6 +67,7 @@ namespace EasyAbp.EShop.Products.Products
bool isPublished, bool isPublished,
bool isStatic, bool isStatic,
bool isHidden, bool isHidden,
TimeSpan? paymentExpireIn,
string mediaResources, string mediaResources,
int displayOrder, int displayOrder,
string productGroupDisplayName, string productGroupDisplayName,
@ -84,6 +88,7 @@ namespace EasyAbp.EShop.Products.Products
IsPublished = isPublished; IsPublished = isPublished;
IsStatic = isStatic; IsStatic = isStatic;
IsHidden = isHidden; IsHidden = isHidden;
PaymentExpireIn = paymentExpireIn;
MediaResources = mediaResources; MediaResources = mediaResources;
DisplayOrder = displayOrder; DisplayOrder = displayOrder;

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

@ -27,7 +27,7 @@ public class BasicBasketItemProductInfoUpdater : IBasketItemProductInfoUpdater,
protected virtual async Task InternalUpdateAsync(int targetQuantity, IBasketItem item, ProductDto productDto) protected virtual async Task InternalUpdateAsync(int targetQuantity, IBasketItem item, ProductDto productDto)
{ {
var productSkuDto = productDto.FindSkuById(item.ProductSkuId); var productSkuDto = (ProductSkuDto)productDto.FindSkuById(item.ProductSkuId);
if (productSkuDto == null) if (productSkuDto == null)
{ {

10
plugins/Booking/src/EasyAbp.EShop.Orders.Booking.Application/EasyAbp/EShop/Orders/Booking/Authorization/BookingOrderCreationAuthorizationHandler.cs

@ -116,7 +116,7 @@ namespace EasyAbp.EShop.Orders.Booking.Authorization
} }
} }
protected virtual OccupyAssetInfoModel CreateOccupyAssetInfoModel(Guid assetId, CreateOrderLineDto orderLine) protected virtual OccupyAssetInfoModel CreateOccupyAssetInfoModel(Guid assetId, ICreateOrderLineInfo orderLine)
{ {
return new OccupyAssetInfoModel( return new OccupyAssetInfoModel(
assetId, assetId,
@ -128,7 +128,7 @@ namespace EasyAbp.EShop.Orders.Booking.Authorization
} }
protected virtual OccupyAssetByCategoryInfoModel CreateOccupyAssetByCategoryInfoModel(Guid assetCategoryId, protected virtual OccupyAssetByCategoryInfoModel CreateOccupyAssetByCategoryInfoModel(Guid assetCategoryId,
CreateOrderLineDto orderLine) ICreateOrderLineInfo orderLine)
{ {
return new OccupyAssetByCategoryInfoModel( return new OccupyAssetByCategoryInfoModel(
assetCategoryId, assetCategoryId,
@ -140,7 +140,7 @@ namespace EasyAbp.EShop.Orders.Booking.Authorization
); );
} }
protected virtual async Task<bool> IsAssetInfoValidAsync(CreateOrderLineDto orderLine, protected virtual async Task<bool> IsAssetInfoValidAsync(ICreateOrderLineInfo orderLine,
OrderCreationResource resource) OrderCreationResource resource)
{ {
var mapping = (await _grantedStoreAppService.GetListAsync(new GetGrantedStoreListDto var mapping = (await _grantedStoreAppService.GetListAsync(new GetGrantedStoreListDto
@ -179,7 +179,7 @@ namespace EasyAbp.EShop.Orders.Booking.Authorization
return productAsset is not null; return productAsset is not null;
} }
protected virtual async Task<bool> IsAssetCategoryInfoValidAsync(CreateOrderLineDto orderLine, protected virtual async Task<bool> IsAssetCategoryInfoValidAsync(ICreateOrderLineInfo orderLine,
OrderCreationResource resource) OrderCreationResource resource)
{ {
var mapping = (await _grantedStoreAppService.GetListAsync(new GetGrantedStoreListDto var mapping = (await _grantedStoreAppService.GetListAsync(new GetGrantedStoreListDto
@ -218,7 +218,7 @@ namespace EasyAbp.EShop.Orders.Booking.Authorization
return productAssetCategory is not null; return productAssetCategory is not null;
} }
protected virtual async Task<bool> IsPeriodInfoValidAsync(CreateOrderLineDto orderLine) protected virtual async Task<bool> IsPeriodInfoValidAsync(ICreateOrderLineInfo orderLine)
{ {
var periodSchemeId = orderLine.GetBookingPeriodSchemeId(); var periodSchemeId = orderLine.GetBookingPeriodSchemeId();
var periodId = orderLine.GetBookingPeriodId(); var periodId = orderLine.GetBookingPeriodId();

13
plugins/Booking/src/EasyAbp.EShop.Orders.Booking.Application/EasyAbp/EShop/Orders/Booking/BookingOrderLinePriceOverrider.cs

@ -6,6 +6,7 @@ using EasyAbp.EShop.Plugins.Booking.ProductAssetCategories;
using EasyAbp.EShop.Plugins.Booking.ProductAssetCategories.Dtos; using EasyAbp.EShop.Plugins.Booking.ProductAssetCategories.Dtos;
using EasyAbp.EShop.Plugins.Booking.ProductAssets; using EasyAbp.EShop.Plugins.Booking.ProductAssets;
using EasyAbp.EShop.Plugins.Booking.ProductAssets.Dtos; using EasyAbp.EShop.Plugins.Booking.ProductAssets.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos; using EasyAbp.EShop.Products.Products.Dtos;
using NodaMoney; using NodaMoney;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -25,8 +26,8 @@ public class BookingOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransie
_productAssetCategoryAppService = productAssetCategoryAppService; _productAssetCategoryAppService = productAssetCategoryAppService;
} }
public virtual async Task<Money?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, public virtual async Task<Money?> GetUnitPriceOrNullAsync(ICreateOrderInfo input,
ProductDto product, ProductSkuDto productSku, Currency effectiveCurrency) ICreateOrderLineInfo inputOrderLine, IProduct product, IProductSku productSku, Currency effectiveCurrency)
{ {
if (inputOrderLine.FindBookingAssetId() is not null) if (inputOrderLine.FindBookingAssetId() is not null)
{ {
@ -41,8 +42,8 @@ public class BookingOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransie
return null; return null;
} }
public virtual async Task<Money?> GetAssetBookingUnitPriceAsync(CreateOrderDto input, public virtual async Task<Money?> GetAssetBookingUnitPriceAsync(ICreateOrderInfo input,
CreateOrderLineDto inputOrderLine, Currency effectiveCurrency) ICreateOrderLineInfo inputOrderLine, Currency effectiveCurrency)
{ {
var productAsset = (await _productAssetAppService.GetListAsync( var productAsset = (await _productAssetAppService.GetListAsync(
new GetProductAssetListDto new GetProductAssetListDto
@ -76,8 +77,8 @@ public class BookingOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransie
return null; return null;
} }
public virtual async Task<Money?> GetAssetCategoryBookingUnitPriceAsync(CreateOrderDto input, public virtual async Task<Money?> GetAssetCategoryBookingUnitPriceAsync(ICreateOrderInfo input,
CreateOrderLineDto inputOrderLine, Currency effectiveCurrency) ICreateOrderLineInfo inputOrderLine, Currency effectiveCurrency)
{ {
var productAssetCategory = (await _productAssetCategoryAppService.GetListAsync( var productAssetCategory = (await _productAssetCategoryAppService.GetListAsync(
new GetProductAssetCategoryListDto new GetProductAssetCategoryListDto

35
plugins/Booking/src/EasyAbp.EShop.Plugins.Booking.Application.Contracts/EasyAbp/EShop/Orders/CreateOrderLineDtoExtensions.cs → plugins/Booking/src/EasyAbp.EShop.Plugins.Booking.Application.Contracts/EasyAbp/EShop/Orders/CreateOrderLineInfoExtensions.cs

@ -1,94 +1,95 @@
using System; using System;
using EasyAbp.EShop.Orders.Orders;
using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Orders.Orders.Dtos;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Data; using Volo.Abp.Data;
namespace EasyAbp.EShop.Orders; namespace EasyAbp.EShop.Orders;
public static class CreateOrderLineDtoExtensions public static class CreateOrderLineInfoExtensions
{ {
public static Guid? FindBookingAssetId(this CreateOrderLineDto orderLine) public static Guid? FindBookingAssetId(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingAssetId); return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingAssetId);
} }
public static Guid GetBookingAssetId(this CreateOrderLineDto orderLine) public static Guid GetBookingAssetId(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingAssetId(orderLine), return Check.NotNull(FindBookingAssetId(orderLine),
BookingOrderProperties.OrderLineBookingAssetId)!.Value; BookingOrderProperties.OrderLineBookingAssetId)!.Value;
} }
public static Guid? FindBookingAssetCategoryId(this CreateOrderLineDto orderLine) public static Guid? FindBookingAssetCategoryId(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingAssetCategoryId); return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingAssetCategoryId);
} }
public static Guid GetBookingAssetCategoryId(this CreateOrderLineDto orderLine) public static Guid GetBookingAssetCategoryId(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingAssetCategoryId(orderLine), return Check.NotNull(FindBookingAssetCategoryId(orderLine),
BookingOrderProperties.OrderLineBookingAssetCategoryId)!.Value; BookingOrderProperties.OrderLineBookingAssetCategoryId)!.Value;
} }
public static Guid? FindBookingPeriodSchemeId(this CreateOrderLineDto orderLine) public static Guid? FindBookingPeriodSchemeId(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingPeriodSchemeId); return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingPeriodSchemeId);
} }
public static Guid GetBookingPeriodSchemeId(this CreateOrderLineDto orderLine) public static Guid GetBookingPeriodSchemeId(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingPeriodSchemeId(orderLine), return Check.NotNull(FindBookingPeriodSchemeId(orderLine),
BookingOrderProperties.OrderLineBookingPeriodSchemeId)!.Value; BookingOrderProperties.OrderLineBookingPeriodSchemeId)!.Value;
} }
public static Guid? FindBookingPeriodId(this CreateOrderLineDto orderLine) public static Guid? FindBookingPeriodId(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingPeriodId); return orderLine.GetProperty<Guid?>(BookingOrderProperties.OrderLineBookingPeriodId);
} }
public static Guid GetBookingPeriodId(this CreateOrderLineDto orderLine) public static Guid GetBookingPeriodId(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingPeriodId(orderLine), return Check.NotNull(FindBookingPeriodId(orderLine),
BookingOrderProperties.OrderLineBookingPeriodId)!.Value; BookingOrderProperties.OrderLineBookingPeriodId)!.Value;
} }
public static int? FindBookingVolume(this CreateOrderLineDto orderLine) public static int? FindBookingVolume(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.Quantity; return orderLine.Quantity;
} }
public static int GetBookingVolume(this CreateOrderLineDto orderLine) public static int GetBookingVolume(this ICreateOrderLineInfo orderLine)
{ {
return FindBookingVolume(orderLine)!.Value; return FindBookingVolume(orderLine)!.Value;
} }
public static DateTime? FindBookingDate(this CreateOrderLineDto orderLine) public static DateTime? FindBookingDate(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.FindDateTimeProperty(BookingOrderProperties.OrderLineBookingDate); return orderLine.FindDateTimeProperty(BookingOrderProperties.OrderLineBookingDate);
} }
public static DateTime GetBookingDate(this CreateOrderLineDto orderLine) public static DateTime GetBookingDate(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingDate(orderLine), return Check.NotNull(FindBookingDate(orderLine),
BookingOrderProperties.OrderLineBookingDate)!.Value; BookingOrderProperties.OrderLineBookingDate)!.Value;
} }
public static TimeSpan? FindBookingStartingTime(this CreateOrderLineDto orderLine) public static TimeSpan? FindBookingStartingTime(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.FindTimeSpanProperty(BookingOrderProperties.OrderLineBookingStartingTime); return orderLine.FindTimeSpanProperty(BookingOrderProperties.OrderLineBookingStartingTime);
} }
public static TimeSpan GetBookingStartingTime(this CreateOrderLineDto orderLine) public static TimeSpan GetBookingStartingTime(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingStartingTime(orderLine), return Check.NotNull(FindBookingStartingTime(orderLine),
BookingOrderProperties.OrderLineBookingStartingTime)!.Value; BookingOrderProperties.OrderLineBookingStartingTime)!.Value;
} }
public static TimeSpan? FindBookingDuration(this CreateOrderLineDto orderLine) public static TimeSpan? FindBookingDuration(this ICreateOrderLineInfo orderLine)
{ {
return orderLine.FindTimeSpanProperty(BookingOrderProperties.OrderLineBookingDuration); return orderLine.FindTimeSpanProperty(BookingOrderProperties.OrderLineBookingDuration);
} }
public static TimeSpan GetBookingDuration(this CreateOrderLineDto orderLine) public static TimeSpan GetBookingDuration(this ICreateOrderLineInfo orderLine)
{ {
return Check.NotNull(FindBookingDuration(orderLine), return Check.NotNull(FindBookingDuration(orderLine),
BookingOrderProperties.OrderLineBookingDuration)!.Value; BookingOrderProperties.OrderLineBookingDuration)!.Value;

85
plugins/Coupons/src/EasyAbp.EShop.Orders.Plugins.Coupons/EasyAbp/EShop/Orders/Plugins/Coupons/OrderDiscount/CouponOrderDiscountProvider.cs

@ -7,7 +7,8 @@ using EasyAbp.EShop.Plugins.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Coupons; using EasyAbp.EShop.Plugins.Coupons.Coupons;
using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos; using EasyAbp.EShop.Plugins.Coupons.Coupons.Dtos;
using EasyAbp.EShop.Plugins.Coupons.CouponTemplates; using EasyAbp.EShop.Plugins.Coupons.CouponTemplates;
using EasyAbp.EShop.Products.Products.Dtos; using EasyAbp.EShop.Products.Products;
using NodaMoney;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing; using Volo.Abp.Timing;
@ -16,6 +17,8 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{ {
public class CouponOrderDiscountProvider : IOrderDiscountProvider, ITransientDependency public class CouponOrderDiscountProvider : IOrderDiscountProvider, ITransientDependency
{ {
public static string OrderDiscountName { get; set; } = "Coupon";
private readonly IClock _clock; private readonly IClock _clock;
private readonly ICouponAppService _couponAppService; private readonly ICouponAppService _couponAppService;
private readonly ICouponLookupService _couponLookupService; private readonly ICouponLookupService _couponLookupService;
@ -32,13 +35,14 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
_couponLookupService = couponLookupService; _couponLookupService = couponLookupService;
_couponTemplateLookupService = couponTemplateLookupService; _couponTemplateLookupService = couponTemplateLookupService;
} }
public virtual async Task<Order> DiscountAsync(Order order, Dictionary<Guid, ProductDto> productDict) public virtual async Task<List<OrderDiscountInfoModel>> GetAllAsync(Order order,
Dictionary<Guid, IProduct> productDict)
{ {
var couponId = order.GetProperty<Guid?>(CouponsConsts.OrderCouponIdPropertyName); var couponId = order.GetProperty<Guid?>(CouponsConsts.OrderCouponIdPropertyName);
if (couponId is null) if (couponId is null)
{ {
return order; return new List<OrderDiscountInfoModel>();
} }
var now = _clock.Now; var now = _clock.Now;
@ -54,7 +58,7 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{ {
throw new CouponHasBeenOccupiedException(); throw new CouponHasBeenOccupiedException();
} }
var couponTemplate = await _couponTemplateLookupService.FindByIdAsync(coupon.CouponTemplateId); var couponTemplate = await _couponTemplateLookupService.FindByIdAsync(coupon.CouponTemplateId);
if (couponTemplate == null || if (couponTemplate == null ||
@ -65,12 +69,12 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
} }
var orderLinesInScope = GetOrderLinesInScope(couponTemplate, order, productDict); var orderLinesInScope = GetOrderLinesInScope(couponTemplate, order, productDict);
DiscountOrderLines(couponTemplate, order, orderLinesInScope);
await _couponAppService.OccupyAsync(coupon.Id, new OccupyCouponInput {OrderId = order.Id});
return order; var models = await DiscountOrderLinesAsync(couponTemplate, coupon, order, orderLinesInScope);
await _couponAppService.OccupyAsync(coupon.Id, new OccupyCouponInput { OrderId = order.Id });
return models;
} }
protected virtual bool IsCurrencyExpected(CouponTemplateData couponTemplate, Order order) protected virtual bool IsCurrencyExpected(CouponTemplateData couponTemplate, Order order)
@ -78,21 +82,25 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
return couponTemplate.Currency == order.Currency; return couponTemplate.Currency == order.Currency;
} }
protected virtual void DiscountOrderLines(CouponTemplateData couponTemplate, Order order, protected virtual Task<List<OrderDiscountInfoModel>> DiscountOrderLinesAsync(CouponTemplateData couponTemplate,
List<OrderLine> orderLinesInScope) CouponData coupon, Order order, List<OrderLine> orderLinesInScope)
{ {
// Todo: support Custom coupon. // Todo: support Custom coupon.
if (couponTemplate.CouponType == CouponType.Custom) if (couponTemplate.CouponType == CouponType.Custom)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
var totalOrderLineActualTotalPrice = orderLinesInScope.Sum(x => x.ActualTotalPrice);
var totalDiscountedAmount = couponTemplate.CouponType == CouponType.PerMeet var nodaCurrency = Currency.FromCode(order.Currency);
? couponTemplate.DiscountAmount * var totalOrderLineActualTotalPrice =
Math.Floor(totalOrderLineActualTotalPrice / couponTemplate.ConditionAmount) new Money(orderLinesInScope.Sum(x => x.ActualTotalPrice), nodaCurrency);
: couponTemplate.DiscountAmount;
var totalDiscountedAmount = new Money(
couponTemplate.CouponType == CouponType.PerMeet
? couponTemplate.DiscountAmount *
Math.Floor(totalOrderLineActualTotalPrice.Amount / couponTemplate.ConditionAmount)
: couponTemplate.DiscountAmount,
nodaCurrency);
if (totalDiscountedAmount > totalOrderLineActualTotalPrice) if (totalDiscountedAmount > totalOrderLineActualTotalPrice)
{ {
@ -101,21 +109,21 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
var remainingDiscountedAmount = totalDiscountedAmount; var remainingDiscountedAmount = totalDiscountedAmount;
// Todo: https://github.com/EasyAbp/EShop/issues/104 var orderLineDiscounts = new Dictionary<OrderLine, Money>();
const int accuracy = 2;
foreach (var orderLine in orderLinesInScope) foreach (var orderLine in orderLinesInScope)
{ {
var orderLineActualTotalPrice = new Money(orderLine.ActualTotalPrice, nodaCurrency);
var maxDiscountAmount = var maxDiscountAmount =
Math.Round(orderLine.ActualTotalPrice / totalOrderLineActualTotalPrice * totalDiscountedAmount, new Money(
accuracy, MidpointRounding.ToZero); orderLineActualTotalPrice.Amount / totalOrderLineActualTotalPrice.Amount *
totalDiscountedAmount.Amount, nodaCurrency, MidpointRounding.ToZero);
var discountAmount = maxDiscountAmount > orderLine.ActualTotalPrice var discountAmount = maxDiscountAmount > totalOrderLineActualTotalPrice
? orderLine.ActualTotalPrice ? orderLineActualTotalPrice
: maxDiscountAmount; : maxDiscountAmount;
order.AddDiscount(orderLine.Id, discountAmount); orderLineDiscounts[orderLine] = discountAmount;
remainingDiscountedAmount -= discountAmount; remainingDiscountedAmount -= discountAmount;
} }
@ -125,13 +133,12 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{ {
break; break;
} }
var discountAmount = remainingDiscountedAmount > orderLine.ActualTotalPrice var discountAmount = remainingDiscountedAmount > totalOrderLineActualTotalPrice
? orderLine.ActualTotalPrice ? totalOrderLineActualTotalPrice
: remainingDiscountedAmount; : remainingDiscountedAmount;
order.AddDiscount(orderLine.Id, discountAmount);
orderLineDiscounts[orderLine] += discountAmount;
remainingDiscountedAmount -= discountAmount; remainingDiscountedAmount -= discountAmount;
} }
@ -139,12 +146,19 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
{ {
throw new ApplicationException(); throw new ApplicationException();
} }
order.SetProperty(CouponsConsts.OrderCouponDiscountAmountPropertyName, totalDiscountedAmount); order.SetProperty(CouponsConsts.OrderCouponDiscountAmountPropertyName, totalDiscountedAmount);
var models = orderLinesInScope.Select(orderLine =>
new OrderDiscountInfoModel(orderLine.Id, OrderDiscountName, coupon.Id.ToString(),
couponTemplate.DisplayName, orderLineDiscounts[orderLine].Amount)
).ToList();
return Task.FromResult(models);
} }
protected virtual List<OrderLine> GetOrderLinesInScope(CouponTemplateData couponTemplate, Order order, protected virtual List<OrderLine> GetOrderLinesInScope(CouponTemplateData couponTemplate, Order order,
Dictionary<Guid, ProductDto> productDict) Dictionary<Guid, IProduct> productDict)
{ {
if (couponTemplate.IsUnscoped) if (couponTemplate.IsUnscoped)
{ {
@ -176,7 +190,8 @@ namespace EasyAbp.EShop.Orders.Plugins.Coupons.OrderDiscount
return expectedOrderLines; return expectedOrderLines;
} }
protected virtual decimal GetOrderLineProductPrice(OrderLine orderLine, Dictionary<Guid, ProductDto> productDict) protected virtual decimal GetOrderLineProductPrice(OrderLine orderLine,
Dictionary<Guid, IProduct> productDict)
{ {
return productDict[orderLine.ProductId].GetSkuById(orderLine.ProductSkuId).Price; return productDict[orderLine.ProductId].GetSkuById(orderLine.ProductSkuId).Price;
} }

10
plugins/Coupons/src/EasyAbp.EShop.Plugins.Coupons.Domain.Shared/EasyAbp/EShop/Plugins/Coupons/Coupons/ICoupon.cs

@ -4,16 +4,18 @@ namespace EasyAbp.EShop.Plugins.Coupons.Coupons
{ {
public interface ICoupon public interface ICoupon
{ {
Guid Id { get; }
Guid CouponTemplateId { get; } Guid CouponTemplateId { get; }
Guid UserId { get; } Guid UserId { get; }
Guid? OrderId { get; } Guid? OrderId { get; }
DateTime? ExpirationTime { get; } DateTime? ExpirationTime { get; }
DateTime? UsedTime { get; } DateTime? UsedTime { get; }
decimal? DiscountedAmount { get; } decimal? DiscountedAmount { get; }
} }
} }

48
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
@ -58,7 +59,7 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
public virtual async Task HandleEventAsync(CreateFlashSaleOrderEto eventData) public virtual async Task HandleEventAsync(CreateFlashSaleOrderEto eventData)
{ {
var product = await ProductAppService.GetAsync(eventData.Plan.ProductId); var product = await ProductAppService.GetAsync(eventData.Plan.ProductId);
var productSku = product.GetSkuById(eventData.Plan.ProductSkuId); var productSku = (ProductSkuDto)product.GetSkuById(eventData.Plan.ProductSkuId);
if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken)) if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken))
{ {
@ -84,9 +85,11 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
var productDict = await GetProductDictionaryAsync(product); var productDict = await GetProductDictionaryAsync(product);
var productDetailDict = await GetProductDetailDictionaryAsync(product, productSku); var productDetailModificationTimeDict =
await GetProductDetailModificationTimeDictionaryAsync(input, productDict);
var order = await NewOrderGenerator.GenerateAsync(eventData.UserId, input, productDict, productDetailDict); var order = await NewOrderGenerator.GenerateAsync(eventData.UserId, input, productDict,
productDetailModificationTimeDict);
await OrderRepository.InsertAsync(order, autoSave: true); await OrderRepository.InsertAsync(order, autoSave: true);
@ -107,25 +110,30 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
}); });
} }
protected virtual Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(ProductDto product) protected virtual Task<Dictionary<Guid, IProduct>> GetProductDictionaryAsync(ProductDto product)
{ {
var productDict = new Dictionary<Guid, ProductDto>() var productDict = new Dictionary<Guid, IProduct>() { { product.Id, product } };
{
{product.Id, product}
};
return Task.FromResult(productDict); return Task.FromResult(productDict);
} }
protected virtual async Task<Dictionary<Guid, ProductDetailDto>> GetProductDetailDictionaryAsync(ProductDto product, ProductSkuDto productSku) protected virtual async Task<Dictionary<Guid, DateTime>> GetProductDetailModificationTimeDictionaryAsync(
ICreateOrderInfo input, Dictionary<Guid, IProduct> productDict)
{ {
var dict = new Dictionary<Guid, ProductDetailDto>(); var productDetailIds = input.OrderLines
.Select(dto =>
productDict[dto.ProductId].GetSkuById(dto.ProductSkuId).ProductDetailId ??
productDict[dto.ProductId].ProductDetailId)
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; var dict = new Dictionary<Guid, DateTime>();
if (productDetailId.HasValue) foreach (var productDetailId in productDetailIds.Distinct())
{ {
dict.Add(productDetailId.Value, await ProductDetailAppService.GetAsync(productDetailId.Value)); var product = await ProductDetailAppService.GetAsync(productDetailId);
dict.Add(productDetailId, product.LastModificationTime ?? product.CreationTime);
} }
return dict; return dict;
@ -133,13 +141,13 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
protected virtual Task<CreateOrderDto> ConvertToCreateOrderDtoAsync(CreateFlashSaleOrderEto eventData) protected virtual Task<CreateOrderDto> ConvertToCreateOrderDtoAsync(CreateFlashSaleOrderEto eventData)
{ {
var input = new CreateOrderDto() var input = new CreateOrderDto
{ {
StoreId = eventData.Plan.StoreId, StoreId = eventData.Plan.StoreId,
CustomerRemark = eventData.CustomerRemark, CustomerRemark = eventData.CustomerRemark,
OrderLines = new List<CreateOrderLineDto>() OrderLines = new List<CreateOrderLineDto>
{ {
new CreateOrderLineDto() new()
{ {
ProductId = eventData.Plan.ProductId, ProductId = eventData.Plan.ProductId,
ProductSkuId = eventData.Plan.ProductSkuId, ProductSkuId = eventData.Plan.ProductSkuId,
@ -153,10 +161,12 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
return Task.FromResult(input); return Task.FromResult(input);
} }
protected virtual async Task<bool> ValidateHashTokenAsync(FlashSalePlanEto plan, ProductDto product, ProductSkuDto productSku, string originHashToken) protected virtual async Task<bool> ValidateHashTokenAsync(FlashSalePlanEto plan, ProductDto product,
ProductSkuDto productSku, string originHashToken)
{ {
var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime,
productSku.LastModificationTime);
return string.Equals(hashToken, originHashToken, StringComparison.InvariantCulture); return string.Equals(hashToken, originHashToken, StringComparison.InvariantCulture);
} }
} }

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs

@ -207,7 +207,7 @@ public class FlashSalePlanAppService :
var plan = await GetFlashSalePlanCacheAsync(id); var plan = await GetFlashSalePlanCacheAsync(id);
var product = await ProductCache.GetAsync(plan.ProductId); var product = await ProductCache.GetAsync(plan.ProductId);
var productSku = product.GetSkuById(plan.ProductSkuId); var productSku = (ProductSkuDto)product.GetSkuById(plan.ProductSkuId);
var expiresTime = DateTimeOffset.Now.Add(Options.PreOrderExpires); var expiresTime = DateTimeOffset.Now.Add(Options.PreOrderExpires);
await ValidatePreOrderAsync(plan, product, productSku); await ValidatePreOrderAsync(plan, product, productSku);

6
plugins/FlashSales/test/EasyAbp.EShop.Plugins.FlashSales.Application.Tests/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppServiceTests.cs

@ -309,7 +309,8 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
var plan = await CreateFlashSalePlanAsync(); var plan = await CreateFlashSalePlanAsync();
var preOrderCacheKey = string.Format(FlashSalePlanAppService.PreOrderCacheKeyFormat, plan.Id, CurrentUser.Id); var preOrderCacheKey = string.Format(FlashSalePlanAppService.PreOrderCacheKeyFormat, plan.Id, CurrentUser.Id);
var hashToken = await GetRequiredService<IFlashSalePlanHasher>() var hashToken = await GetRequiredService<IFlashSalePlanHasher>()
.HashAsync(plan.LastModificationTime, Product1.LastModificationTime, Product1.GetSkuById(plan.ProductSkuId).LastModificationTime); .HashAsync(plan.LastModificationTime, Product1.LastModificationTime,
((ProductSkuDto)Product1.GetSkuById(plan.ProductSkuId)).LastModificationTime);
var dto = await AppService.PreOrderAsync(plan.Id); var dto = await AppService.PreOrderAsync(plan.Id);
@ -358,7 +359,8 @@ public class FlashSalePlanAppServiceTests : FlashSalesApplicationTestBase
{ {
var plan = await CreateFlashSalePlanAsync(); var plan = await CreateFlashSalePlanAsync();
var hashToken = await GetRequiredService<IFlashSalePlanHasher>() var hashToken = await GetRequiredService<IFlashSalePlanHasher>()
.HashAsync(plan.LastModificationTime, Product1.LastModificationTime, Product1.GetSkuById(plan.ProductSkuId).LastModificationTime); .HashAsync(plan.LastModificationTime, Product1.LastModificationTime,
((ProductSkuDto)Product1.GetSkuById(plan.ProductSkuId)).LastModificationTime);
var orderFlashSalePlanInput = new OrderFlashSalePlanInput var orderFlashSalePlanInput = new OrderFlashSalePlanInput
{ {
CustomerRemark = "remark1" CustomerRemark = "remark1"

6406
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230329111403_RefactordOrderAndSomeOthers.Designer.cs

File diff suppressed because it is too large

74
samples/EShopSample/aspnet-core/src/EShopSample.EntityFrameworkCore/Migrations/20230329111403_RefactordOrderAndSomeOthers.cs

@ -0,0 +1,74 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EShopSample.Migrations
{
/// <inheritdoc />
public partial class RefactordOrderAndSomeOthers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<TimeSpan>(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProductViews",
type: "time",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DisplayName",
table: "EasyAbpEShopPaymentsRefundItemOrderExtraFees",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DisplayName",
table: "EasyAbpEShopOrdersOrderExtraFees",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.CreateTable(
name: "EasyAbpEShopOrdersOrderDiscounts",
columns: table => new
{
OrderId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
OrderLineId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
Key = table.Column<string>(type: "nvarchar(450)", nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true),
DiscountedAmount = table.Column<decimal>(type: "decimal(20,8)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EasyAbpEShopOrdersOrderDiscounts", x => new { x.OrderId, x.OrderLineId, x.Name, x.Key });
table.ForeignKey(
name: "FK_EasyAbpEShopOrdersOrderDiscounts_EasyAbpEShopOrdersOrders_OrderId",
column: x => x.OrderId,
principalTable: "EasyAbpEShopOrdersOrders",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EasyAbpEShopOrdersOrderDiscounts");
migrationBuilder.DropColumn(
name: "PaymentExpireIn",
table: "EasyAbpEShopProductsProductViews");
migrationBuilder.DropColumn(
name: "DisplayName",
table: "EasyAbpEShopPaymentsRefundItemOrderExtraFees");
migrationBuilder.DropColumn(
name: "DisplayName",
table: "EasyAbpEShopOrdersOrderExtraFees");
}
}
}

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

@ -607,6 +607,31 @@ namespace EShopSample.Migrations
b.ToTable("EasyAbpEShopOrdersOrders", (string)null); b.ToTable("EasyAbpEShopOrdersOrders", (string)null);
}); });
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderDiscount", b =>
{
b.Property<Guid>("OrderId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("OrderLineId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Key")
.HasColumnType("nvarchar(450)");
b.Property<decimal>("DiscountedAmount")
.HasColumnType("decimal(20,8)");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.HasKey("OrderId", "OrderLineId", "Name", "Key");
b.ToTable("EasyAbpEShopOrdersOrderDiscounts", (string)null);
});
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderExtraFee", b => modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderExtraFee", b =>
{ {
b.Property<Guid>("OrderId") b.Property<Guid>("OrderId")
@ -618,6 +643,9 @@ namespace EShopSample.Migrations
b.Property<string>("Key") b.Property<string>("Key")
.HasColumnType("nvarchar(450)"); .HasColumnType("nvarchar(450)");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Fee") b.Property<decimal>("Fee")
.HasColumnType("decimal(20,8)"); .HasColumnType("decimal(20,8)");
@ -1066,6 +1094,9 @@ namespace EShopSample.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
b.Property<string>("DisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("Key") b.Property<string>("Key")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
@ -2568,6 +2599,9 @@ namespace EShopSample.Migrations
b.Property<string>("Overview") b.Property<string>("Overview")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<TimeSpan?>("PaymentExpireIn")
.HasColumnType("time");
b.Property<Guid?>("ProductDetailId") b.Property<Guid?>("ProductDetailId")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
@ -5708,7 +5742,7 @@ namespace EShopSample.Migrations
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
b.OwnsOne("EasyAbp.BookingService.AssetCategories.AssetCategory.TimeInAdvance#EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 => b.OwnsOne("EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 =>
{ {
b1.Property<Guid>("AssetCategoryId") b1.Property<Guid>("AssetCategoryId")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
@ -5727,7 +5761,7 @@ namespace EShopSample.Migrations
b1.HasKey("AssetCategoryId"); b1.HasKey("AssetCategoryId");
b1.ToTable("EasyAbpBookingServiceAssetCategories", (string)null); b1.ToTable("EasyAbpBookingServiceAssetCategories");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("AssetCategoryId"); .HasForeignKey("AssetCategoryId");
@ -5740,7 +5774,7 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.BookingService.AssetSchedules.AssetSchedule", b => modelBuilder.Entity("EasyAbp.BookingService.AssetSchedules.AssetSchedule", b =>
{ {
b.OwnsOne("EasyAbp.BookingService.AssetSchedules.AssetSchedule.TimeInAdvance#EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 => b.OwnsOne("EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 =>
{ {
b1.Property<Guid>("AssetScheduleId") b1.Property<Guid>("AssetScheduleId")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
@ -5759,7 +5793,7 @@ namespace EShopSample.Migrations
b1.HasKey("AssetScheduleId"); b1.HasKey("AssetScheduleId");
b1.ToTable("EasyAbpBookingServiceAssetSchedules", (string)null); b1.ToTable("EasyAbpBookingServiceAssetSchedules");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("AssetScheduleId"); .HasForeignKey("AssetScheduleId");
@ -5770,7 +5804,7 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.BookingService.Assets.Asset", b => modelBuilder.Entity("EasyAbp.BookingService.Assets.Asset", b =>
{ {
b.OwnsOne("EasyAbp.BookingService.Assets.Asset.TimeInAdvance#EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 => b.OwnsOne("EasyAbp.BookingService.TimeInAdvance", "TimeInAdvance", b1 =>
{ {
b1.Property<Guid>("AssetId") b1.Property<Guid>("AssetId")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
@ -5789,7 +5823,7 @@ namespace EShopSample.Migrations
b1.HasKey("AssetId"); b1.HasKey("AssetId");
b1.ToTable("EasyAbpBookingServiceAssets", (string)null); b1.ToTable("EasyAbpBookingServiceAssets");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("AssetId"); .HasForeignKey("AssetId");
@ -5805,6 +5839,15 @@ namespace EShopSample.Migrations
.HasForeignKey("PeriodSchemeId"); .HasForeignKey("PeriodSchemeId");
}); });
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderDiscount", b =>
{
b.HasOne("EasyAbp.EShop.Orders.Orders.Order", null)
.WithMany("OrderDiscounts")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderExtraFee", b => modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.OrderExtraFee", b =>
{ {
b.HasOne("EasyAbp.EShop.Orders.Orders.Order", null) b.HasOne("EasyAbp.EShop.Orders.Orders.Order", null)
@ -6203,6 +6246,8 @@ namespace EShopSample.Migrations
modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.Order", b => modelBuilder.Entity("EasyAbp.EShop.Orders.Orders.Order", b =>
{ {
b.Navigation("OrderDiscounts");
b.Navigation("OrderExtraFees"); b.Navigation("OrderExtraFees");
b.Navigation("OrderLines"); b.Navigation("OrderLines");

Loading…
Cancel
Save