diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index c08078e0..5cb8dd62 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -42,7 +42,7 @@ } .form-field__input { - @apply p-3 pt-1 w-full bg-transparent border-none opacity-50; + @apply p-3 w-full bg-transparent border-none opacity-50; @apply focus:outline-none focus:ring-0 focus:opacity-100; } diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index 484fd7c8..88ebb2a9 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -35,7 +35,7 @@ class TransactionsController < ApplicationController respond_to do |format| if @transaction.save - format.html { redirect_to transaction_url(@transaction), notice: t(".success") } + format.html { redirect_to transactions_url, notice: t(".success") } else format.html { render :new, status: :unprocessable_entity } end diff --git a/app/helpers/application_form_builder.rb b/app/helpers/application_form_builder.rb index e5c1f24a..c7b96857 100644 --- a/app/helpers/application_form_builder.rb +++ b/app/helpers/application_form_builder.rb @@ -22,6 +22,34 @@ class ApplicationFormBuilder < ActionView::Helpers::FormBuilder RUBY_EVAL end + # See `Monetizable` concern, which adds a _money suffix to the attribute name + # For a monetized field, the setter will always be the attribute name without the _money suffix + def money_field(method, options = {}) + money = @object.send(method) + raise ArgumentError, "The value of #{method} is not a Money object" unless money.is_a?(Money) || money.nil? + + money_amount_method = method.to_s.chomp("_money").to_sym + money_currency_method = :currency + + readonly_currency = options[:readonly_currency] || false + + default_options = { + class: "form-field__input", + value: money&.amount, + placeholder: Money.new(0, money&.currency || Money.default_currency).format + } + + merged_options = default_options.merge(options) + + @template.form_field_tag do + (label(method, *label_args(options)).to_s if options[:label]) + + @template.tag.div(class: "flex items-center") do + number_field(money_amount_method, merged_options.except(:label)) + + select(money_currency_method, Money::Currency.popular.map(&:iso_code), { selected: money&.currency&.iso_code }, { disabled: readonly_currency, class: "ml-auto form-field__input w-fit pr-8" }) + end + end + end + def select(method, choices, options = {}, html_options = {}) default_options = { class: "form-field__input" } merged_options = default_options.merge(html_options) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c7051ed2..43e6f831 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -27,9 +27,7 @@ module ApplicationHelper render partial: "shared/modal", locals: { content: content } end - def currency_dropdown(f: nil, options: []) - render partial: "shared/currency_dropdown", locals: { f: f, options: options } - end + def sidebar_modal(&block) content = capture &block @@ -97,12 +95,15 @@ module ApplicationHelper end end - def format_currency(number, options = {}) - user_currency_preference = Current.family.try(:currency) || "USD" + def format_money(number_or_money, options = {}) + money = Money.new(number_or_money) + options.reverse_merge!(money.default_format_options) + number_to_currency(money.amount, options) + end - currency_options = CURRENCY_OPTIONS[user_currency_preference.to_sym] - options.reverse_merge!(currency_options) - - number_to_currency(number, options) + def format_money_without_symbol(number_or_money, options = {}) + money = Money.new(number_or_money) + options.reverse_merge!(money.default_format_options) + ActiveSupport::NumberHelper.number_to_delimited(money.amount.round(options[:precision] || 0), { delimiter: options[:delimiter], separator: options[:separator] }) end end diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb index ac138976..4228b7bf 100644 --- a/app/helpers/forms_helper.rb +++ b/app/helpers/forms_helper.rb @@ -2,4 +2,8 @@ module FormsHelper def form_field_tag(&) tag.div class: "form-field", & end + + def currency_dropdown(f: nil, options: []) + render partial: "shared/currency_dropdown", locals: { f: f, options: options } + end end diff --git a/app/javascript/controllers/line_chart_controller.js b/app/javascript/controllers/line_chart_controller.js index 2d16079a..9fbb70ba 100644 --- a/app/javascript/controllers/line_chart_controller.js +++ b/app/javascript/controllers/line_chart_controller.js @@ -17,7 +17,7 @@ export default class extends Controller { renderChart = () => { this.drawChart(this.seriesValue); - } + }; trendStyles(trendDirection) { return { @@ -45,11 +45,11 @@ export default class extends Controller { formatted: { value: Intl.NumberFormat("en-US", { style: "currency", - currency: b.currency || "USD", + currency: b.currency.iso_code || "USD", }).format(b.amount), change: Intl.NumberFormat("en-US", { style: "currency", - currency: b.currency || "USD", + currency: b.currency.iso_code || "USD", signDisplay: "always", }).format(b.trend.amount), }, diff --git a/app/models/account.rb b/app/models/account.rb index 5c77a16c..fb5f7037 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -1,5 +1,6 @@ class Account < ApplicationRecord include Syncable + include Monetizable validates :family, presence: true @@ -9,6 +10,8 @@ class Account < ApplicationRecord has_many :valuations has_many :transactions + monetize :balance + enum :status, { ok: "ok", syncing: "syncing", error: "error" }, validate: true scope :active, -> { where(is_active: true) } diff --git a/app/models/account/balance_calculator.rb b/app/models/account/balance_calculator.rb index 640ec640..9dbf2f55 100644 --- a/app/models/account/balance_calculator.rb +++ b/app/models/account/balance_calculator.rb @@ -6,8 +6,8 @@ class Account::BalanceCalculator def daily_balances(start_date = nil) calc_start_date = [ start_date, @account.effective_start_date ].compact.max - valuations = @account.valuations.where("date >= ?", calc_start_date).order(:date).select(:date, :value) - transactions = @account.transactions.where("date > ?", calc_start_date).order(:date).select(:date, :amount) + valuations = @account.valuations.where("date >= ?", calc_start_date).order(:date).select(:date, :value, :currency) + transactions = @account.transactions.where("date > ?", calc_start_date).order(:date).select(:date, :amount, :currency) oldest_entry = [ valuations.first, transactions.first ].compact.min_by(&:date) net_transaction_flows = transactions.sum(&:amount) diff --git a/app/models/account_balance.rb b/app/models/account_balance.rb index 9915ddde..6a47d368 100644 --- a/app/models/account_balance.rb +++ b/app/models/account_balance.rb @@ -1,7 +1,9 @@ class AccountBalance < ApplicationRecord - belongs_to :account + include Monetizable + belongs_to :account validates :account, :date, :balance, presence: true + monetize :balance scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) } diff --git a/app/models/concerns/monetizable.rb b/app/models/concerns/monetizable.rb new file mode 100644 index 00000000..909c7210 --- /dev/null +++ b/app/models/concerns/monetizable.rb @@ -0,0 +1,14 @@ +module Monetizable + extend ActiveSupport::Concern + + class_methods do + def monetize(*fields) + fields.each do |field| + define_method("#{field}_money") do + value = self.send(field) + value.nil? ? nil : Money.new(value, currency) + end + end + end + end +end diff --git a/app/models/family.rb b/app/models/family.rb index 3f81b93b..a04522e1 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -1,9 +1,13 @@ class Family < ApplicationRecord + include Monetizable + has_many :users, dependent: :destroy has_many :accounts, dependent: :destroy has_many :transactions, through: :accounts has_many :transaction_categories, dependent: :destroy, class_name: "Transaction::Category" + monetize :net_worth, :assets, :liabilities + def snapshot(period = Period.all) query = accounts.active.joins(:balances) .where("account_balances.currency = ?", self.currency) @@ -35,7 +39,7 @@ class Family < ApplicationRecord end def assets - accounts.active.assets.sum(:balance) + accounts.active.assets.sum(:balance) end def liabilities diff --git a/app/models/money.rb b/app/models/money.rb deleted file mode 100644 index b61c1fb3..00000000 --- a/app/models/money.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Money - attr_reader :amount, :currency - - def self.from_amount(amount, currency = "USD") - Money.new(amount, currency) - end - - def initialize(amount, currency = :USD) - @amount = amount - @currency = currency - end - - def cents(precision: nil) - _precision = precision || CURRENCY_OPTIONS[@currency.to_sym][:precision] - return "" unless _precision.positive? - - fractional_part = @amount.to_s.split(".")[1] || "" - fractional_part = fractional_part[0, _precision].ljust(_precision, "0") - end - - def symbol - CURRENCY_OPTIONS[@currency.to_sym][:symbol] - end - - def separator - CURRENCY_OPTIONS[@currency.to_sym][:separator] - end - - def precision - CURRENCY_OPTIONS[@currency.to_sym][:precision] - end -end diff --git a/app/models/money_series.rb b/app/models/money_series.rb index f9cea5d9..c55d095f 100644 --- a/app/models/money_series.rb +++ b/app/models/money_series.rb @@ -14,7 +14,7 @@ class MoneySeries { raw: current, date: current.date, - value: Money.from_amount(current.send(@accessor), current.currency), + value: Money.new(current.send(@accessor), current.currency), trend: Trend.new( current: current.send(@accessor), previous: previous&.send(@accessor), diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 43ea6480..6e8cdbca 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -1,4 +1,6 @@ class Transaction < ApplicationRecord + include Monetizable + belongs_to :account belongs_to :category, optional: true @@ -6,6 +8,12 @@ class Transaction < ApplicationRecord after_commit :sync_account + monetize :amount + + scope :inflows, -> { where("amount > 0") } + scope :outflows, -> { where("amount < 0") } + scope :active, -> { where(excluded: false) } + def self.ransackable_attributes(auth_object = nil) %w[name amount date] end diff --git a/app/models/valuation.rb b/app/models/valuation.rb index 70c520bf..2f8dc919 100644 --- a/app/models/valuation.rb +++ b/app/models/valuation.rb @@ -1,9 +1,10 @@ class Valuation < ApplicationRecord + include Monetizable + belongs_to :account - validates :account, :date, :value, presence: true - after_commit :sync_account + monetize :value scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) } diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb index 745c9549..ac7c27bc 100644 --- a/app/views/accounts/_account.html.erb +++ b/app/views/accounts/_account.html.erb @@ -10,7 +10,7 @@

"> - <%= format_currency account.balance %> + <%= format_money account.balance_money %>

<%= form_with model: account, method: :patch, html: { class: "flex items-center", data: { turbo_frame: "_top" } } do |form| %>
diff --git a/app/views/accounts/_account_list.html.erb b/app/views/accounts/_account_list.html.erb index f5e962e8..fb7f42aa 100644 --- a/app/views/accounts/_account_list.html.erb +++ b/app/views/accounts/_account_list.html.erb @@ -1,31 +1,27 @@ <%# locals: (type:) -%> - <% accounts = Current.family.accounts.where(accountable_type: type.name) %> - <% if accounts.sum(&:converted_balance) > 0 %> -
- - <%= lucide_icon("chevron-down", class: "hidden group-open:block text-gray-500 w-5 h-5") %> - <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %> -
<%= type.model_name.human %>
-
<%= format_currency accounts.sum(&:converted_balance) %>
-
- - <% accounts.each do |account| %> - <%= link_to account_path(account), class: "flex items-center w-full gap-3 px-2 py-3 mb-1 hover:bg-gray-100 rounded-[10px]" do %> -
-

<%= account.name %>

- <% if account.subtype %> -

<%= account.subtype&.humanize %>

- <% end %> -
-

<%= format_currency account.converted_balance %>

+
+ + <%= lucide_icon("chevron-down", class: "hidden group-open:block text-gray-500 w-5 h-5") %> + <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %> +
<%= type.model_name.human %>
+
<%= format_money accounts.sum(&:converted_balance) %>
+
+ <% accounts.each do |account| %> + <%= link_to account_path(account), class: "flex items-center w-full gap-3 px-2 py-3 mb-1 hover:bg-gray-100 rounded-[10px]" do %> +
+

<%= account.name %>

+ <% if account.subtype %> +

<%= account.subtype&.humanize %>

+ <% end %> +
+

<%= format_money account.converted_balance %>

+ <% end %> <% end %> - <% end %> - - <%= link_to new_account_path(step: 'method', type: type.name.demodulize), class: "flex items-center gap-4 px-2 py-3 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %> - <%= lucide_icon("plus", class: "w-5 h-5") %> -

New <%= type.model_name.human.downcase %>

- <% end %> -
+ <%= link_to new_account_path(step: 'method', type: type.name.demodulize), class: "flex items-center gap-4 px-2 py-3 mb-1 text-gray-500 text-sm font-medium rounded-[10px] hover:bg-gray-100", data: { turbo_frame: "modal" } do %> + <%= lucide_icon("plus", class: "w-5 h-5") %> +

New <%= type.model_name.human.downcase %>

+ <% end %> +
<% end %> diff --git a/app/views/accounts/_account_valuation_list.html.erb b/app/views/accounts/_account_valuation_list.html.erb index e9244e13..f6ddd597 100644 --- a/app/views/accounts/_account_valuation_list.html.erb +++ b/app/views/accounts/_account_valuation_list.html.erb @@ -15,13 +15,13 @@ <%# TODO: Add descriptive name of valuation %>

Manually entered

-
<%= format_currency(valuation.value) %>
+
<%= format_money valuation.value_money %>
<% if trend.amount == 0 %> No change <% else %> - <%= valuation_styles[:symbol] %><%= format_currency(trend.amount.abs) %> + <%= valuation_styles[:symbol] %><%= format_money trend.amount.abs %> (<%= lucide_icon(valuation_styles[:icon], class: "w-4 h-4 align-text-bottom inline") %> <%= trend.percent %>%) <% end %>
diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb index f1e54e7a..21f5ec1c 100644 --- a/app/views/accounts/index.html.erb +++ b/app/views/accounts/index.html.erb @@ -40,7 +40,7 @@

<%= to_accountable_title(Accountable.from_type(group)) %>

·

<%= accounts.count %>

-

<%= format_currency accounts.sum(&:balance) %>

+

<%= format_money accounts.sum(&:balance_money) %>

<% accounts.each do |account| %> diff --git a/app/views/accounts/new.html.erb b/app/views/accounts/new.html.erb index 3dfa82d7..f17a842d 100644 --- a/app/views/accounts/new.html.erb +++ b/app/views/accounts/new.html.erb @@ -69,11 +69,7 @@ <%= f.hidden_field :accountable_type %> <%= f.text_field :name, placeholder: t('accounts.new.name.placeholder'), required: 'required', label: t('accounts.new.name.label'), autofocus: true %> <%= render "accounts/#{permitted_accountable_partial(@account.accountable_type)}", f: f %> - <%= form_field_tag do %> - <%= f.label :balance, class: "form-field__label" %> - <%= f.number_field :balance, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: 'form-field__input max-w-[80%]' %> - <%= currency_dropdown(f: f, options: Currency.all.order(:iso_code).pluck(:iso_code)) if Currency.count > 1 %> - <% end %> + <%= f.money_field :balance_money, label: "Balance", required: 'required' %>
<%= f.submit "Add #{@account.accountable.model_name.human.downcase}" %> <% end %> diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 11123b4c..bab1c98f 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -1,5 +1,5 @@ <%= turbo_stream_from @account %> -<% balance = Money.from_amount(@account.balance, @account.currency) %> +<% balance = Money.new(@account.balance, @account.currency) %>
@@ -11,7 +11,7 @@
- <%= balance.currency %> <%= balance.symbol %> + <%= balance.currency.iso_code %> <%= balance.currency.symbol %> <%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
@@ -29,7 +29,7 @@ <%= render partial: "shared/balance_heading", locals: { label: "Total Value", period: @period, - balance: Money.from_amount(@account.balance, @account.currency), + balance: @account.balance_money, trend: @balance_series.trend } %> diff --git a/app/views/pages/_account_group_disclosure.erb b/app/views/pages/_account_group_disclosure.erb index db63c46a..d6b83632 100644 --- a/app/views/pages/_account_group_disclosure.erb +++ b/app/views/pages/_account_group_disclosure.erb @@ -15,7 +15,7 @@

<%= account_details[:allocation] %>%

-

<%= format_currency account_details[:end_balance] %>

+

<%= format_money account_details[:end_balance] %>

<%= render partial: "shared/trend_change", locals: { trend: account_details[:trend] } %> @@ -39,7 +39,7 @@

<%= account[:allocation] %>%

-

<%= format_currency account[:end_balance] %>

+

<%= format_money account[:end_balance] %>

<%= render partial: "shared/trend_change", locals: { trend: account[:trend] } %> diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb index 6e1a541f..75afb1e2 100644 --- a/app/views/pages/dashboard.html.erb +++ b/app/views/pages/dashboard.html.erb @@ -10,7 +10,7 @@ <%= render partial: "shared/balance_heading", locals: { label: "Net Worth", period: @period, - balance: Money.from_amount(Current.family.net_worth, Current.family.currency), + balance: Current.family.net_worth_money, trend: @net_worth_series.trend } %> @@ -26,7 +26,7 @@ <%= render partial: "shared/balance_heading", locals: { label: "Assets", period: @period, - balance: Money.from_amount(Current.family.assets, Current.family.currency), + balance: Current.family.assets_money, trend: @asset_series.trend } %>
@@ -44,7 +44,7 @@ label: "Liabilities", period: @period, size: "md", - balance: Money.from_amount(Current.family.liabilities, Current.family.currency), + balance: Current.family.liabilities_money, trend: @liability_series.trend } %>
diff --git a/app/views/shared/_balance_heading.html.erb b/app/views/shared/_balance_heading.html.erb index d93543d7..403327bb 100644 --- a/app/views/shared/_balance_heading.html.erb +++ b/app/views/shared/_balance_heading.html.erb @@ -2,11 +2,11 @@

<%= label %>

- <%= balance.symbol %> - font-medium"><%= format_currency(balance.amount, precision: 0, unit: '') %> - <%- if balance.precision.positive? -%> + <%= balance.currency.symbol %> + font-medium"><%= format_money_without_symbol balance, precision: 0 %> + <%- if balance.currency.default_precision.positive? -%> - <%= balance.separator %><%= balance.cents %> + <%= balance.currency.separator %><%= balance.cents_str %> <% end %>

diff --git a/app/views/shared/_trend_change.html.erb b/app/views/shared/_trend_change.html.erb index 142f4e03..f0bbc0ec 100644 --- a/app/views/shared/_trend_change.html.erb +++ b/app/views/shared/_trend_change.html.erb @@ -4,7 +4,7 @@ <% if trend.direction == "flat" %> No change <% else %> - <%= styles[:symbol] %><%= format_currency(trend.amount.abs) %> + <%= styles[:symbol] %><%= format_money trend.amount.abs %> (<%= lucide_icon(styles[:icon], class: "w-4 h-4 align-text-bottom inline") %><%= trend.percent %>%) <% end %>

\ No newline at end of file diff --git a/app/views/transactions/_form.html.erb b/app/views/transactions/_form.html.erb index 9d71cb14..7799f8c2 100644 --- a/app/views/transactions/_form.html.erb +++ b/app/views/transactions/_form.html.erb @@ -1,7 +1,7 @@ <%= form_with model: @transaction do |f| %> <%= f.collection_select :account_id, Current.family.accounts, :id, :name, { prompt: "Select an Account", label: "Account" } %> <%= f.date_field :date, label: "Date" %> - <%= f.text_field :name, label: "Name" %> - <%= f.number_field :amount, label: "Amount", step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00 %> + <%= f.text_field :name, label: "Name", placeholder: "Groceries" %> + <%= f.money_field :amount_money, label: "Amount" %> <%= f.submit %> <% end %> diff --git a/app/views/transactions/_transaction.html.erb b/app/views/transactions/_transaction.html.erb index 0a1eace0..fac70440 100644 --- a/app/views/transactions/_transaction.html.erb +++ b/app/views/transactions/_transaction.html.erb @@ -12,6 +12,6 @@

<%= transaction.account.name %>

-

"><%= number_to_currency(-transaction.amount, { precision: 2 }) %>

+

"><%= format_money -transaction.amount_money %>

<% end %> diff --git a/app/views/transactions/_transaction_group.html.erb b/app/views/transactions/_transaction_group.html.erb index 6de9d6a6..a1517ce0 100644 --- a/app/views/transactions/_transaction_group.html.erb +++ b/app/views/transactions/_transaction_group.html.erb @@ -2,7 +2,7 @@

<%= date.strftime('%b %d, %Y') %> · <%= transactions.size %>

- <%= number_to_currency(-transactions.sum(&:amount)) %> + <%= format_money -transactions.sum(&:amount_money) %>
<%= render partial: "transactions/transaction", collection: transactions %> diff --git a/app/views/transactions/index.html.erb b/app/views/transactions/index.html.erb index cb6e8afb..a03872e7 100644 --- a/app/views/transactions/index.html.erb +++ b/app/views/transactions/index.html.erb @@ -23,13 +23,13 @@

Income

- <%= number_to_currency(@transactions.select { |t| t.amount < 0 }.sum(&:amount).abs, precision: 2) %> + <%= format_money @transactions.inflows.sum(&:amount_money).abs %>

Expenses

- <%= number_to_currency(@transactions.select { |t| t.amount >= 0 }.sum(&:amount), precision: 2) %> + <%= format_money @transactions.outflows.sum(&:amount_money) %>

@@ -50,7 +50,6 @@
<%= form.select :date, options_for_select([['All', 'all'], ['7D', 'last_7_days'], ['1M', 'last_30_days'], ["1Y", "last_365_days"]], selected: params.dig(:q, :date)), {}, { class: "block h-full w-full border border-gray-200 rounded-lg text-sm py-2 pr-8 pl-2", "data-transactions-search-target": "date" } %> - <%= form.hidden_field :date_gteq, value: '', "data-transactions-search-target": "dateGteq" %> <%= form.hidden_field :date_lteq, value: '', "data-transactions-search-target": "dateLteq" %> @@ -76,14 +75,12 @@

amount

-
<% @transactions.group_by { |transaction| transaction.date }.each do |date, grouped_transactions| %> <%= render partial: "transactions/transaction_group", locals: { date: date, transactions: grouped_transactions } %> <% end %>
<% end %> - <% if @pagy.pages > 1 %>
- <%= f.date_field :date, label: "Date" %>
<%= f.collection_select :account_id, Current.family.accounts, :id, :name, { prompt: "Select an Account", label: "Account", class: "text-gray-400" }, {class: "form-field__input cursor-not-allowed text-gray-400", disabled: "disabled"} %> -
@@ -28,10 +25,8 @@ <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
- <%= f.text_field :name, label: "Name" %>
-
@@ -40,7 +35,6 @@ <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
-
-
@@ -59,10 +52,7 @@ <%= lucide_icon("chevron-right", class: "group-open:hidden text-gray-500 w-5 h-5") %>
- <%= f.text_area :notes, label: "Notes", placeholder: "Enter a note" %>
- <% end %> - <% end %> diff --git a/app/views/valuations/_form_row.html.erb b/app/views/valuations/_form_row.html.erb index 27ad7e5f..67614073 100644 --- a/app/views/valuations/_form_row.html.erb +++ b/app/views/valuations/_form_row.html.erb @@ -7,7 +7,7 @@
<%= f.date_field :date, required: 'required', class: "border border-alpha-black-200 bg-white rounded-lg shadow-xs min-w-[200px] px-3 py-1.5 text-gray-900 text-sm" %> - <%= f.number_field :value, step: :any, placeholder: number_to_currency(0), in: 0.00..100000000.00, required: 'required', class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %> + <%= f.number_field :value, required: 'required', placeholder: "0.00", class: "bg-white border border-alpha-black-200 rounded-lg shadow-xs text-gray-900 text-sm px-3 py-1.5 text-right" %>
<%= link_to "Cancel", account_path(@valuation.account), class: "text-sm text-gray-900 hover:text-gray-800 font-medium px-3 py-1.5" %> diff --git a/config/currencies.yml b/config/currencies.yml new file mode 100644 index 00000000..97f10d42 --- /dev/null +++ b/config/currencies.yml @@ -0,0 +1,2520 @@ +usd: + name: United States Dollar + priority: 1 + iso_code: USD + iso_numeric: "840" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +eur: + name: Euro + priority: 2 + iso_code: EUR + iso_numeric: "978" + html_code: "€" + symbol: "€" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +gbp: + name: British Pound + priority: 3 + iso_code: GBP + iso_numeric: "826" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +aud: + name: Australian Dollar + priority: 4 + iso_code: AUD + iso_numeric: "036" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +cad: + name: Canadian Dollar + priority: 5 + iso_code: CAD + iso_numeric: "124" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +jpy: + name: Japanese Yen + priority: 6 + iso_code: JPY + iso_numeric: "392" + html_code: "¥" + symbol: "¥" + minor_unit: + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 0 +byr: + name: Belarusian Ruble + priority: 50 + iso_code: BYR + iso_numeric: "974" + html_code: "" + symbol: Br + minor_unit: + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 0 +sar: + name: Saudi Riyal + priority: 100 + iso_code: SAR + iso_numeric: "682" + html_code: "﷼" + symbol: ر.س + minor_unit: Hallallah + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +sbd: + name: Solomon Islands Dollar + priority: 100 + iso_code: SBD + iso_numeric: "090" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +scr: + name: Seychellois Rupee + priority: 100 + iso_code: SCR + iso_numeric: "690" + html_code: "₨" + symbol: "₨" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +sdg: + name: Sudanese Pound + priority: 100 + iso_code: SDG + iso_numeric: "938" + html_code: "" + symbol: "£" + minor_unit: Piastre + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +sek: + name: Swedish Krona + priority: 100 + iso_code: SEK + iso_numeric: "752" + html_code: "" + symbol: kr + minor_unit: Öre + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 2 +sgd: + name: Singapore Dollar + priority: 100 + iso_code: SGD + iso_numeric: "702" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +shp: + name: Saint Helenian Pound + priority: 100 + iso_code: SHP + iso_numeric: "654" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +skk: + name: Slovak Koruna + priority: 100 + iso_code: SKK + iso_numeric: "703" + html_code: "" + symbol: Sk + minor_unit: Halier + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +sle: + name: New Leone + priority: 100 + iso_code: SLE + iso_numeric: "925" + html_code: "" + symbol: Le + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +sll: + name: Sierra Leonean Leone + priority: 100 + iso_code: SLL + iso_numeric: "694" + html_code: "" + symbol: Le + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +sos: + name: Somali Shilling + priority: 100 + iso_code: SOS + iso_numeric: "706" + html_code: "" + symbol: Sh + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +srd: + name: Surinamese Dollar + priority: 100 + iso_code: SRD + iso_numeric: "968" + html_code: "" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ssp: + name: South Sudanese Pound + priority: 100 + iso_code: SSP + iso_numeric: "728" + html_code: "£" + symbol: "£" + minor_unit: piaster + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +std: + name: São Tomé and Príncipe Dobra + priority: 100 + iso_code: STD + iso_numeric: "678" + html_code: "" + symbol: Db + minor_unit: Cêntimo + minor_unit_conversion: 100 + smallest_denomination: 10000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +stn: + name: São Tomé and Príncipe Second Dobra + priority: 100 + iso_code: STN + iso_numeric: "930" + html_code: "" + symbol: Db + minor_unit: Cêntimo + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +svc: + name: Salvadoran Colón + priority: 100 + iso_code: SVC + iso_numeric: "222" + html_code: "₡" + symbol: "₡" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +syp: + name: Syrian Pound + priority: 100 + iso_code: SYP + iso_numeric: "760" + html_code: "£" + symbol: "£S" + minor_unit: Piastre + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +szl: + name: Swazi Lilangeni + priority: 100 + iso_code: SZL + iso_numeric: "748" + html_code: "" + symbol: E + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +xbc: + name: European Unit of Account 9 + priority: 100 + iso_code: XBC + iso_numeric: "957" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +mvr: + name: Maldivian Rufiyaa + priority: 100 + iso_code: MVR + iso_numeric: "462" + html_code: "" + symbol: MVR + minor_unit: Laari + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mwk: + name: Malawian Kwacha + priority: 100 + iso_code: MWK + iso_numeric: "454" + html_code: "" + symbol: MK + minor_unit: Tambala + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mxn: + name: Mexican Peso + priority: 100 + iso_code: MXN + iso_numeric: "484" + html_code: "$" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +myr: + name: Malaysian Ringgit + priority: 100 + iso_code: MYR + iso_numeric: "458" + html_code: "" + symbol: RM + minor_unit: Sen + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +mzn: + name: Mozambican Metical + priority: 100 + iso_code: MZN + iso_numeric: "943" + html_code: "" + symbol: MTn + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +nad: + name: Namibian Dollar + priority: 100 + iso_code: NAD + iso_numeric: "516" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ngn: + name: Nigerian Naira + priority: 100 + iso_code: NGN + iso_numeric: "566" + html_code: "₦" + symbol: "₦" + minor_unit: Kobo + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +nio: + name: Nicaraguan Córdoba + priority: 100 + iso_code: NIO + iso_numeric: "558" + html_code: "" + symbol: C$ + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +nok: + name: Norwegian Krone + priority: 100 + iso_code: NOK + iso_numeric: "578" + html_code: kr + symbol: kr + minor_unit: Øre + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 2 +npr: + name: Nepalese Rupee + priority: 100 + iso_code: NPR + iso_numeric: "524" + html_code: "₨" + symbol: Rs. + minor_unit: Paisa + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +nzd: + name: New Zealand Dollar + priority: 100 + iso_code: NZD + iso_numeric: "554" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +omr: + name: Omani Rial + priority: 100 + iso_code: OMR + iso_numeric: "512" + html_code: "﷼" + symbol: ر.ع. + minor_unit: Baisa + minor_unit_conversion: 1000 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 3 +pab: + name: Panamanian Balboa + priority: 100 + iso_code: PAB + iso_numeric: "590" + html_code: "" + symbol: B/. + minor_unit: Centésimo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +pen: + name: Peruvian Sol + priority: 100 + iso_code: PEN + iso_numeric: "604" + html_code: S/ + symbol: S/ + minor_unit: Céntimo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +pgk: + name: Papua New Guinean Kina + priority: 100 + iso_code: PGK + iso_numeric: "598" + html_code: "" + symbol: K + minor_unit: Toea + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +php: + name: Philippine Peso + priority: 100 + iso_code: PHP + iso_numeric: "608" + html_code: "₱" + symbol: "₱" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +pkr: + name: Pakistani Rupee + priority: 100 + iso_code: PKR + iso_numeric: "586" + html_code: "₨" + symbol: "₨" + minor_unit: Paisa + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +pln: + name: Polish Złoty + priority: 100 + iso_code: PLN + iso_numeric: "985" + html_code: zł + symbol: zł + minor_unit: Grosz + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 2 +pyg: + name: Paraguayan Guaraní + priority: 100 + iso_code: PYG + iso_numeric: "600" + html_code: "₲" + symbol: "₲" + minor_unit: Céntimo + minor_unit_conversion: 1 + smallest_denomination: 5000 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 0 +qar: + name: Qatari Riyal + priority: 100 + iso_code: QAR + iso_numeric: "634" + html_code: "﷼" + symbol: ر.ق + minor_unit: Dirham + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ron: + name: Romanian Leu + priority: 100 + iso_code: RON + iso_numeric: "946" + html_code: "" + symbol: Lei + minor_unit: Bani + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 2 +rsd: + name: Serbian Dinar + priority: 100 + iso_code: RSD + iso_numeric: "941" + html_code: "" + symbol: РСД + minor_unit: Para + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +rub: + name: Russian Ruble + priority: 100 + iso_code: RUB + iso_numeric: "643" + html_code: "₽" + symbol: "₽" + minor_unit: Kopeck + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 2 +rwf: + name: Rwandan Franc + priority: 100 + iso_code: RWF + iso_numeric: "646" + html_code: "" + symbol: FRw + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xbd: + name: European Unit of Account 17 + priority: 100 + iso_code: XBD + iso_numeric: "958" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xcd: + name: East Caribbean Dollar + priority: 100 + iso_code: XCD + iso_numeric: "951" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +xdr: + name: Special Drawing Rights + priority: 100 + iso_code: XDR + iso_numeric: "960" + html_code: "$" + symbol: SDR + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xof: + name: West African Cfa Franc + priority: 100 + iso_code: XOF + iso_numeric: "952" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xpd: + name: Palladium + priority: 100 + iso_code: XPD + iso_numeric: "964" + html_code: "" + symbol: oz t + minor_unit: oz + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xpf: + name: Cfp Franc + priority: 100 + iso_code: XPF + iso_numeric: "953" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xpt: + name: Platinum + priority: 100 + iso_code: XPT + iso_numeric: "962" + html_code: "" + symbol: oz t + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: "" + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xts: + name: Codes specifically reserved for testing purposes + priority: 100 + iso_code: XTS + iso_numeric: "963" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: "" + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +yer: + name: Yemeni Rial + priority: 100 + iso_code: YER + iso_numeric: "886" + html_code: "﷼" + symbol: "﷼" + minor_unit: Fils + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +zar: + name: South African Rand + priority: 100 + iso_code: ZAR + iso_numeric: "710" + html_code: "R" + symbol: R + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "," + delimiter: " " + default_format: "%u%n" + default_precision: 2 +zmk: + name: Zambian Kwacha + priority: 100 + iso_code: ZMK + iso_numeric: "894" + html_code: "" + symbol: ZK + minor_unit: Ngwee + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +zmw: + name: Zambian Kwacha + priority: 100 + iso_code: ZMW + iso_numeric: "967" + html_code: "" + symbol: K + minor_unit: Ngwee + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bch: + name: Bitcoin Cash + priority: 100 + iso_code: BCH + iso_numeric: "" + html_code: "₿" + symbol: "₿" + minor_unit: Satoshi + minor_unit_conversion: 100000000 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 8 +btc: + name: Bitcoin + priority: 100 + iso_code: BTC + iso_numeric: "" + html_code: "₿" + symbol: "₿" + minor_unit: Satoshi + minor_unit_conversion: 100000000 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 8 +jep: + name: Jersey Pound + priority: 100 + iso_code: JEP + iso_numeric: "" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +ggp: + name: Guernsey Pound + priority: 100 + iso_code: GGP + iso_numeric: "" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +imp: + name: Isle of Man Pound + priority: 100 + iso_code: IMP + iso_numeric: "" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +xfu: + name: UIC Franc + priority: 100 + iso_code: XFU + iso_numeric: "" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 100 + smallest_denomination: "" + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +gbx: + name: British Penny + priority: 100 + iso_code: GBX + iso_numeric: "" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 0 +cnh: + name: Chinese Renminbi Yuan Offshore + priority: 100 + iso_code: CNH + iso_numeric: "" + html_code: "¥" + symbol: "¥" + minor_unit: Fen + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +usdc: + name: USD Coin + priority: 100 + iso_code: USDC + iso_numeric: "" + html_code: "$" + symbol: USDC + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +thb: + name: Thai Baht + priority: 100 + iso_code: THB + iso_numeric: "764" + html_code: "฿" + symbol: "฿" + minor_unit: Satang + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +tjs: + name: Tajikistani Somoni + priority: 100 + iso_code: TJS + iso_numeric: "972" + html_code: "" + symbol: ЅМ + minor_unit: Diram + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +tmt: + name: Turkmenistani Manat + priority: 100 + iso_code: TMT + iso_numeric: "934" + html_code: "" + symbol: T + minor_unit: Tenge + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +tnd: + name: Tunisian Dinar + priority: 100 + iso_code: TND + iso_numeric: "788" + html_code: "" + symbol: د.ت + minor_unit: Millime + minor_unit_conversion: 1000 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 3 +top: + name: Tongan Paʻanga + priority: 100 + iso_code: TOP + iso_numeric: "776" + html_code: "" + symbol: T$ + minor_unit: Seniti + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +try: + name: Turkish Lira + priority: 100 + iso_code: TRY + iso_numeric: "949" + html_code: "₺" + symbol: "₺" + minor_unit: kuruş + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +ttd: + name: Trinidad and Tobago Dollar + priority: 100 + iso_code: TTD + iso_numeric: "780" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +twd: + name: New Taiwan Dollar + priority: 100 + iso_code: TWD + iso_numeric: "901" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +tzs: + name: Tanzanian Shilling + priority: 100 + iso_code: TZS + iso_numeric: "834" + html_code: "" + symbol: Sh + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5000 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +uah: + name: Ukrainian Hryvnia + priority: 100 + iso_code: UAH + iso_numeric: "980" + html_code: "₴" + symbol: "₴" + minor_unit: Kopiyka + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ugx: + name: Ugandan Shilling + priority: 100 + iso_code: UGX + iso_numeric: "800" + html_code: "" + symbol: USh + minor_unit: Cent + minor_unit_conversion: 1 + smallest_denomination: 1000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +uyu: + name: Uruguayan Peso + priority: 100 + iso_code: UYU + iso_numeric: "858" + html_code: "$U" + symbol: "$U" + minor_unit: Centésimo + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +uzs: + name: Uzbekistan Som + priority: 100 + iso_code: UZS + iso_numeric: "860" + html_code: "" + symbol: so'm + minor_unit: Tiyin + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ves: + name: Venezuelan Bolívar Soberano + priority: 100 + iso_code: VES + iso_numeric: "928" + html_code: "" + symbol: Bs + minor_unit: Céntimo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +vnd: + name: Vietnamese Đồng + priority: 100 + iso_code: VND + iso_numeric: "704" + html_code: "₫" + symbol: "₫" + minor_unit: Hào + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 0 +vuv: + name: Vanuatu Vatu + priority: 100 + iso_code: VUV + iso_numeric: "548" + html_code: "" + symbol: Vt + minor_unit: + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 0 +wst: + name: Samoan Tala + priority: 100 + iso_code: WST + iso_numeric: "882" + html_code: "" + symbol: T + minor_unit: Sene + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +xaf: + name: Central African Cfa Franc + priority: 100 + iso_code: XAF + iso_numeric: "950" + html_code: "" + symbol: CFA + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xag: + name: Silver (Troy Ounce) + priority: 100 + iso_code: XAG + iso_numeric: "961" + html_code: "" + symbol: oz t + minor_unit: oz + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xau: + name: Gold (Troy Ounce) + priority: 100 + iso_code: XAU + iso_numeric: "959" + html_code: "" + symbol: oz t + minor_unit: oz + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xba: + name: European Composite Unit + priority: 100 + iso_code: XBA + iso_numeric: "955" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +xbb: + name: European Monetary Unit + priority: 100 + iso_code: XBB + iso_numeric: "956" + html_code: "" + symbol: "" + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +byn: + name: Belarusian Ruble + priority: 100 + iso_code: BYN + iso_numeric: "933" + html_code: "" + symbol: Br + minor_unit: Kapeyka + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 2 +bzd: + name: Belize Dollar + priority: 100 + iso_code: BZD + iso_numeric: "084" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +cdf: + name: Congolese Franc + priority: 100 + iso_code: CDF + iso_numeric: "976" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +chf: + name: Swiss Franc + priority: 100 + iso_code: CHF + iso_numeric: "756" + html_code: "" + symbol: CHF + minor_unit: Rappen + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u %n" + default_precision: 2 +clf: + name: Unidad de Fomento + priority: 100 + iso_code: CLF + iso_numeric: "990" + html_code: "₱" + symbol: UF + minor_unit: Peso + minor_unit_conversion: 10000 + smallest_denomination: + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 4 +clp: + name: Chilean Peso + priority: 100 + iso_code: CLP + iso_numeric: "152" + html_code: "$" + symbol: "$" + minor_unit: Peso + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 0 +cny: + name: Chinese Renminbi Yuan + priority: 100 + iso_code: CNY + iso_numeric: "156" + html_code: "¥" + symbol: "¥" + minor_unit: Fen + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +cop: + name: Colombian Peso + priority: 100 + iso_code: COP + iso_numeric: "170" + html_code: "$" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 20 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +crc: + name: Costa Rican Colón + priority: 100 + iso_code: CRC + iso_numeric: "188" + html_code: "₡" + symbol: "₡" + minor_unit: Céntimo + minor_unit_conversion: 100 + smallest_denomination: 500 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +cuc: + name: Cuban Convertible Peso + priority: 100 + iso_code: CUC + iso_numeric: "931" + html_code: "" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +cup: + name: Cuban Peso + priority: 100 + iso_code: CUP + iso_numeric: "192" + html_code: "₱" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +cve: + name: Cape Verdean Escudo + priority: 100 + iso_code: CVE + iso_numeric: "132" + html_code: "" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +czk: + name: Czech Koruna + priority: 100 + iso_code: CZK + iso_numeric: "203" + html_code: "" + symbol: Kč + minor_unit: Haléř + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 2 +djf: + name: Djiboutian Franc + priority: 100 + iso_code: DJF + iso_numeric: "262" + html_code: "" + symbol: Fdj + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +dkk: + name: Danish Krone + priority: 100 + iso_code: DKK + iso_numeric: "208" + html_code: "" + symbol: kr. + minor_unit: Øre + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 2 +dop: + name: Dominican Peso + priority: 100 + iso_code: DOP + iso_numeric: "214" + html_code: "₱" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +dzd: + name: Algerian Dinar + priority: 100 + iso_code: DZD + iso_numeric: "012" + html_code: "" + symbol: د.ج + minor_unit: Centime + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +egp: + name: Egyptian Pound + priority: 100 + iso_code: EGP + iso_numeric: "818" + html_code: "£" + symbol: ج.م + minor_unit: Piastre + minor_unit_conversion: 100 + smallest_denomination: 25 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +ern: + name: Eritrean Nakfa + priority: 100 + iso_code: ERN + iso_numeric: "232" + html_code: "" + symbol: Nfk + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +etb: + name: Ethiopian Birr + priority: 100 + iso_code: ETB + iso_numeric: "230" + html_code: "" + symbol: Br + minor_unit: Santim + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +fjd: + name: Fijian Dollar + priority: 100 + iso_code: FJD + iso_numeric: "242" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +khr: + name: Cambodian Riel + priority: 100 + iso_code: KHR + iso_numeric: "116" + html_code: "៛" + symbol: "៛" + minor_unit: Sen + minor_unit_conversion: 100 + smallest_denomination: 5000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +aed: + name: United Arab Emirates Dirham + priority: 100 + iso_code: AED + iso_numeric: "784" + html_code: "" + symbol: د.إ + minor_unit: Fils + minor_unit_conversion: 100 + smallest_denomination: 25 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +afn: + name: Afghan Afghani + priority: 100 + iso_code: AFN + iso_numeric: "971" + html_code: "" + symbol: "؋" + minor_unit: Pul + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +all: + name: Albanian Lek + priority: 100 + iso_code: ALL + iso_numeric: "008" + html_code: "" + symbol: L + minor_unit: Qintar + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +amd: + name: Armenian Dram + priority: 100 + iso_code: AMD + iso_numeric: "051" + html_code: "" + symbol: դր. + minor_unit: Luma + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ang: + name: Netherlands Antillean Gulden + priority: 100 + iso_code: ANG + iso_numeric: "532" + html_code: "ƒ" + symbol: ƒ + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +aoa: + name: Angolan Kwanza + priority: 100 + iso_code: AOA + iso_numeric: "973" + html_code: "" + symbol: Kz + minor_unit: Cêntimo + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ars: + name: Argentine Peso + priority: 100 + iso_code: ARS + iso_numeric: "032" + html_code: "$" + symbol: "$" + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +awg: + name: Aruban Florin + priority: 100 + iso_code: AWG + iso_numeric: "533" + html_code: "ƒ" + symbol: ƒ + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +azn: + name: Azerbaijani Manat + priority: 100 + iso_code: AZN + iso_numeric: "944" + html_code: "" + symbol: "₼" + minor_unit: Qəpik + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bam: + name: Bosnia and Herzegovina Convertible Mark + priority: 100 + iso_code: BAM + iso_numeric: "977" + html_code: "" + symbol: КМ + minor_unit: Fening + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bbd: + name: Barbadian Dollar + priority: 100 + iso_code: BBD + iso_numeric: "052" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bdt: + name: Bangladeshi Taka + priority: 100 + iso_code: BDT + iso_numeric: "050" + html_code: "" + symbol: "৳" + minor_unit: Paisa + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bgn: + name: Bulgarian Lev + priority: 100 + iso_code: BGN + iso_numeric: "975" + html_code: "" + symbol: лв. + minor_unit: Stotinka + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +bhd: + name: Bahraini Dinar + priority: 100 + iso_code: BHD + iso_numeric: "048" + html_code: "" + symbol: د.ب + minor_unit: Fils + minor_unit_conversion: 1000 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 3 +bif: + name: Burundian Franc + priority: 100 + iso_code: BIF + iso_numeric: "108" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +bmd: + name: Bermudian Dollar + priority: 100 + iso_code: BMD + iso_numeric: "060" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bnd: + name: Brunei Dollar + priority: 100 + iso_code: BND + iso_numeric: "096" + html_code: "$" + symbol: "$" + minor_unit: Sen + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +bob: + name: Bolivian Boliviano + priority: 100 + iso_code: BOB + iso_numeric: "068" + html_code: "" + symbol: Bs. + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +brl: + name: Brazilian Real + priority: 100 + iso_code: BRL + iso_numeric: "986" + html_code: R$ + symbol: R$ + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +bsd: + name: Bahamian Dollar + priority: 100 + iso_code: BSD + iso_numeric: "044" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +btn: + name: Bhutanese Ngultrum + priority: 100 + iso_code: BTN + iso_numeric: "064" + html_code: "" + symbol: Nu. + minor_unit: Chertrum + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +bwp: + name: Botswana Pula + priority: 100 + iso_code: BWP + iso_numeric: "072" + html_code: "" + symbol: P + minor_unit: Thebe + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +kmf: + name: Comorian Franc + priority: 100 + iso_code: KMF + iso_numeric: "174" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +kpw: + name: North Korean Won + priority: 100 + iso_code: KPW + iso_numeric: "408" + html_code: "₩" + symbol: "₩" + minor_unit: Chŏn + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +krw: + name: South Korean Won + priority: 100 + iso_code: KRW + iso_numeric: "410" + html_code: "₩" + symbol: "₩" + minor_unit: + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 0 +kwd: + name: Kuwaiti Dinar + priority: 100 + iso_code: KWD + iso_numeric: "414" + html_code: "" + symbol: د.ك + minor_unit: Fils + minor_unit_conversion: 1000 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 3 +kyd: + name: Cayman Islands Dollar + priority: 100 + iso_code: KYD + iso_numeric: "136" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +kzt: + name: Kazakhstani Tenge + priority: 100 + iso_code: KZT + iso_numeric: "398" + html_code: "" + symbol: "₸" + minor_unit: Tiyn + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +lak: + name: Lao Kip + priority: 100 + iso_code: LAK + iso_numeric: "418" + html_code: "₭" + symbol: "₭" + minor_unit: Att + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +lbp: + name: Lebanese Pound + priority: 100 + iso_code: LBP + iso_numeric: "422" + html_code: "£" + symbol: ل.ل + minor_unit: Piastre + minor_unit_conversion: 100 + smallest_denomination: 25000 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +lkr: + name: Sri Lankan Rupee + priority: 100 + iso_code: LKR + iso_numeric: "144" + html_code: "₨" + symbol: "₨" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +lrd: + name: Liberian Dollar + priority: 100 + iso_code: LRD + iso_numeric: "430" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +lsl: + name: Lesotho Loti + priority: 100 + iso_code: LSL + iso_numeric: "426" + html_code: "" + symbol: L + minor_unit: Sente + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +lyd: + name: Libyan Dinar + priority: 100 + iso_code: LYD + iso_numeric: "434" + html_code: "" + symbol: ل.د + minor_unit: Dirham + minor_unit_conversion: 1000 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 3 +mad: + name: Moroccan Dirham + priority: 100 + iso_code: MAD + iso_numeric: "504" + html_code: "" + symbol: د.م. + minor_unit: Centime + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mdl: + name: Moldovan Leu + priority: 100 + iso_code: MDL + iso_numeric: "498" + html_code: "" + symbol: L + minor_unit: Ban + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mga: + name: Malagasy Ariary + priority: 100 + iso_code: MGA + iso_numeric: "969" + html_code: "" + symbol: Ar + minor_unit: Iraimbilanja + minor_unit_conversion: 5 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 1 +mkd: + name: Macedonian Denar + priority: 100 + iso_code: MKD + iso_numeric: "807" + html_code: "" + symbol: ден + minor_unit: Deni + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mmk: + name: Myanmar Kyat + priority: 100 + iso_code: MMK + iso_numeric: "104" + html_code: "" + symbol: K + minor_unit: Pya + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mnt: + name: Mongolian Tögrög + priority: 100 + iso_code: MNT + iso_numeric: "496" + html_code: "₮" + symbol: "₮" + minor_unit: Möngö + minor_unit_conversion: 100 + smallest_denomination: 2000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mop: + name: Macanese Pataca + priority: 100 + iso_code: MOP + iso_numeric: "446" + html_code: "" + symbol: P + minor_unit: Avo + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +mru: + name: Mauritanian Ouguiya + priority: 100 + iso_code: MRU + iso_numeric: "929" + html_code: "" + symbol: UM + minor_unit: Khoums + minor_unit_conversion: 5 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 1 +mur: + name: Mauritian Rupee + priority: 100 + iso_code: MUR + iso_numeric: "480" + html_code: "₨" + symbol: "₨" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +fkp: + name: Falkland Pound + priority: 100 + iso_code: FKP + iso_numeric: "238" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +gel: + name: Georgian Lari + priority: 100 + iso_code: GEL + iso_numeric: "981" + html_code: "" + symbol: "₾" + minor_unit: Tetri + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +ghs: + name: Ghanaian Cedi + priority: 100 + iso_code: GHS + iso_numeric: "936" + html_code: "₵" + symbol: "₵" + minor_unit: Pesewa + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +gip: + name: Gibraltar Pound + priority: 100 + iso_code: GIP + iso_numeric: "292" + html_code: "£" + symbol: "£" + minor_unit: Penny + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +gmd: + name: Gambian Dalasi + priority: 100 + iso_code: GMD + iso_numeric: "270" + html_code: "" + symbol: D + minor_unit: Butut + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +gnf: + name: Guinean Franc + priority: 100 + iso_code: GNF + iso_numeric: "324" + html_code: "" + symbol: Fr + minor_unit: Centime + minor_unit_conversion: 1 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 0 +gtq: + name: Guatemalan Quetzal + priority: 100 + iso_code: GTQ + iso_numeric: "320" + html_code: "" + symbol: Q + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +gyd: + name: Guyanese Dollar + priority: 100 + iso_code: GYD + iso_numeric: "328" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 100 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +hkd: + name: Hong Kong Dollar + priority: 100 + iso_code: HKD + iso_numeric: "344" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +hnl: + name: Honduran Lempira + priority: 100 + iso_code: HNL + iso_numeric: "340" + html_code: "" + symbol: L + minor_unit: Centavo + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +htg: + name: Haitian Gourde + priority: 100 + iso_code: HTG + iso_numeric: "332" + html_code: "" + symbol: G + minor_unit: Centime + minor_unit_conversion: 100 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 +huf: + name: Hungarian Forint + priority: 100 + iso_code: HUF + iso_numeric: "348" + html_code: "" + symbol: Ft + minor_unit: "" + minor_unit_conversion: 1 + smallest_denomination: 5 + separator: "," + delimiter: " " + default_format: "%n %u" + default_precision: 0 +idr: + name: Indonesian Rupiah + priority: 100 + iso_code: IDR + iso_numeric: "360" + html_code: "" + symbol: Rp + minor_unit: Sen + minor_unit_conversion: 100 + smallest_denomination: 5000 + separator: "," + delimiter: "." + default_format: "%u%n" + default_precision: 2 +ils: + name: Israeli New Sheqel + priority: 100 + iso_code: ILS + iso_numeric: "376" + html_code: "₪" + symbol: "₪" + minor_unit: Agora + minor_unit_conversion: 100 + smallest_denomination: 10 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +inr: + name: Indian Rupee + priority: 100 + iso_code: INR + iso_numeric: "356" + html_code: "₹" + symbol: "₹" + minor_unit: Paisa + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +iqd: + name: Iraqi Dinar + priority: 100 + iso_code: IQD + iso_numeric: "368" + html_code: "" + symbol: ع.د + minor_unit: Fils + minor_unit_conversion: 1000 + smallest_denomination: 50000 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 3 +irr: + name: Iranian Rial + priority: 100 + iso_code: IRR + iso_numeric: "364" + html_code: "﷼" + symbol: "﷼" + minor_unit: + minor_unit_conversion: 100 + smallest_denomination: 5000 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +isk: + name: Icelandic Króna + priority: 100 + iso_code: ISK + iso_numeric: "352" + html_code: "" + symbol: kr. + minor_unit: + minor_unit_conversion: 1 + smallest_denomination: 1 + separator: "," + delimiter: "." + default_format: "%n %u" + default_precision: 0 +jmd: + name: Jamaican Dollar + priority: 100 + iso_code: JMD + iso_numeric: "388" + html_code: "$" + symbol: "$" + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +jod: + name: Jordanian Dinar + priority: 100 + iso_code: JOD + iso_numeric: "400" + html_code: "" + symbol: د.ا + minor_unit: Fils + minor_unit_conversion: 1000 + smallest_denomination: 5 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 3 +kes: + name: Kenyan Shilling + priority: 100 + iso_code: KES + iso_numeric: "404" + html_code: "" + symbol: KSh + minor_unit: Cent + minor_unit_conversion: 100 + smallest_denomination: 50 + separator: "." + delimiter: "," + default_format: "%u%n" + default_precision: 2 +kgs: + name: Kyrgyzstani Som + priority: 100 + iso_code: KGS + iso_numeric: "417" + html_code: "" + symbol: som + minor_unit: Tyiyn + minor_unit_conversion: 100 + smallest_denomination: 1 + separator: "." + delimiter: "," + default_format: "%n %u" + default_precision: 2 diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb index fa6cc653..f6a58af6 100644 --- a/config/initializers/constants.rb +++ b/config/initializers/constants.rb @@ -1,18 +1 @@ -default_currency_options = { symbol: "$", precision: 2, delimiter: ",", separator: "." } - -CURRENCY_OPTIONS = Hash.new { |hash, key| hash[key] = default_currency_options.dup }.merge( - "USD": { symbol: "$", precision: 2, delimiter: ",", separator: "." }, - "EUR": { symbol: "€", precision: 2, delimiter: ".", separator: "," }, - "GBP": { symbol: "£", precision: 2, delimiter: ",", separator: "." }, - "CAD": { symbol: "C$", precision: 2, delimiter: ",", separator: "." }, - "MXN": { symbol: "MX$", precision: 2, delimiter: ",", separator: "." }, - "HKD": { symbol: "HK$", precision: 2, delimiter: ",", separator: "." }, - "CHF": { symbol: "CHF", precision: 2, delimiter: ".", separator: "," }, - "SGD": { symbol: "S$", precision: 2, delimiter: ",", separator: "." }, - "NZD": { symbol: "NZ$", precision: 2, delimiter: ",", separator: "." }, - "AUD": { symbol: "A$", precision: 2, delimiter: ",", separator: "." }, - "KRW": { symbol: "₩", precision: 0, delimiter: ",", separator: "." }, - "INR": { symbol: "₹", precision: 2, delimiter: ",", separator: "." } -) - EXCHANGE_RATE_ENABLED = ENV["OPEN_EXCHANGE_APP_ID"].present? diff --git a/db/migrate/20240206031739_replace_money_field.rb b/db/migrate/20240206031739_replace_money_field.rb index c89c2cb9..1ededb35 100644 --- a/db/migrate/20240206031739_replace_money_field.rb +++ b/db/migrate/20240206031739_replace_money_field.rb @@ -2,13 +2,6 @@ class ReplaceMoneyField < ActiveRecord::Migration[7.2] def change add_column :accounts, :balance_cents, :integer change_column :accounts, :balance_cents, :integer, limit: 8 - - Account.reset_column_information - - Account.find_each do |account| - account.update_columns(balance_cents: Money.from_amount(account.balance_in_database, account.currency).cents) - end - remove_column :accounts, :balance end end diff --git a/db/schema.rb b/db/schema.rb index aed174db..4595cc8a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -82,7 +82,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_03_09_180636) do t.string "currency", default: "USD" t.decimal "converted_balance", precision: 19, scale: 4, default: "0.0" t.string "converted_currency", default: "USD" - t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Account::Loan'::character varying)::text, ('Account::Credit'::character varying)::text, ('Account::OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true + t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY ((ARRAY['Account::Loan'::character varying, 'Account::Credit'::character varying, 'Account::OtherLiability'::character varying])::text[])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true t.boolean "is_active", default: true, null: false t.enum "status", default: "ok", null: false, enum_type: "account_status" t.jsonb "sync_warnings", default: "[]", null: false diff --git a/lib/money.rb b/lib/money.rb new file mode 100644 index 00000000..2c9581cf --- /dev/null +++ b/lib/money.rb @@ -0,0 +1,61 @@ +class Money + include Comparable + include Arithmetic + + attr_reader :amount, :currency + + class << self + def default_currency + @default ||= Money::Currency.new(:usd) + end + + def default_currency=(object) + @default = Money::Currency.new(object) + end + end + + def initialize(obj, currency = Money.default_currency) + unless obj.is_a?(Money) || obj.is_a?(Numeric) || obj.is_a?(BigDecimal) + raise ArgumentError, "obj must be an instance of Money, Numeric, or BigDecimal" + end + + @amount = obj.is_a?(Money) ? obj.amount : BigDecimal(obj.to_s) + @currency = obj.is_a?(Money) ? obj.currency : Money::Currency.new(currency) + end + + def cents_str(precision = @currency.default_precision) + format_str = "%.#{precision}f" + amount_str = format_str % @amount + parts = amount_str.split(@currency.separator) + + return "" if parts.length < 2 + + parts.last.ljust(precision, "0") + end + + # Basic formatting only. Use the Rails number_to_currency helper for more advanced formatting. + alias to_s format + def format + whole_part, fractional_part = sprintf("%.#{@currency.default_precision}f", @amount).split(".") + whole_with_delimiters = whole_part.chars.to_a.reverse.each_slice(3).map(&:join).join(@currency.delimiter).reverse + formatted_amount = "#{whole_with_delimiters}#{@currency.separator}#{fractional_part}" + @currency.default_format.gsub("%n", formatted_amount).gsub("%u", @currency.symbol) + end + + def <=>(other) + raise TypeError, "Money can only be compared with other Money objects except for 0" unless other.is_a?(Money) || other.eql?(0) + return @amount <=> other if other.is_a?(Numeric) + amount_comparison = @amount <=> other.amount + return amount_comparison unless amount_comparison == 0 + @currency <=> other.currency + end + + def default_format_options + { + unit: @currency.symbol, + precision: @currency.default_precision, + delimiter: @currency.delimiter, + separator: @currency.separator + } + end +end diff --git a/lib/money/arithmetic.rb b/lib/money/arithmetic.rb new file mode 100644 index 00000000..dc48abfd --- /dev/null +++ b/lib/money/arithmetic.rb @@ -0,0 +1,62 @@ +module Money::Arithmetic + CoercedNumeric = Struct.new(:value) + + def +(other) + if other.is_a?(Money) + self.class.new(amount + other.amount, currency) + else + value = other.is_a?(CoercedNumeric) ? other.value : other + self.class.new(amount + value, currency) + end + end + + def -(other) + if other.is_a?(Money) + self.class.new(amount - other.amount, currency) + else + value = other.is_a?(CoercedNumeric) ? other.value : other + self.class.new(amount - value, currency) + end + end + + def -@ + self.class.new(-amount, currency) + end + + def *(other) + raise TypeError, "Can't multiply Money by Money, use Numeric instead" if other.is_a?(self.class) + value = other.is_a?(CoercedNumeric) ? other.value : other + self.class.new(amount * value, currency) + end + + def /(other) + if other.is_a?(self.class) + amount / other.amount + else + raise TypeError, "can't divide Numeric by Money" if other.is_a?(CoercedNumeric) + self.class.new(amount / other, currency) + end + end + + def abs + self.class.new(amount.abs, currency) + end + + def zero? + amount.zero? + end + + def negative? + amount.negative? + end + + def positive? + amount.positive? + end + + # Override Ruby's coerce method so the order of operands doesn't matter + # Wrap in Coerced so we can distinguish between Money and other types + def coerce(other) + [ self, CoercedNumeric.new(other) ] + end +end diff --git a/lib/money/currency.rb b/lib/money/currency.rb new file mode 100644 index 00000000..71411899 --- /dev/null +++ b/lib/money/currency.rb @@ -0,0 +1,61 @@ +class Money::Currency + include Comparable + + class UnknownCurrencyError < ArgumentError; end + + CURRENCIES_FILE_PATH = Rails.root.join("config", "currencies.yml") + + # Cached instances by iso code + @@instances = {} + + class << self + def new(object) + iso_code = case object + when String, Symbol + object.to_s.downcase + when Money::Currency + object.iso_code.downcase + else + raise ArgumentError, "Invalid argument type" + end + + @@instances[iso_code] ||= super(iso_code) + end + + def all + @all ||= YAML.load_file(CURRENCIES_FILE_PATH) + end + + def popular + all.values.sort_by { |currency| currency["priority"] }.first(12).map { |currency_data| new(currency_data["iso_code"]) } + end + end + + attr_reader :name, :priority, :iso_code, :iso_numeric, :html_code, + :symbol, :minor_unit, :minor_unit_conversion, :smallest_denomination, + :separator, :delimiter, :default_format, :default_precision + + def initialize(iso_code) + currency_data = self.class.all[iso_code] + raise UnknownCurrencyError if currency_data.nil? + + @name = currency_data["name"] + @priority = currency_data["priority"] + @iso_code = currency_data["iso_code"] + @iso_numeric = currency_data["iso_numeric"] + @html_code = currency_data["html_code"] + @symbol = currency_data["symbol"] + @minor_unit = currency_data["minor_unit"] + @minor_unit_conversion = currency_data["minor_unit_conversion"] + @smallest_denomination = currency_data["smallest_denomination"] + @separator = currency_data["separator"] + @delimiter = currency_data["delimiter"] + @default_format = currency_data["default_format"] + @default_precision = currency_data["default_precision"] + end + + def <=>(other) + return nil unless other.is_a?(Money::Currency) + @iso_code <=> other.iso_code + end +end diff --git a/test/controllers/transactions_controller_test.rb b/test/controllers/transactions_controller_test.rb index 428f8e01..168523e5 100644 --- a/test/controllers/transactions_controller_test.rb +++ b/test/controllers/transactions_controller_test.rb @@ -22,7 +22,7 @@ class TransactionsControllerTest < ActionDispatch::IntegrationTest post transactions_url, params: { transaction: { account_id: @transaction.account_id, amount: @transaction.amount, currency: @transaction.currency, date: @transaction.date, name: } } end - assert_redirected_to transaction_url(Transaction.find_by(name:)) + assert_redirected_to transactions_url end test "should show transaction" do diff --git a/test/lib/money/currency_test.rb b/test/lib/money/currency_test.rb new file mode 100644 index 00000000..e1b35f99 --- /dev/null +++ b/test/lib/money/currency_test.rb @@ -0,0 +1,49 @@ +require "test_helper" + +class Money::CurrencyTest < ActiveSupport::TestCase + setup do + @currency = Money::Currency.new(:usd) + end + + test "has many currencies" do + assert_operator Money::Currency.all.count, :>, 100 + end + + test "can test equality of currencies" do + assert_equal Money::Currency.new(:usd), Money::Currency.new(:usd) + assert_not_equal Money::Currency.new(:usd), Money::Currency.new(:eur) + end + + test "can get metadata about a currency" do + assert_equal "USD", @currency.iso_code + assert_equal "United States Dollar", @currency.name + assert_equal "$", @currency.symbol + assert_equal 1, @currency.priority + assert_equal "Cent", @currency.minor_unit + assert_equal 100, @currency.minor_unit_conversion + assert_equal 1, @currency.smallest_denomination + assert_equal ".", @currency.separator + assert_equal ",", @currency.delimiter + assert_equal "%u%n", @currency.default_format + assert_equal 2, @currency.default_precision + end + + test "can extract cents string from amount" do + value1 = Money.new(100) + value2 = Money.new(100.1) + value3 = Money.new(100.12) + value4 = Money.new(100.123) + value5 = Money.new(200, :jpy) + + assert_equal "00", value1.cents_str + assert_equal "10", value2.cents_str + assert_equal "12", value3.cents_str + assert_equal "12", value4.cents_str + assert_equal "", value5.cents_str + + assert_equal "", value4.cents_str(0) + assert_equal "1", value4.cents_str(1) + assert_equal "12", value4.cents_str(2) + assert_equal "123", value4.cents_str(3) + end +end diff --git a/test/lib/money_test.rb b/test/lib/money_test.rb new file mode 100644 index 00000000..6de805db --- /dev/null +++ b/test/lib/money_test.rb @@ -0,0 +1,90 @@ +require "test_helper" + +class MoneyTest < ActiveSupport::TestCase + test "can create with default currency" do + value = Money.new(1000) + assert_equal 1000, value.amount + end + + test "can create with custom currency" do + value1 = Money.new(1000, :EUR) + value2 = Money.new(1000, :eur) + value3 = Money.new(1000, "eur") + value4 = Money.new(1000, "EUR") + + assert_equal value1.currency.iso_code, value2.currency.iso_code + assert_equal value2.currency.iso_code, value3.currency.iso_code + assert_equal value3.currency.iso_code, value4.currency.iso_code + end + + test "equality tests amount and currency" do + assert_equal Money.new(1000), Money.new(1000) + assert_not_equal Money.new(1000), Money.new(1001) + assert_not_equal Money.new(1000, :usd), Money.new(1000, :eur) + end + + test "can compare with zero Numeric" do + assert_equal Money.new(0), 0 + assert_raises(TypeError) { Money.new(1) == 1 } + end + + test "can negate" do + assert_equal (-Money.new(1000)), Money.new(-1000) + end + + test "can use comparison operators" do + assert_operator Money.new(1000), :>, Money.new(999) + assert_operator Money.new(1000), :>=, Money.new(1000) + assert_operator Money.new(1000), :<, Money.new(1001) + assert_operator Money.new(1000), :<=, Money.new(1000) + end + + test "can add and subtract" do + assert_equal Money.new(1000) + Money.new(1000), Money.new(2000) + assert_equal Money.new(1000) + 1000, Money.new(2000) + assert_equal Money.new(1000) - Money.new(1000), Money.new(0) + assert_equal Money.new(1000) - 1000, Money.new(0) + end + + test "can multiply" do + assert_equal Money.new(1000) * 2, Money.new(2000) + assert_raises(TypeError) { Money.new(1000) * Money.new(2) } + end + + test "can divide" do + assert_equal Money.new(1000) / 2, Money.new(500) + assert_equal Money.new(1000) / Money.new(500), 2 + assert_raise(TypeError) { 1000 / Money.new(2) } + end + + test "operator order does not matter" do + assert_equal Money.new(1000) + 1000, 1000 + Money.new(1000) + assert_equal Money.new(1000) - 1000, 1000 - Money.new(1000) + assert_equal Money.new(1000) * 2, 2 * Money.new(1000) + end + + test "can get absolute value" do + assert_equal Money.new(1000).abs, Money.new(1000) + assert_equal Money.new(-1000).abs, Money.new(1000) + end + + test "can test if zero" do + assert Money.new(0).zero? + assert_not Money.new(1000).zero? + end + + test "can test if negative" do + assert Money.new(-1000).negative? + assert_not Money.new(1000).negative? + end + + test "can test if positive" do + assert Money.new(1000).positive? + assert_not Money.new(-1000).positive? + end + + test "can cast to string with basic formatting" do + assert_equal "$1,000.90", Money.new(1000.899).format + assert_equal "€1.000,12", Money.new(1000.12, :eur).format + end +end diff --git a/test/models/family_test.rb b/test/models/family_test.rb index 88ac1086..be7351b8 100644 --- a/test/models/family_test.rb +++ b/test/models/family_test.rb @@ -38,15 +38,15 @@ class FamilyTest < ActiveSupport::TestCase end test "should calculate total assets" do - assert_equal BigDecimal("25550"), @family.assets + assert_equal Money.new(25550), @family.assets_money end test "should calculate total liabilities" do - assert_equal BigDecimal("1000"), @family.liabilities + assert_equal Money.new(1000), @family.liabilities_money end test "should calculate net worth" do - assert_equal BigDecimal("24550"), @family.net_worth + assert_equal Money.new(24550), @family.net_worth_money end test "should calculate snapshot correctly" do diff --git a/test/models/money_test.rb b/test/models/money_test.rb deleted file mode 100644 index f3cc4855..00000000 --- a/test/models/money_test.rb +++ /dev/null @@ -1,45 +0,0 @@ -require "test_helper" - -class MoneyTest < ActiveSupport::TestCase - test "#symbol returns the currency symbol for a given currency code" do - assert_equal "$", Money.from_amount(0, "USD").symbol - assert_equal "€", Money.from_amount(0, "EUR").symbol - end - - test "#separator returns the currency separator for a given currency code" do - assert_equal ".", Money.from_amount(0, "USD").separator - assert_equal ",", Money.from_amount(0, "EUR").separator - end - - test "#precision returns the currency's precision for a given currency code" do - assert_equal 2, Money.from_amount(0, "USD").precision - assert_equal 0, Money.from_amount(123.45, "KRW").precision - end - - test "#cents returns the cents part with 2 precisions by default" do - assert_equal "45", Money.from_amount(123.45, "USD").cents - end - - test "#cents returns empty when precision is 0" do - assert_equal "", Money.from_amount(123.45, "USD").cents(precision: 0) - end - - test "#cents returns the cents part of the string with given precision" do - amount = Money.from_amount(123.4862, "USD") - assert_equal "4", amount.cents(precision: 1) - assert_equal "486", amount.cents(precision: 3) - end - - test "#cents pads the cents part with zeros up to the specified precision" do - amount_without_decimal = Money.from_amount(123, "USD") - amount_with_decimal = Money.from_amount(123.4, "USD") - - assert_equal "00", amount_without_decimal.cents - assert_equal "40", amount_with_decimal.cents - end - - test "works with BigDecimal" do - amount = Money.from_amount(BigDecimal("123.45"), "USD") - assert_equal "45", amount.cents - end -end