Skip to content

Commit 82b2436

Browse files
committed
smartstore#1240 Make currency rounding configurable (in progress)
1 parent b093623 commit 82b2436

28 files changed

Lines changed: 850 additions & 225 deletions

File tree

src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,10 @@ public partial class Currency : BaseEntity, IAuditable, ILocalizedEntity, IStore
7676
/// </summary>
7777
[DataMember]
7878
public string DomainEndings { get; set; }
79-
}
79+
80+
/// <summary>
81+
/// Gets or sets the rounding method
82+
/// </summary>
83+
public CurrencyRoundingMethod RoundingMethod { get; set; }
84+
}
8085
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace SmartStore.Core.Domain.Directory
2+
{
3+
/// <summary>
4+
/// <see cref="https://en.wikipedia.org/wiki/Cash_rounding"/>
5+
/// </summary>
6+
public enum CurrencyRoundingMethod
7+
{
8+
/// <summary>
9+
/// Default rounding. Round to nearest 0.01 even number (following IEEE 754).
10+
/// </summary>
11+
Default = 0,
12+
13+
/// <summary>
14+
/// Round down to nearest 0.05
15+
/// </summary>
16+
Down005,
17+
18+
/// <summary>
19+
/// Round up to nearest 0.05
20+
/// </summary>
21+
Up005,
22+
23+
/// <summary>
24+
/// Round down to nearest 0.10
25+
/// </summary>
26+
Down01,
27+
28+
/// <summary>
29+
/// Round up to nearest 0.10
30+
/// </summary>
31+
Up01,
32+
33+
/// <summary>
34+
/// 0.01 - 0.24: down to 0.00
35+
/// 0.25 - 0.49: up to 0.50
36+
/// 0.51 - 0.74: down to 0.50
37+
/// 0.75 - 0.99: up to next integer
38+
/// </summary>
39+
Interval05,
40+
41+
/// <summary>
42+
/// 0.01 - 0.49: down to 0.00
43+
/// 0.50 - 0.99: up to next integer
44+
/// </summary>
45+
Interval1,
46+
47+
/// <summary>
48+
/// Always round up decimals to next integer
49+
/// </summary>
50+
Up1
51+
}
52+
}
Lines changed: 146 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
1-
using System;
1+
using SmartStore.Core.Domain.Directory;
2+
using System;
23
using System.Globalization;
34

45
namespace SmartStore
5-
{
6+
{
67
public static class DecimalExtensions
78
{
8-
/// <summary>
9-
/// Rounds and formats a decimal culture invariant
10-
/// </summary>
11-
/// <param name="value">The decimal</param>
12-
/// <param name="decimals">Rounding decimal number</param>
13-
/// <returns>Formated value</returns>
14-
public static string FormatInvariant(this decimal value, int decimals = 2)
15-
{
16-
return Math.Round(value, decimals).ToString("0.00", CultureInfo.InvariantCulture);
17-
}
18-
199
/// <summary>
2010
/// Calculates the tax (percentage) from a gross and a net value.
2111
/// </summary>
@@ -26,7 +16,9 @@ public static string FormatInvariant(this decimal value, int decimals = 2)
2616
public static decimal ToTaxPercentage(this decimal inclTax, decimal exclTax, int? decimals = null)
2717
{
2818
if (exclTax == decimal.Zero)
19+
{
2920
return decimal.Zero;
21+
}
3022

3123
var result = ((inclTax / exclTax) - 1.0M) * 100.0M;
3224

@@ -39,8 +31,148 @@ public static decimal ToTaxPercentage(this decimal inclTax, decimal exclTax, int
3931
/// <returns>Smallest currency unit</returns>
4032
public static int ToSmallestCurrencyUnit(this decimal value, MidpointRounding rounding = MidpointRounding.AwayFromZero)
4133
{
42-
var result = Math.Round(value * 100, 0, MidpointRounding.AwayFromZero);
34+
var result = Math.Round(value * 100, 0, rounding);
4335
return Convert.ToInt32(result);
4436
}
37+
38+
//public static decimal RoundToNearest(this decimal value, decimal nearest, bool roundUp)
39+
//{
40+
// if (nearest == decimal.Zero)
41+
// {
42+
// return value;
43+
// }
44+
45+
// if (roundUp)
46+
// {
47+
// return Math.Ceiling(value / nearest) * nearest;
48+
// }
49+
// else
50+
// {
51+
// return Math.Floor(value / nearest) * nearest;
52+
// }
53+
//}
54+
55+
/// <summary>
56+
/// Rounds and formats a decimal culture invariant
57+
/// </summary>
58+
/// <param name="value">Decimal to round</param>
59+
/// <param name="decimals">Rounding decimal number</param>
60+
/// <returns>Rounded and formated value</returns>
61+
public static string FormatInvariant(this decimal value, int decimals = 2)
62+
{
63+
return Math.Round(value, decimals).ToString("0.00", CultureInfo.InvariantCulture);
64+
}
65+
66+
/// <summary>
67+
/// Rounds and formats a currency value culture invariant
68+
/// </summary>
69+
/// <param name="value">Value to round</param>
70+
/// <param name="currency">Rounding method providing currency</param>
71+
/// <returns>Rounded and formated value</returns>
72+
public static string FormatInvariant(this decimal value, Currency currency)
73+
{
74+
Guard.NotNull(currency, nameof(currency));
75+
76+
var result = value
77+
.Round(currency.RoundingMethod)
78+
.ToString("0.00", CultureInfo.InvariantCulture);
79+
80+
return result;
81+
}
82+
83+
/// <summary>
84+
/// Rounds a currency value
85+
/// </summary>
86+
/// <param name="value">Value to round</param>
87+
/// <param name="currency">Rounding method providing currency</param>
88+
/// <returns>Rounded value</returns>
89+
public static decimal Round(this decimal value, Currency currency)
90+
{
91+
Guard.NotNull(currency, nameof(currency));
92+
93+
return value.Round(currency.RoundingMethod);
94+
}
95+
96+
/// <summary>
97+
/// Rounds a currency value
98+
/// </summary>
99+
/// <param name="value">Value to round</param>
100+
/// <param name="method">Currency rounding method</param>
101+
/// <returns>Rounded value</returns>
102+
/// <see cref="https://en.wikipedia.org/wiki/Cash_rounding"/>
103+
public static decimal Round(this decimal value, CurrencyRoundingMethod method)
104+
{
105+
var result = Math.Round(value, 2);
106+
var frac = (result - Math.Truncate(result)) * 10;
107+
108+
if (frac == decimal.Zero)
109+
return result;
110+
111+
switch (method)
112+
{
113+
case CurrencyRoundingMethod.Down005:
114+
case CurrencyRoundingMethod.Up005:
115+
frac = (frac - Math.Truncate(frac)) * 10;
116+
117+
if (method == CurrencyRoundingMethod.Down005)
118+
{
119+
frac = frac < 5 ? -1 * frac : 5 - frac;
120+
}
121+
else
122+
{
123+
frac = (frac < 5 ? 5 : 10) - frac;
124+
}
125+
126+
result += frac / 100;
127+
break;
128+
129+
case CurrencyRoundingMethod.Down01:
130+
case CurrencyRoundingMethod.Up01:
131+
frac = (frac - Math.Truncate(frac)) * 10;
132+
133+
if (method == CurrencyRoundingMethod.Down01 && frac == 5)
134+
{
135+
frac = -5;
136+
}
137+
else
138+
{
139+
frac = frac < 5 ? -1 * frac : 10 - frac;
140+
}
141+
142+
result += frac / 100;
143+
break;
144+
145+
case CurrencyRoundingMethod.Interval05:
146+
frac *= 10;
147+
148+
if (frac < 25)
149+
{
150+
frac *= -1;
151+
}
152+
else
153+
{
154+
frac = (frac < 50 || frac < 75 ? 50 : 100) - frac;
155+
}
156+
157+
result += frac / 100;
158+
break;
159+
160+
case CurrencyRoundingMethod.Interval1:
161+
case CurrencyRoundingMethod.Up1:
162+
frac *= 10;
163+
164+
if (method == CurrencyRoundingMethod.Up1 && frac > 0)
165+
{
166+
result = Math.Truncate(result) + 1;
167+
}
168+
else
169+
{
170+
result = frac < 50 ? Math.Truncate(result) : Math.Truncate(result) + 1;
171+
}
172+
break;
173+
}
174+
175+
return result;
176+
}
45177
}
46178
}

src/Libraries/SmartStore.Core/SmartStore.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
<Compile Include="Domain\DataExchange\DataExchangeSettings.cs" />
218218
<Compile Include="Domain\DataExchange\ExportDeployment.cs" />
219219
<Compile Include="Domain\DataExchange\ExportProjection.cs" />
220+
<Compile Include="Domain\Directory\CurrencyRoundingMethod.cs" />
220221
<Compile Include="Domain\Directory\QuantityUnit.cs" />
221222
<Compile Include="Domain\DataExchange\ExportEnums.cs" />
222223
<Compile Include="Domain\DataExchange\ExportFilter.cs" />

src/Libraries/SmartStore.Data/Migrations/201709300842479_CurrencyRounding.Designer.cs

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
namespace SmartStore.Data.Migrations
2+
{
3+
using Setup;
4+
using System.Data.Entity.Migrations;
5+
6+
public partial class CurrencyRounding : DbMigration, ILocaleResourcesProvider, IDataSeeder<SmartObjectContext>
7+
{
8+
public override void Up()
9+
{
10+
AddColumn("dbo.Currency", "RoundingMethod", c => c.Int(nullable: false));
11+
}
12+
13+
public override void Down()
14+
{
15+
DropColumn("dbo.Currency", "RoundingMethod");
16+
}
17+
18+
public bool RollbackOnFailure
19+
{
20+
get { return false; }
21+
}
22+
23+
public void Seed(SmartObjectContext context)
24+
{
25+
context.MigrateLocaleResources(MigrateLocaleResources);
26+
27+
context.SaveChanges();
28+
}
29+
30+
public void MigrateLocaleResources(LocaleResourcesBuilder builder)
31+
{
32+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Default",
33+
"Default",
34+
"Standard");
35+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Down005",
36+
"Round down to nearest 0.05",
37+
"Auf nächste 0,05 abrunden");
38+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Up005",
39+
"Round up to nearest 0.05",
40+
"Auf nächste 0,05 aufrunden");
41+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Down01",
42+
"Round down to nearest 0.10",
43+
"Auf nächste 0,10 abrunden");
44+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Up01",
45+
"Round up to nearest 0.10",
46+
"Auf nächste 0,10 aufrunden");
47+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Interval05",
48+
"Round in 0.50 intervals",
49+
"In 0,50 Intervalle runden");
50+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Interval1",
51+
"Round in 1.00 intervals",
52+
"In 1,00 Intervalle runden");
53+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingMethod.Up1",
54+
"Round up to nearest 1.00",
55+
"Auf nächste 1,00 aufrunden");
56+
57+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod",
58+
"Round",
59+
"Runden",
60+
"Specifies how to round currency values.",
61+
"Legt fest, wie Währungswerte gerundet werden sollen.");
62+
63+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Default.Hint",
64+
"Examples:<br />9.4548 round down to 9.45<br />9.4568 round up to 9.46",
65+
"Beispiele:<br />9,4548 abrunden nach 9,45<br />9,4568 aufrunden nach 9,46");
66+
67+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Down005.Hint",
68+
"Examples:<br />9.43 round down to 9.40<br />9.46 round down to 9.45<br />9.496 round up to 9.50",
69+
"Beispiele:<br />9,43 abrunden nach 9,40<br />9,46 abrunden nach 9,45<br />9,496 aufrunden nach 9,50");
70+
71+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Up005.Hint",
72+
"Examples:<br />9.001 round down to 9.00<br />9.02 round up to 9.05<br />9.08 round up to 9.10",
73+
"Beispiele:<br />9,001 abrunden nach 9,00<br />9,02 aufrunden nach 9,05<br />9,08 aufrunden nach 9,10");
74+
75+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Down01.Hint",
76+
"Examples:<br />9.43 round down to 9.40<br />9.46 round up to 9.50",
77+
"Beispiele:<br />9,43 abrunden nach 9,40<br />9,46 aufrunden nach 9,50");
78+
79+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Up01.Hint",
80+
"Examples:<br />9.02 round down to 9.00<br />9.08 round up to 9.10",
81+
"Beispiele:<br />9,02 abrunden nach 9,00<br />9,08 aufrunden nach 9,10");
82+
83+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Interval05.Hint",
84+
"0.01 - 0.24: round down to 0.00<br />0.25 - 0.49: round up to 0.50<br />0.51 - 0.74: round down to 0.50<br />0.75 - 0.99: round up to next integer",
85+
"0,01 - 0,24: abrunden nach 0,00<br />0,25 - 0,49: aufrunden nach 0,50<br />0,51 - 0,74: abrunden nach 0,50<br />0,75 - 0,99: aufrunden zur nächsten Ganzzahl");
86+
87+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Interval1.Hint",
88+
"0.01 - 0.49: round down to 0.00<br />0.50 - 0.99: round up to next integer",
89+
"0,01 - 0,49: abrunden nach 0,00<br />0,50 - 0,99: aufrunden zur nächsten Ganzzahl");
90+
91+
builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundingMethod.Up1.Hint",
92+
"Always round up decimals to next integer",
93+
"Dezimalzahlen immer zur nächsten Ganzzahl aufrunden");
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)