From 4ad07e9741f9a3e1c02489c22c4cbdd2ceaa2c0f Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 9 Jul 2024 17:30:57 +0200 Subject: [PATCH] feat: added custom_display_options configuration (#225) * feat: added custom_display_options configuration * chore: updated code to receive a boolean in the custom_display_options symbol * added more tests * updated README file * Update README.md Co-authored-by: Arturo Medina Merino * added more tests --------- Co-authored-by: Gabriel Perales Co-authored-by: Arturo Medina Merino --- README.md | 12 ++++++++ lib/money/display_options.ex | 54 +++++++++++++++++++++++++----------- test/money_test.exs | 51 +++++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 20de471..7cb663e 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,18 @@ config :money, ] ``` +### Custom Currencies Display Options + +In some cases, we may need to display the currency in a different way than the default format. For example, we may want to display the currency symbol on the right side of the amount for some currencies. To achieve this, you can add the following configuration: + +```elixir +config :money, + custom_display_options: [ + EUR: %{symbol_on_right: true, symbol_space: true, separator: ".", delimiter: ",", symbol: true}, + JPY: %{symbol_on_right: true, separator: ".", delimiter: ",", symbol: true} + ] +``` + ## Troubleshooting ### Validating amount in Ecto changeset diff --git a/lib/money/display_options.ex b/lib/money/display_options.ex index 425c6c6..b476c8b 100644 --- a/lib/money/display_options.ex +++ b/lib/money/display_options.ex @@ -31,8 +31,29 @@ defmodule Money.DisplayOptions do @enforce_keys @all_fields defstruct @all_fields + defp custom_display_options do + Enum.into(Application.get_env(:money, :custom_display_options, []), %{}) + end + @spec get(Money.t(), Keyword.t()) :: t() def get(%Money{} = money, opts) do + custom_currency_display_options = get_currency_custom_display_options(money.currency) + + opts_with_symbol = + case Keyword.get(opts, :symbol) do + nil -> opts + true -> Keyword.put(opts, :symbol, Currency.symbol(money.currency)) + false -> Keyword.put(opts, :symbol, "") + end + + money + |> get_defaults(opts) + |> Map.merge(custom_currency_display_options) + |> Map.merge(Enum.into(opts_with_symbol, %{})) + end + + @spec get_defaults(Money.t(), Keyword.t()) :: t() + defp get_defaults(%Money{} = money, opts) do %{separator: separator, delimiter: delimiter} = ParseOptions.get(opts) default_symbol = Application.get_env(:money, :symbol, true) @@ -47,27 +68,28 @@ defmodule Money.DisplayOptions do Application.get_env(:money, :strip_insignificant_fractional_unit, false) symbol = if Keyword.get(opts, :symbol, default_symbol), do: Currency.symbol(money), else: "" - symbol_on_right = Keyword.get(opts, :symbol_on_right, default_symbol_on_right) - symbol_space = Keyword.get(opts, :symbol_space, default_symbol_space) - fractional_unit = Keyword.get(opts, :fractional_unit, default_fractional_unit) - strip_insignificant_zeros = Keyword.get(opts, :strip_insignificant_zeros, default_strip_insignificant_zeros) - code = Keyword.get(opts, :code, default_code) - minus_sign_first = Keyword.get(opts, :minus_sign_first, default_minus_sign_first) - - strip_insignificant_fractional_unit = - Keyword.get(opts, :strip_insignificant_fractional_unit, default_strip_insignificant_fractional_unit) %__MODULE__{ separator: separator, delimiter: delimiter, symbol: symbol, - symbol_on_right: symbol_on_right, - symbol_space: symbol_space, - fractional_unit: fractional_unit, - strip_insignificant_zeros: strip_insignificant_zeros, - code: code, - minus_sign_first: minus_sign_first, - strip_insignificant_fractional_unit: strip_insignificant_fractional_unit + symbol_on_right: default_symbol_on_right, + symbol_space: default_symbol_space, + fractional_unit: default_fractional_unit, + strip_insignificant_zeros: default_strip_insignificant_zeros, + code: default_code, + minus_sign_first: default_minus_sign_first, + strip_insignificant_fractional_unit: default_strip_insignificant_fractional_unit } end + + defp get_currency_custom_display_options(currency) do + display_options = Map.get(custom_display_options(), currency, %{}) + + case display_options[:symbol] do + nil -> display_options + true -> Map.put(display_options, :symbol, Currency.symbol(currency)) + false -> Map.put(display_options, :symbol, "") + end + end end diff --git a/test/money_test.exs b/test/money_test.exs index 5ca8f85..3f42e7f 100644 --- a/test/money_test.exs +++ b/test/money_test.exs @@ -3,7 +3,7 @@ defmodule MoneyTest do doctest Money require Money.Currency - import Money.Currency, only: [usd: 1, eur: 1, clf: 1, jpy: 1, omr: 1, xau: 1, zar: 1] + import Money.Currency, only: [usd: 1, eur: 1, clf: 1, jpy: 1, omr: 1, xau: 1, zar: 1, ars: 1, pen: 1, brl: 1] test "new/1 with default currency set" do try do @@ -339,6 +339,55 @@ defmodule MoneyTest do end end + test "to_string configuration with custom_display_options" do + try do + # Default configuration + Application.put_env(:money, :separator, ",") + Application.put_env(:money, :delimeter, ".") + Application.put_env(:money, :symbol, false) + Application.put_env(:money, :symbol_on_right, false) + Application.put_env(:money, :symbol_space, false) + Application.put_env(:money, :fractional_unit, true) + Application.put_env(:money, :strip_insignificant_zeros, false) + + # Custom configuration + Application.put_env(:money, :custom_display_options, + EUR: %{symbol: true, symbol_on_right: true, symbol_space: true, separator: ".", delimiter: ","}, + JPY: %{symbol: true, symbol_on_right: true, separator: ","}, + ARS: %{symbol: true, symbol_on_right: false, separator: ".", delimiter: ","}, + PEN: %{symbol: true, symbol_on_right: false, symbol_space: true, separator: ",", delimiter: "."}, + BRL: %{symbol: false, separator: ".", delimiter: ","} + ) + + # default configuration test + assert Money.to_string(usd(1_234_567_890)) == "12,345,678.90" + + # custom configuration test + assert Money.to_string(eur(1_234_567_890)) == "12.345.678,90 €" + assert Money.to_string(jpy(1_234_567_890)) == "1,234,567,890¥" + assert Money.to_string(ars(1_234_567_890)) == "$12.345.678,90" + assert Money.to_string(pen(1_234_567_890)) == "S/ 12,345,678.90" + assert Money.to_string(brl(1_234_567_8900)) == "123.456.789,00" + + # overwriting with options + assert Money.to_string(eur(1_234_567_890), symbol: false) == "12.345.678,90" + assert Money.to_string(jpy(1_234_567_890), symbol_space: true) == "1,234,567,890 ¥" + assert Money.to_string(ars(1_234_567_890), symbol_on_right: true) == "12.345.678,90$" + assert Money.to_string(pen(1_234_567_890), fractional_unit: false, symbol_space: false) == "S/12,345,678" + assert Money.to_string(brl(1_234_567_8900), symbol: true, strip_insignificant_zeros: true) == "R$123.456.789" + after + Application.delete_env(:money, :separator) + Application.delete_env(:money, :delimeter) + Application.delete_env(:money, :symbol) + Application.delete_env(:money, :symbol_on_right) + Application.delete_env(:money, :symbol_space) + Application.delete_env(:money, :fractional_unit) + Application.delete_env(:money, :strip_insignificant_zeros) + + Application.delete_env(:money, :custom_display_options) + end + end + test "to_string configuration with old delimeter" do try do Application.put_env(:money, :separator, ".")