diff --git a/src/VirtoCommerce.CoreModule.Core/Currency/Currency.cs b/src/VirtoCommerce.CoreModule.Core/Currency/Currency.cs index 09fd2f2f8..ea28469dd 100644 --- a/src/VirtoCommerce.CoreModule.Core/Currency/Currency.cs +++ b/src/VirtoCommerce.CoreModule.Core/Currency/Currency.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Text.Json.Serialization; +using Newtonsoft.Json; using VirtoCommerce.CoreModule.Core.Common; using VirtoCommerce.Platform.Core.Common; +using MidpointRoundingEnum = System.MidpointRounding; +using RoundingTypeEnum = VirtoCommerce.CoreModule.Core.Enums.RoundingType; namespace VirtoCommerce.CoreModule.Core.Currency { @@ -60,10 +62,8 @@ public Currency(Language language, string code) public Currency() : this(Language.InvariantLanguage, null) { - } - /// /// Currency code may be used ISO 4217. /// @@ -87,7 +87,6 @@ public string CultureName } } - public string EnglishName { get; set; } [JsonIgnore] @@ -97,14 +96,17 @@ public string CultureName /// name of the currency /// public string Name { get; set; } + /// /// Flag specifies that this is the primary currency /// public bool IsPrimary { get; set; } + /// /// The exchange rate against the primary exchange rate of the currency. /// public decimal ExchangeRate { get; set; } + /// /// Currency symbol /// @@ -115,6 +117,24 @@ public string CultureName /// public string CustomFormatting { get; set; } + [JsonIgnore] + public IMoneyRoundingPolicy RoundingPolicy { get; set; } + + private RoundingTypeEnum _roundingType = RoundingTypeEnum.Rounding001; + + public string RoundingType + { + get { return _roundingType.ToString(); } + set { _roundingType = EnumUtility.SafeParse(value, RoundingTypeEnum.Rounding001); } + } + + private MidpointRoundingEnum _midpointRounding = MidpointRoundingEnum.AwayFromZero; + + public string MidpointRounding + { + get { return _midpointRounding.ToString(); } + set { _midpointRounding = EnumUtility.SafeParse(value, MidpointRoundingEnum.AwayFromZero); } + } protected override IEnumerable GetEqualityComponents() { diff --git a/src/VirtoCommerce.CoreModule.Core/Currency/DefaultMoneyRoundingPolicy.cs b/src/VirtoCommerce.CoreModule.Core/Currency/DefaultMoneyRoundingPolicy.cs new file mode 100644 index 000000000..b01de3ffe --- /dev/null +++ b/src/VirtoCommerce.CoreModule.Core/Currency/DefaultMoneyRoundingPolicy.cs @@ -0,0 +1,92 @@ +using System; +using VirtoCommerce.CoreModule.Core.Enums; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.CoreModule.Core.Currency +{ + // TODO: Reduce the cognitive complexity + // https://github.com/grandnode/grandnode2/blob/master/src/Business/Grand.Business.Catalog/Extensions/RoundingHelper.cs#L44 + public class DefaultMoneyRoundingPolicy : IMoneyRoundingPolicy + { + public decimal RoundMoney(decimal amount, Currency currency) + { + var roundingType = EnumUtility.SafeParse(currency.RoundingType, RoundingType.Rounding001); + var midpointRounding = EnumUtility.SafeParse(currency.MidpointRounding, MidpointRounding.AwayFromZero); + return Round(amount, currency.NumberFormat.NumberDecimalDigits, roundingType, midpointRounding); + } + + /// + /// Round + /// + /// Value to round + /// Number of digits in fractional part + /// The rounding type + /// Specifies how mathematical rounding methods should process a number that is midway between two numbers + /// Rounded value + protected decimal Round(decimal value, int decimals, RoundingType roundingType, MidpointRounding midpointRounding) + { + //default round (Rounding001) + var result = Math.Round(value, decimals, midpointRounding); + var fractionPart = (result - Math.Truncate(result)) * 10; + + //cash rounding not needed + if (fractionPart == 0) + return result; + + //Cash rounding (details: https://en.wikipedia.org/wiki/Cash_rounding) + switch (roundingType) + { + //rounding with 0.05 or 5 intervals + case RoundingType.Rounding005Up: + case RoundingType.Rounding005Down: + fractionPart = (fractionPart - Math.Truncate(fractionPart)) * 10; + if (fractionPart == 5 || fractionPart == 0) + break; + if (roundingType == RoundingType.Rounding005Down) + fractionPart = fractionPart > 5 ? 5 - fractionPart : fractionPart * -1; + else + fractionPart = fractionPart > 5 ? 10 - fractionPart : 5 - fractionPart; + + result += fractionPart / 100; + break; + //rounding with 0.10 intervals + case RoundingType.Rounding01Up: + case RoundingType.Rounding01Down: + fractionPart = (fractionPart - Math.Truncate(fractionPart)) * 10; + if (fractionPart == 0) + break; + + if (roundingType == RoundingType.Rounding01Down && fractionPart == 5) + fractionPart = -5; + else + fractionPart = fractionPart < 5 ? fractionPart * -1 : 10 - fractionPart; + + result += fractionPart / 100; + break; + //rounding with 0.50 intervals + case RoundingType.Rounding05: + fractionPart *= 10; + fractionPart = fractionPart < 25 ? fractionPart * -1 : fractionPart < 50 || fractionPart < 75 ? 50 - fractionPart : 100 - fractionPart; + + result += fractionPart / 100; + break; + //rounding with 1.00 intervals + case RoundingType.Rounding1: + case RoundingType.Rounding1Up: + fractionPart *= 10; + + if (roundingType == RoundingType.Rounding1Up && fractionPart > 0) + result = Math.Truncate(result) + 1; + else + result = fractionPart < 50 ? Math.Truncate(result) : Math.Truncate(result) + 1; + + break; + case RoundingType.Rounding001: + default: + break; + } + + return result; + } + } +} diff --git a/src/VirtoCommerce.CoreModule.Core/Currency/IMoneyRoundingPolicy.cs b/src/VirtoCommerce.CoreModule.Core/Currency/IMoneyRoundingPolicy.cs new file mode 100644 index 000000000..356da7704 --- /dev/null +++ b/src/VirtoCommerce.CoreModule.Core/Currency/IMoneyRoundingPolicy.cs @@ -0,0 +1,7 @@ +namespace VirtoCommerce.CoreModule.Core.Currency +{ + public interface IMoneyRoundingPolicy + { + decimal RoundMoney(decimal amount, Currency currency); + } +} diff --git a/src/VirtoCommerce.CoreModule.Core/Currency/Money.cs b/src/VirtoCommerce.CoreModule.Core/Currency/Money.cs index 58dff05ab..970b62920 100644 --- a/src/VirtoCommerce.CoreModule.Core/Currency/Money.cs +++ b/src/VirtoCommerce.CoreModule.Core/Currency/Money.cs @@ -32,7 +32,7 @@ public Money(decimal amount, Currency currency) /// of the associated currency using MidpointRounding.AwayFromZero. /// /// A decimal with the amount rounded to the significant number of decimal digits. - public decimal Amount => decimal.Round(InternalAmount, DecimalDigits, MidpointRounding.AwayFromZero); + public decimal Amount => Currency.RoundingPolicy.RoundMoney(InternalAmount, Currency); /// /// Truncates the amount to the number of significant decimal digits diff --git a/src/VirtoCommerce.CoreModule.Core/Enums/RoundingType.cs b/src/VirtoCommerce.CoreModule.Core/Enums/RoundingType.cs new file mode 100644 index 000000000..03ce92996 --- /dev/null +++ b/src/VirtoCommerce.CoreModule.Core/Enums/RoundingType.cs @@ -0,0 +1,15 @@ +namespace VirtoCommerce.CoreModule.Core.Enums +{ + //Cash rounding (details: https://en.wikipedia.org/wiki/Cash_rounding) + public enum RoundingType + { + Rounding001, + Rounding005Up, + Rounding005Down, + Rounding01Up, + Rounding01Down, + Rounding05, + Rounding1, + Rounding1Up + } +} diff --git a/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyEntity.cs b/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyEntity.cs index 48218b78e..0013548f5 100644 --- a/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyEntity.cs +++ b/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyEntity.cs @@ -24,6 +24,10 @@ public class CurrencyEntity : AuditableEntity [StringLength(64)] public string CustomFormatting { get; set; } + [StringLength(16)] + public string MidpointRounding { get; set; } + [StringLength(16)] + public string RoundingType { get; set; } public virtual Core.Currency.Currency ToModel(Core.Currency.Currency currency) { @@ -33,6 +37,8 @@ public virtual Core.Currency.Currency ToModel(Core.Currency.Currency currency) currency.ExchangeRate = ExchangeRate; currency.Symbol = Symbol; currency.CustomFormatting = CustomFormatting; + currency.MidpointRounding = MidpointRounding; + currency.RoundingType = RoundingType; return currency; } @@ -44,17 +50,21 @@ public virtual CurrencyEntity FromModel(Core.Currency.Currency currency) ExchangeRate = currency.ExchangeRate; Symbol = currency.Symbol; CustomFormatting = currency.CustomFormatting; + MidpointRounding = currency.MidpointRounding.ToString(); + RoundingType = currency.RoundingType.ToString(); return this; } public virtual void Patch(CurrencyEntity target) { - target.Code = Code; + target.Code = Code; target.Name = Name; target.IsPrimary = IsPrimary; target.ExchangeRate = ExchangeRate; target.Symbol = Symbol; target.CustomFormatting = CustomFormatting; + target.MidpointRounding = MidpointRounding; + target.RoundingType = RoundingType; } } diff --git a/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyService.cs b/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyService.cs index 965c9d850..62ac60df7 100644 --- a/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyService.cs +++ b/src/VirtoCommerce.CoreModule.Data/Currency/CurrencyService.cs @@ -17,12 +17,14 @@ public class CurrencyService : ICurrencyService private readonly Func _repositoryFactory; private readonly IEventPublisher _eventPublisher; private readonly IPlatformMemoryCache _platformMemoryCache; + private readonly IMoneyRoundingPolicy _moneyRoundingPolicy; - public CurrencyService(Func repositoryFactory, IEventPublisher eventPublisher, IPlatformMemoryCache platformMemoryCache) + public CurrencyService(Func repositoryFactory, IEventPublisher eventPublisher, IPlatformMemoryCache platformMemoryCache, IMoneyRoundingPolicy moneyRoundingPolicy) { _repositoryFactory = repositoryFactory; _eventPublisher = eventPublisher; _platformMemoryCache = platformMemoryCache; + _moneyRoundingPolicy = moneyRoundingPolicy; } public async Task> GetAllCurrenciesAsync() @@ -34,7 +36,12 @@ public CurrencyService(Func repositoryFactory, IEventPublisher using (var repository = _repositoryFactory()) { var currencyEntities = await repository.Currencies.OrderByDescending(x => x.IsPrimary).ThenBy(x => x.Code).ToArrayAsync(); - var result = currencyEntities.Select(x => x.ToModel(AbstractTypeFactory.TryCreateInstance())).ToList(); + var result = currencyEntities.Select(x => + { + var currency = x.ToModel(AbstractTypeFactory.TryCreateInstance()); + currency.RoundingPolicy = _moneyRoundingPolicy; + return currency; + }).ToList(); return result; } diff --git a/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.Designer.cs b/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.Designer.cs new file mode 100644 index 000000000..9cf51de68 --- /dev/null +++ b/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.Designer.cs @@ -0,0 +1,139 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using VirtoCommerce.CoreModule.Data.Repositories; + +namespace VirtoCommerce.CoreModule.Data.Migrations +{ + [DbContext(typeof(CoreDbContext))] + [Migration("20210728124419_roundingpolicy")] + partial class roundingpolicy + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("VirtoCommerce.CoreModule.Data.Currency.CurrencyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreatedDate") + .HasColumnType("datetime2"); + + b.Property("CustomFormatting") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ExchangeRate") + .HasColumnType("Money"); + + b.Property("IsPrimary") + .HasColumnType("bit"); + + b.Property("MidpointRounding") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ModifiedDate") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("RoundingType") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("Symbol") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.HasKey("Id"); + + b.HasIndex("Code") + .HasName("IX_Code"); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("VirtoCommerce.CoreModule.Data.Model.SequenceEntity", b => + { + b.Property("ObjectType") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ModifiedDate") + .HasColumnType("datetime2"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.Property("Value") + .HasColumnType("int"); + + b.HasKey("ObjectType"); + + b.ToTable("Sequence"); + }); + + modelBuilder.Entity("VirtoCommerce.CoreModule.Data.Package.PackageTypeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Height") + .HasColumnType("decimal(18,2)"); + + b.Property("Length") + .HasColumnType("decimal(18,2)"); + + b.Property("MeasureUnit") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(254)") + .HasMaxLength(254); + + b.Property("Width") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.ToTable("PackageType"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.cs b/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.cs new file mode 100644 index 000000000..dd19ef26c --- /dev/null +++ b/src/VirtoCommerce.CoreModule.Data/Migrations/20210728124419_roundingpolicy.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace VirtoCommerce.CoreModule.Data.Migrations +{ + public partial class roundingpolicy : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MidpointRounding", + table: "Currency", + maxLength: 16, + nullable: true); + + migrationBuilder.AddColumn( + name: "RoundingType", + table: "Currency", + maxLength: 16, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MidpointRounding", + table: "Currency"); + + migrationBuilder.DropColumn( + name: "RoundingType", + table: "Currency"); + } + } +} diff --git a/src/VirtoCommerce.CoreModule.Data/Migrations/CoreDbContextModelSnapshot.cs b/src/VirtoCommerce.CoreModule.Data/Migrations/CoreDbContextModelSnapshot.cs index f21be264b..b7beeb33b 100644 --- a/src/VirtoCommerce.CoreModule.Data/Migrations/CoreDbContextModelSnapshot.cs +++ b/src/VirtoCommerce.CoreModule.Data/Migrations/CoreDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("ProductVersion", "3.1.8") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -23,35 +23,53 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); b.Property("Code") .IsRequired() + .HasColumnType("nvarchar(16)") .HasMaxLength(16); b.Property("CreatedBy") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); - b.Property("CreatedDate"); + b.Property("CreatedDate") + .HasColumnType("datetime2"); b.Property("CustomFormatting") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("ExchangeRate") .HasColumnType("Money"); - b.Property("IsPrimary"); + b.Property("IsPrimary") + .HasColumnType("bit"); + + b.Property("MidpointRounding") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); b.Property("ModifiedBy") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); - b.Property("ModifiedDate"); + b.Property("ModifiedDate") + .HasColumnType("datetime2"); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); + b.Property("RoundingType") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + b.Property("Symbol") + .HasColumnType("nvarchar(16)") .HasMaxLength(16); b.HasKey("Id"); @@ -65,16 +83,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("VirtoCommerce.CoreModule.Data.Model.SequenceEntity", b => { b.Property("ObjectType") - .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); - b.Property("ModifiedDate"); + b.Property("ModifiedDate") + .HasColumnType("datetime2"); b.Property("RowVersion") .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate(); + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); - b.Property("Value"); + b.Property("Value") + .HasColumnType("int"); b.HasKey("ObjectType"); @@ -85,20 +106,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); - b.Property("Height"); + b.Property("Height") + .HasColumnType("decimal(18,2)"); - b.Property("Length"); + b.Property("Length") + .HasColumnType("decimal(18,2)"); b.Property("MeasureUnit") + .HasColumnType("nvarchar(16)") .HasMaxLength(16); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(254)") .HasMaxLength(254); - b.Property("Width"); + b.Property("Width") + .HasColumnType("decimal(18,2)"); b.HasKey("Id"); diff --git a/src/VirtoCommerce.CoreModule.Web/Localizations/en.VirtoCommerce.Core.json b/src/VirtoCommerce.CoreModule.Web/Localizations/en.VirtoCommerce.Core.json index 81f66d031..cc2d6f8ca 100644 --- a/src/VirtoCommerce.CoreModule.Web/Localizations/en.VirtoCommerce.Core.json +++ b/src/VirtoCommerce.CoreModule.Web/Localizations/en.VirtoCommerce.Core.json @@ -71,7 +71,9 @@ "is-primary": "Is primary", "exchange-rate": "Exchange rate", "symbol": "Symbol", - "custom-formatting": "Currency custom formatting" + "custom-formatting": "Currency custom formatting", + "midpoint-rounding": "Midpoint Rounding", + "rounding-type": "Rounding Type" }, "descriptions": { "code": "The currency code. Use 3 letter ISO 4217 code, if it exists.", @@ -79,7 +81,9 @@ "is-primary": "Is this the primary currency? Only one currency can by primary.", "exchange-rate": "The exchange rate against the primary exchange rate currency", "symbol": "Short currency symbol", - "custom-formatting": "Custom formatting to be applied to the currency value" + "custom-formatting": "Custom formatting to be applied to the currency value", + "midpoint-rounding": "Specifies how mathematical rounding methods should process a number that is midway between two numbers", + "rounding-type": "Enumerates rounding" }, "placeholders": { "symbol": "Enter symbol or leave empty for default culture", diff --git a/src/VirtoCommerce.CoreModule.Web/Module.cs b/src/VirtoCommerce.CoreModule.Web/Module.cs index 62082d954..b190dc73f 100644 --- a/src/VirtoCommerce.CoreModule.Web/Module.cs +++ b/src/VirtoCommerce.CoreModule.Web/Module.cs @@ -51,6 +51,9 @@ public void Initialize(IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); + + // Money rounding + serviceCollection.AddTransient(); } public void PostInitialize(IApplicationBuilder appBuilder) diff --git a/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.js b/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.js index e314473af..7ac11b4a1 100644 --- a/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.js +++ b/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.js @@ -11,6 +11,24 @@ angular.module('virtoCommerce.coreModule.currency') blade.metafields = blade.metafields ? blade.metafields : metaFormsService.getMetaFields('currencyDetail'); + blade.roundingTypes = [ + 'Rounding001', + 'Rounding005Up', + 'Rounding005Down', + 'Rounding01Up', + 'Rounding01Down', + 'Rounding05', + 'Rounding1', + 'Rounding1Up' + ]; + blade.midpointRoundings = [ + 'ToEven', + 'AwayFromZero', + 'ToZero', + 'ToNegativeInfinity', + 'ToPositiveInfinity' + ]; + $scope.saveChanges = function () { blade.isLoading = true; @@ -37,7 +55,7 @@ angular.module('virtoCommerce.coreModule.currency') function initializeBlade(data) { if (blade.isNew) data = { exchangeRate: 1.00 }; - + blade.currentEntity = angular.copy(data); blade.origEntity = data; blade.isLoading = false; diff --git a/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.tpl.html b/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.tpl.html index b17bc257e..21e69fc9e 100644 --- a/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.tpl.html +++ b/src/VirtoCommerce.CoreModule.Web/Scripts/currency/blades/currency-detail.tpl.html @@ -50,4 +50,27 @@
{{ 'core.blades.currency-detail.descriptions.custom-formatting' | translate }}
- \ No newline at end of file + + + + diff --git a/src/VirtoCommerce.CoreModule.Web/Scripts/virtoCommerce-core.js b/src/VirtoCommerce.CoreModule.Web/Scripts/virtoCommerce-core.js index fb562aa72..250c0f562 100644 --- a/src/VirtoCommerce.CoreModule.Web/Scripts/virtoCommerce-core.js +++ b/src/VirtoCommerce.CoreModule.Web/Scripts/virtoCommerce-core.js @@ -43,6 +43,16 @@ angular.module(moduleName, [ name: 'customFormatting', title: 'core.blades.currency-detail.labels.custom-formatting', templateUrl: 'currencyDetail-customFormatting.html' + }, + { + name: 'midpointRounding', + title: 'core.blades.currency-detail.labels.midpoint-rounding', + templateUrl: 'currencyDetail-midpointRounding.html' + }, + { + name: 'roundingType', + title: 'core.blades.currency-detail.labels.rounding-type', + templateUrl: 'currencyDetail-roundingType.html' } ]); }]); diff --git a/tests/VirtoCommerce.CoreModule.Tests/DefaultMoneyRoundingPolicyTests.cs b/tests/VirtoCommerce.CoreModule.Tests/DefaultMoneyRoundingPolicyTests.cs new file mode 100644 index 000000000..e1d2d313d --- /dev/null +++ b/tests/VirtoCommerce.CoreModule.Tests/DefaultMoneyRoundingPolicyTests.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using VirtoCommerce.CoreModule.Core.Currency; +using VirtoCommerce.CoreModule.Core.Enums; +using Xunit; + +namespace VirtoCommerce.CoreModule.Tests +{ + public class DefaultMoneyRoundingPolicyTests + { + [Theory] + [MemberData(nameof(Data))] + public void CanRound(decimal amount, decimal expected, RoundingType roundingType, MidpointRounding midpointRounding) + { + // Arrange + var roundPolicy = new DefaultMoneyRoundingPolicy(); + var currency = new Currency(); + currency.RoundingType = roundingType.ToString(); + currency.MidpointRounding = midpointRounding.ToString(); + + // Act + var result = roundPolicy.RoundMoney(amount, currency); + + // Assert + Assert.Equal(expected, result); + } + + public static IEnumerable Data => + new List + { + // AwayFromZero + new object[] { 1.005m, 1.01m, RoundingType.Rounding001, MidpointRounding.AwayFromZero}, + new object[] { 1.004m, 1m, RoundingType.Rounding001, MidpointRounding.AwayFromZero}, + new object[] { 1.0009m, 1m, RoundingType.Rounding001, MidpointRounding.AwayFromZero}, + + new object[] { 1.005m, 1m, RoundingType.Rounding005Down, MidpointRounding.AwayFromZero}, + new object[] { 1.004m, 1m, RoundingType.Rounding005Down, MidpointRounding.AwayFromZero}, + new object[] { 1.009m, 1m, RoundingType.Rounding005Down, MidpointRounding.AwayFromZero}, + + new object[] { 1.005m, 1.05m, RoundingType.Rounding005Up, MidpointRounding.AwayFromZero}, + new object[] { 1.095m, 1.1m, RoundingType.Rounding005Up, MidpointRounding.AwayFromZero}, + new object[] { 1.5m, 1.5m, RoundingType.Rounding005Up, MidpointRounding.AwayFromZero}, + + new object[] { 1.105m, 1.1m, RoundingType.Rounding01Down, MidpointRounding.AwayFromZero}, + new object[] { 1.599m, 1.6m, RoundingType.Rounding01Down, MidpointRounding.AwayFromZero}, + new object[] { 1.999m, 2m, RoundingType.Rounding01Down, MidpointRounding.AwayFromZero}, + + new object[] { 1.09m, 1.1m, RoundingType.Rounding01Up, MidpointRounding.AwayFromZero}, + + new object[] { 1.04m, 1m, RoundingType.Rounding05, MidpointRounding.AwayFromZero}, + new object[] { 1.400009m, 1.5m, RoundingType.Rounding05, MidpointRounding.AwayFromZero}, + + new object[] { 1.005m, 1m, RoundingType.Rounding1, MidpointRounding.AwayFromZero}, + new object[] { 1.6m, 2m, RoundingType.Rounding1, MidpointRounding.AwayFromZero}, + new object[] { 1.5m, 2m, RoundingType.Rounding1, MidpointRounding.AwayFromZero}, + + new object[] { 1.009m, 2m, RoundingType.Rounding1Up, MidpointRounding.AwayFromZero}, + + // To Even + new object[] { 1.005m, 1m, RoundingType.Rounding001, MidpointRounding.ToEven}, + new object[] { 1.004m, 1m, RoundingType.Rounding001, MidpointRounding.ToEven}, + new object[] { 1.0009m, 1m, RoundingType.Rounding001, MidpointRounding.ToEven}, + + new object[] { 1.005m, 1m, RoundingType.Rounding005Down, MidpointRounding.ToEven}, + new object[] { 1.004m, 1m, RoundingType.Rounding005Down, MidpointRounding.ToEven}, + new object[] { 1.009m, 1m, RoundingType.Rounding005Down, MidpointRounding.ToEven}, + + new object[] { 1.005m, 1m, RoundingType.Rounding005Up, MidpointRounding.ToEven}, + new object[] { 1.095m, 1.1m, RoundingType.Rounding005Up, MidpointRounding.ToEven}, + new object[] { 1.5m, 1.5m, RoundingType.Rounding005Up, MidpointRounding.ToEven}, + + new object[] { 1.105m, 1.1m, RoundingType.Rounding01Down, MidpointRounding.ToEven}, + new object[] { 1.599m, 1.6m, RoundingType.Rounding01Down, MidpointRounding.ToEven}, + new object[] { 1.999m, 2m, RoundingType.Rounding01Down, MidpointRounding.ToEven}, + + new object[] { 1.09m, 1.1m, RoundingType.Rounding01Up, MidpointRounding.ToEven}, + + new object[] { 1.04m, 1m, RoundingType.Rounding05, MidpointRounding.ToEven}, + new object[] { 1.400009m, 1.5m, RoundingType.Rounding05, MidpointRounding.ToEven}, + + new object[] { 1.005m, 1m, RoundingType.Rounding1, MidpointRounding.ToEven}, + new object[] { 1.6m, 2m, RoundingType.Rounding1, MidpointRounding.ToEven}, + new object[] { 1.5m, 2m, RoundingType.Rounding1, MidpointRounding.ToEven}, + + new object[] { 1.009m, 2m, RoundingType.Rounding1Up, MidpointRounding.ToEven}, + }; + } +}