1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-09 23:45:21 +02:00

feature: money abbreviate formatting

This commit is contained in:
Luan Estradioto 2025-04-23 21:15:25 -03:00
parent 210b89cd17
commit a352b3b2d1
3 changed files with 83 additions and 5 deletions

View file

@ -3,7 +3,7 @@
<div class="space-y-4">
<% balance_sheet.classification_groups.each do |classification_group| %>
<div class="bg-container shadow-border-xs rounded-xl space-y-4 p-4">
<h2 class="text-lg font-medium"><%= classification_group.display_name %></h2>
<h2 class="text-lg font-medium"><%= classification_group.display_name %> · <span class="text-secondary"><%= classification_group.key == "asset" ? format_money(balance_sheet.total_assets, abbreviate: true) : format_money(balance_sheet.total_liabilities, abbreviate: true) %></span></h2>
<% if classification_group.account_groups.any? %>
<div class="space-y-4">

View file

@ -5,7 +5,11 @@ module Money::Formatting
locale = options[:locale] || I18n.locale
default_opts = format_options(locale)
number_to_currency(amount, default_opts.merge(options))
if options[:abbreviate]
format_abbreviated(default_opts.merge(options))
else
number_to_currency(amount, default_opts.merge(options))
end
end
alias_method :to_s, :format
@ -22,6 +26,39 @@ module Money::Formatting
end
private
def format_abbreviated(options)
threshold = options[:abbreviate_threshold] || 1000
abs_amount = amount.abs
return number_to_currency(amount, options) if abs_amount < threshold
suffixes = options[:unit_suffixes]
if abs_amount >= 1_000_000_000
unit_suffix = suffixes[:billion]
value = abs_amount / 1_000_000_000.0
elsif abs_amount >= 1_000_000
unit_suffix = suffixes[:million]
value = abs_amount / 1_000_000.0
else
unit_suffix = suffixes[:thousand]
value = abs_amount / 1000.0
end
# Always use period as decimal separator for abbreviations and don't round up
abbrev_options = options.merge(precision: 1, separator: ".", round_mode: :down)
value_formatted = number_to_rounded(value, abbrev_options)
abbreviated_num = "#{value_formatted}#{unit_suffix}"
if options[:format].to_s.include?("%u %n")
formatted = "#{options[:unit]} #{abbreviated_num}"
else
formatted = "#{options[:unit]}#{abbreviated_num}"
end
amount.negative? ? "-#{formatted}" : formatted
end
def get_symbol
if currency.symbol == "$" && currency.iso_code != "USD"
[ currency.iso_code.first(2), currency.symbol ].join
@ -33,11 +70,20 @@ module Money::Formatting
def locale_options(locale)
case [ currency.iso_code, locale.to_sym ]
when [ "EUR", :nl ], [ "EUR", :pt ]
{ delimiter: ".", separator: ",", format: "%u %n" }
{
delimiter: ".",
separator: ",",
format: "%u %n",
unit_suffixes: { thousand: "K", million: "M", billion: "B" }
}
when [ "EUR", :en ], [ "EUR", :en_IE ]
{ delimiter: ",", separator: "." }
{
delimiter: ",",
separator: ".",
unit_suffixes: { thousand: "K", million: "M", billion: "B" }
}
else
{}
{ unit_suffixes: { thousand: "K", million: "M", billion: "B" } }
end
end
end

View file

@ -90,6 +90,38 @@ class MoneyTest < ActiveSupport::TestCase
assert_equal "€ 1.000,12", Money.new(1000.12, :eur).format(locale: :nl)
end
test "can format with abbreviation" do
# Values below 1000 should be formatted normally
assert_equal "$500.00", Money.new(500).format(abbreviate: true)
assert_equal "$999.99", Money.new(999.99).format(abbreviate: true)
assert_equal "-$500.00", Money.new(-500).format(abbreviate: true)
assert_equal "$1.0K", Money.new(1000).format(abbreviate: true)
assert_equal "$1.5K", Money.new(1500).format(abbreviate: true)
assert_equal "$999.9K", Money.new(999900).format(abbreviate: true)
assert_equal "-$1.5K", Money.new(-1500).format(abbreviate: true)
assert_equal "$1.0M", Money.new(1_000_000).format(abbreviate: true)
assert_equal "$1.5M", Money.new(1_500_000).format(abbreviate: true)
assert_equal "$999.9M", Money.new(999_900_000).format(abbreviate: true)
assert_equal "-$1.5M", Money.new(-1_500_000).format(abbreviate: true)
assert_equal "$1.0B", Money.new(1_000_000_000).format(abbreviate: true)
assert_equal "$1.5B", Money.new(1_500_000_000).format(abbreviate: true)
assert_equal "$999.9B", Money.new(999_900_000_000).format(abbreviate: true)
assert_equal "-$1.5B", Money.new(-1_500_000_000).format(abbreviate: true)
assert_equal "€1.5M", Money.new(1_500_000, :EUR).format(abbreviate: true)
assert_equal "£1.5M", Money.new(1_500_000, :GBP).format(abbreviate: true)
assert_equal "CA$1.5M", Money.new(1_500_000, :CAD).format(abbreviate: true)
assert_equal "$900.00", Money.new(900).format(abbreviate: true, abbreviate_threshold: 1000)
assert_equal "$0.9K", Money.new(900).format(abbreviate: true, abbreviate_threshold: 500)
assert_equal "$0.1K", Money.new(100).format(abbreviate: true, abbreviate_threshold: 50)
assert_equal "€ 1.5M", Money.new(1_500_000, :EUR).format(abbreviate: true, locale: :nl)
end
test "converts currency when rate available" do
ExchangeRate.expects(:find_or_fetch_rate).returns(OpenStruct.new(rate: 1.2))