ioquatix / latinum

Latinum is a framework for resource and currency calculations.
MIT License
113 stars 7 forks source link

Brazilian currency support #10

Closed rainerborene closed 3 years ago

rainerborene commented 3 years ago

Hi @ioquatix. We're considering migrating from money-rails to latinum gem and we faced some issues during this transition. Here's what I have on my latinum.rb initializer. By the way, it seems that latinum/currencies/global isn't required by default.

require "latinum/currencies/global"

Latinum::Currencies::Global[:BRL] = {
  precision: 2,
  symbol: "R$",
  name: "BRL",
  description: "Brazilian Real",
  formatter: Latinum::Formatters::DecimalCurrencyFormatter,
  delimeter: ".",
  separator: ","
}

First problem is about parsing values. For example:

bank = Latinum::Bank.new(Latinum::Currencies::Global)
bank.parse("9,90", default_name: :BRL) # returns #<Latinum::Resource "990.0 BRL">

In this example, shouldn't it return #<Latinum::Resource "9.9 BRL">? Second problem: In our database we're storing values as integer and only in cents. Is there any configuration available to handle values only in cents by default?

Thanks.

ioquatix commented 3 years ago

Storing an integral format for currency should be done according to the number of places/precision required. This is provided by the formatter: https://github.com/ioquatix/latinum/blob/master/lib/latinum/formatters.rb#L41-L50 as this can be a lossy operation. It's totally reasonable to write "9.999999 USD" and expect multiplication by 1_000_000 to produce 9_999_999 rather than 9_990_000 which is what you'd get if you think that the smallest unit in any computation is "cents".

As an aside, one way I've solved this in the past is storing both - a string for the value that was provided by the user - and a floating point value which is used for aggregations/statistics only (e.g. total sales where a few cents or dollars won't matter on a bar chart). This is really a lot faster than using decimal types for values.

Regarding bank.parse - this is a standard algorithm for turning strings into money instances. It's not taking into consideration the formatting options since we don't even know what formatter to use until we figured out the currency. As it stands, the amount is parsed by BigDecimal. Therefore, the way it's currently implemented doesn't consider the delimiter/separator options you specified for formatting.

There are two (non-exclusive) options here:

  1. We could extend bank.parse to take into account formatting options, or
  2. We could add a #parse method to the formatter.

If we can always determine the currency by the trailing symbol, maybe this is acceptable. The key point for me is (1) predictability and (2) consistency. However, I also feel it's reasonable that Bank#parse should be able to parse the inversion of Bank#format which is a strong reason to do the above.

rainerborene commented 3 years ago

However, I also feel it's reasonable that Bank#parse should be able to parse the inversion of Bank#format which is a strong reason to do the above.

That would be awesome.

ioquatix commented 3 years ago

I have an open PR which should address your issue, do you mind testing it out? Maybe we can add your use case as a test/spec.