diff --git a/Directory.Build.props b/Directory.Build.props index 989380733..f91d24918 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ 0.8.2 1.7.0 3.6.2 + 1.0.5 \ No newline at end of file diff --git a/EShop.sln.DotSettings b/EShop.sln.DotSettings index 7b7d41762..e8263a9e0 100644 --- a/EShop.sln.DotSettings +++ b/EShop.sln.DotSettings @@ -22,4 +22,5 @@ SQL True True + True True \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs index 0b19dd1de..9cd5ba9da 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderExtraFeeProvider.cs @@ -3,11 +3,13 @@ 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 IOrderExtraFeeProvider { - Task> GetListAsync(Guid customerUserId, CreateOrderDto input, Dictionary productDict); + Task> GetListAsync(Guid customerUserId, CreateOrderDto input, + Dictionary productDict, Currency effectiveCurrency); } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs index c86f54634..a1440d637 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/IOrderLinePriceOverrider.cs @@ -1,11 +1,12 @@ 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 GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product, - ProductSkuDto productSku); + Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product, + ProductSkuDto productSku, Currency effectiveCurrency); } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs index 2ab9a54c4..495930c8d 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Application/EasyAbp/EShop/Orders/Orders/NewOrderGenerator.cs @@ -8,6 +8,7 @@ using EasyAbp.EShop.Products.Products; using EasyAbp.EShop.Products.Products.Dtos; using Microsoft.Extensions.DependencyInjection; +using NodaMoney; using Volo.Abp; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; @@ -52,18 +53,14 @@ public NewOrderGenerator( public virtual async Task GenerateAsync(Guid customerUserId, CreateOrderDto input, Dictionary productDict, Dictionary productDetailDict) { + var effectiveCurrency = await GetEffectiveCurrencyAsync(); + var orderLines = new List(); foreach (var inputOrderLine in input.OrderLines) { - orderLines.Add(await GenerateOrderLineAsync(input, inputOrderLine, productDict, productDetailDict)); - } - - var effectiveCurrency = await GetEffectiveCurrencyAsync(); - - if (orderLines.Any(x => x.Currency != effectiveCurrency)) - { - throw new UnexpectedCurrencyException(effectiveCurrency); + orderLines.Add(await GenerateOrderLineAsync( + input, inputOrderLine, productDict, productDetailDict, effectiveCurrency)); } var productTotalPrice = orderLines.Select(x => x.TotalPrice).Sum(); @@ -79,7 +76,7 @@ public virtual async Task GenerateAsync(Guid customerUserId, CreateOrderD tenantId: _currentTenant.Id, storeId: input.StoreId, customerUserId: customerUserId, - currency: effectiveCurrency, + currency: effectiveCurrency.Code, productTotalPrice: productTotalPrice, totalDiscount: totalDiscount, totalPrice: totalPrice, @@ -90,7 +87,7 @@ public virtual async Task GenerateAsync(Guid customerUserId, CreateOrderD input.MapExtraPropertiesTo(order, MappingPropertyDefinitionChecks.Destination); - await AddOrderExtraFeesAsync(order, customerUserId, input, productDict); + await AddOrderExtraFeesAsync(order, customerUserId, input, productDict, effectiveCurrency); order.SetOrderLines(orderLines); @@ -100,27 +97,33 @@ public virtual async Task GenerateAsync(Guid customerUserId, CreateOrderD } protected virtual async Task AddOrderExtraFeesAsync(Order order, Guid customerUserId, - CreateOrderDto input, Dictionary productDict) + CreateOrderDto input, Dictionary productDict, Currency effectiveCurrency) { var providers = _serviceProvider.GetServices(); foreach (var provider in providers) { - var infoModels = await provider.GetListAsync(customerUserId, input, productDict); + var infoModels = await provider.GetListAsync(customerUserId, input, productDict, effectiveCurrency); foreach (var infoModel in infoModels) { - order.AddOrderExtraFee(infoModel.Fee, infoModel.Name, infoModel.Key); + var fee = new Money(infoModel.Fee, effectiveCurrency); + order.AddOrderExtraFee(fee.Amount, infoModel.Name, infoModel.Key); } } } protected virtual async Task GenerateOrderLineAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, Dictionary productDict, - Dictionary productDetailDict) + Dictionary productDetailDict, Currency effectiveCurrency) { var product = productDict[inputOrderLine.ProductId]; var productSku = product.GetSkuById(inputOrderLine.ProductSkuId); + + if (productSku.Currency != effectiveCurrency.Code) + { + throw new UnexpectedCurrencyException(effectiveCurrency.Code); + } var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; var productDetail = productDetailId.HasValue ? productDetailDict[productDetailId.Value] : null; @@ -130,7 +133,7 @@ protected virtual async Task GenerateOrderLineAsync(CreateOrderDto in throw new OrderLineInvalidQuantityException(product.Id, productSku.Id, inputOrderLine.Quantity); } - var unitPrice = await GetUnitPriceAsync(input, inputOrderLine, product, productSku); + var unitPrice = await GetUnitPriceAsync(input, inputOrderLine, product, productSku, effectiveCurrency); var totalPrice = unitPrice * inputOrderLine.Quantity; @@ -149,10 +152,10 @@ protected virtual async Task GenerateOrderLineAsync(CreateOrderDto in skuDescription: await _productSkuDescriptionProvider.GenerateAsync(product, productSku), mediaResources: product.MediaResources, currency: productSku.Currency, - unitPrice: unitPrice, - totalPrice: totalPrice, + unitPrice: unitPrice.Amount, + totalPrice: totalPrice.Amount, totalDiscount: 0, - actualTotalPrice: totalPrice, + actualTotalPrice: totalPrice.Amount, quantity: inputOrderLine.Quantity ); @@ -161,13 +164,14 @@ protected virtual async Task GenerateOrderLineAsync(CreateOrderDto in return orderLine; } - protected virtual async Task GetUnitPriceAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, - ProductDto product, ProductSkuDto productSku) + protected virtual async Task GetUnitPriceAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, + ProductDto product, ProductSkuDto productSku, Currency effectiveCurrency) { foreach (var overrider in _orderLinePriceOverriders) { var overridenUnitPrice = - await overrider.GetUnitPriceOrNullAsync(input, inputOrderLine, product, productSku); + await overrider.GetUnitPriceOrNullAsync(input, inputOrderLine, product, productSku, + effectiveCurrency); if (overridenUnitPrice is not null) { @@ -175,17 +179,17 @@ protected virtual async Task GetUnitPriceAsync(CreateOrderDto input, Cr } } - return productSku.Price; + return new Money(productSku.Price, effectiveCurrency); } - protected virtual async Task GetEffectiveCurrencyAsync() + protected virtual async Task GetEffectiveCurrencyAsync() { var currencyCode = Check.NotNullOrWhiteSpace( await _settingProvider.GetOrNullAsync(OrdersSettings.CurrencyCode), nameof(OrdersSettings.CurrencyCode) ); - return currencyCode; + return Currency.FromCode(currencyCode); } } } \ No newline at end of file diff --git a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp.EShop.Orders.Domain.csproj b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp.EShop.Orders.Domain.csproj index 3bfe30854..4689b724a 100644 --- a/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp.EShop.Orders.Domain.csproj +++ b/modules/EasyAbp.EShop.Orders/src/EasyAbp.EShop.Orders.Domain/EasyAbp.EShop.Orders.Domain.csproj @@ -8,6 +8,7 @@ + diff --git a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs index 5d580aece..10d61c50e 100644 --- a/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs +++ b/modules/EasyAbp.EShop.Orders/test/EasyAbp.EShop.Orders.Application.Tests/Orders/TestOrderLinePriceOverrider.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using EasyAbp.EShop.Orders.Orders.Dtos; using EasyAbp.EShop.Products.Products.Dtos; +using NodaMoney; using Volo.Abp.DependencyInjection; namespace EasyAbp.EShop.Orders.Orders; @@ -9,12 +10,12 @@ public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientD { public static decimal Sku3UnitPrice { get; set; } = 100m; - public async Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, - ProductDto product, ProductSkuDto productSku) + public async Task GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, + ProductDto product, ProductSkuDto productSku, Currency effectiveCurrency) { if (inputOrderLine.ProductSkuId == OrderTestData.ProductSku3Id) { - return Sku3UnitPrice; + return new Money(Sku3UnitPrice, effectiveCurrency); } return null; diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj index 627d37210..bac32da5c 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp.EShop.Products.Domain.csproj @@ -8,6 +8,7 @@ + diff --git a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs index 9f83221db..efe06138e 100644 --- a/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs +++ b/modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductSku.cs @@ -1,6 +1,7 @@ using System; using System.Text.Json.Serialization; using JetBrains.Annotations; +using NodaMoney; using Volo.Abp; using Volo.Abp.Data; using Volo.Abp.Domain.Entities.Auditing; @@ -55,12 +56,15 @@ public ProductSku( [CanBeNull] string mediaResources, Guid? productDetailId) : base(id) { + Check.NotNullOrWhiteSpace(currency, nameof(currency)); + var nodaCurrency = NodaMoney.Currency.FromCode(currency); + SerializedAttributeOptionIds = Check.NotNullOrWhiteSpace(serializedAttributeOptionIds, nameof(serializedAttributeOptionIds)); Name = name?.Trim(); - Currency = Check.NotNullOrWhiteSpace(currency, nameof(currency)); - OriginalPrice = originalPrice; - Price = price; + Currency = nodaCurrency.Code; + OriginalPrice = originalPrice.HasValue ? new Money(originalPrice.Value, nodaCurrency).Amount : null; + Price = new Money(price, nodaCurrency).Amount; OrderMinQuantity = orderMinQuantity; OrderMaxQuantity = orderMaxQuantity; PaymentExpireIn = paymentExpireIn; @@ -87,10 +91,13 @@ public void Update( [CanBeNull] string mediaResources, Guid? productDetailId) { + Check.NotNullOrWhiteSpace(currency, nameof(currency)); + var nodaCurrency = NodaMoney.Currency.FromCode(currency); + Name = name?.Trim(); - Currency = Check.NotNullOrWhiteSpace(currency, nameof(currency)); - OriginalPrice = originalPrice; - Price = price; + Currency = nodaCurrency.Code; + OriginalPrice = originalPrice.HasValue ? new Money(originalPrice.Value, nodaCurrency).Amount : null; + Price = new Money(price, nodaCurrency).Amount; OrderMinQuantity = orderMinQuantity; OrderMaxQuantity = orderMaxQuantity; PaymentExpireIn = paymentExpireIn; diff --git a/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/CurrencyTests.cs b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/CurrencyTests.cs new file mode 100644 index 000000000..f6b4422f1 --- /dev/null +++ b/modules/EasyAbp.EShop.Products/test/EasyAbp.EShop.Products.Domain.Tests/Products/CurrencyTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using NodaMoney; +using Shouldly; +using Xunit; + +namespace EasyAbp.EShop.Products.Products; + +public class CurrencyTests : ProductsDomainTestBase +{ + [Fact] + public Task Should_Rounding_If_Amount_Is_Out_Of_Decimals() + { + var money = new Money(1.115m, Currency.FromCode("CNY")); + + money.Currency.ShouldBe(Currency.FromCode("CNY")); + money.Amount.ShouldBe(1.12m); + + return Task.CompletedTask; + } + [Fact] + public Task Should_Throw_If_Currency_Is_Undefined() + { + var exception = Should.Throw(() => new Money(1.115m, Currency.FromCode("BTC"))); + + exception.Message.ShouldBe("BTC is an unknown currency code!"); + + return Task.CompletedTask; + } +} \ No newline at end of file