Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use NodaMoney to format money value #180

Merged
merged 1 commit into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<EasyAbpAbpTagHelperPlusModuleVersion>0.8.2</EasyAbpAbpTagHelperPlusModuleVersion>
<DaprSdkVersion>1.7.0</DaprSdkVersion>
<OrleansVersion>3.6.2</OrleansVersion>
<NodaMoneyVersion>1.0.5</NodaMoneyVersion>

</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions EShop.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Authorizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dapr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=noda/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Skus/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<OrderExtraFeeInfoModel>> GetListAsync(Guid customerUserId, CreateOrderDto input, Dictionary<Guid, ProductDto> productDict);
Task<List<OrderExtraFeeInfoModel>> GetListAsync(Guid customerUserId, CreateOrderDto input,
Dictionary<Guid, ProductDto> productDict, Currency effectiveCurrency);
}
}
Original file line number Diff line number Diff line change
@@ -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<decimal?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product,
ProductSkuDto productSku);
Task<Money?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine, ProductDto product,
ProductSkuDto productSku, Currency effectiveCurrency);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,18 +53,14 @@ public NewOrderGenerator(
public virtual async Task<Order> GenerateAsync(Guid customerUserId, CreateOrderDto input,
Dictionary<Guid, ProductDto> productDict, Dictionary<Guid, ProductDetailDto> productDetailDict)
{
var effectiveCurrency = await GetEffectiveCurrencyAsync();

var orderLines = new List<OrderLine>();

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();
Expand All @@ -79,7 +76,7 @@ public virtual async Task<Order> GenerateAsync(Guid customerUserId, CreateOrderD
tenantId: _currentTenant.Id,
storeId: input.StoreId,
customerUserId: customerUserId,
currency: effectiveCurrency,
currency: effectiveCurrency.Code,
productTotalPrice: productTotalPrice,
totalDiscount: totalDiscount,
totalPrice: totalPrice,
Expand All @@ -90,7 +87,7 @@ public virtual async Task<Order> 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);

Expand All @@ -100,27 +97,33 @@ public virtual async Task<Order> GenerateAsync(Guid customerUserId, CreateOrderD
}

protected virtual async Task AddOrderExtraFeesAsync(Order order, Guid customerUserId,
CreateOrderDto input, Dictionary<Guid, ProductDto> productDict)
CreateOrderDto input, Dictionary<Guid, ProductDto> productDict, Currency effectiveCurrency)
{
var providers = _serviceProvider.GetServices<IOrderExtraFeeProvider>();

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<OrderLine> GenerateOrderLineAsync(CreateOrderDto input,
CreateOrderLineDto inputOrderLine, Dictionary<Guid, ProductDto> productDict,
Dictionary<Guid, ProductDetailDto> productDetailDict)
Dictionary<Guid, ProductDetailDto> 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;
Expand All @@ -130,7 +133,7 @@ protected virtual async Task<OrderLine> 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;

Expand All @@ -149,10 +152,10 @@ protected virtual async Task<OrderLine> 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
);

Expand All @@ -161,31 +164,32 @@ protected virtual async Task<OrderLine> GenerateOrderLineAsync(CreateOrderDto in
return orderLine;
}

protected virtual async Task<decimal> GetUnitPriceAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine,
ProductDto product, ProductSkuDto productSku)
protected virtual async Task<Money> 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)
{
return overridenUnitPrice.Value;
}
}

return productSku.Price;
return new Money(productSku.Price, effectiveCurrency);
}

protected virtual async Task<string> GetEffectiveCurrencyAsync()
protected virtual async Task<Currency> GetEffectiveCurrencyAsync()
{
var currencyCode = Check.NotNullOrWhiteSpace(
await _settingProvider.GetOrNullAsync(OrdersSettings.CurrencyCode),
nameof(OrdersSettings.CurrencyCode)
);

return currencyCode;
return Currency.FromCode(currencyCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NodaMoney" Version="$(NodaMoneyVersion)" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(AbpVersion)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,12 +10,12 @@ public class TestOrderLinePriceOverrider : IOrderLinePriceOverrider, ITransientD
{
public static decimal Sku3UnitPrice { get; set; } = 100m;

public async Task<decimal?> GetUnitPriceOrNullAsync(CreateOrderDto input, CreateOrderLineDto inputOrderLine,
ProductDto product, ProductSkuDto productSku)
public async Task<Money?> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NodaMoney" Version="$(NodaMoneyVersion)" />
<PackageReference Include="EasyAbp.Abp.Trees.Domain" Version="$(EasyAbpAbpTreesModuleVersion)" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ArgumentException>(() => new Money(1.115m, Currency.FromCode("BTC")));

exception.Message.ShouldBe("BTC is an unknown currency code!");

return Task.CompletedTask;
}
}