Git Product home page Git Product logo

money's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

money's Issues

Return atoms from compare instead of -1/0/1

For example, DateTime.compare/2 returns :lt, :eq or :gt. This seams more readable than returning -1/0/1.

I'm willing to make a PR for this change, but since this breaks compatibility, it will probably require version 2.0, so I'm making this issue first to see if there is any interest in such a PR.

Ecto 3.0 released

Ecto 3.0 has just been released, can we update the soft deps line and cut a new version?

The move to 1.0

I’m mostly happy with where things are.
What do you think we need to tackle before releasing 1.0?

  • #9 Multiply or Divide Money with Money
  • Update README
  • Remove config.exs

Anything else?

Move currency shortcuts to a separate module

When running documentation, the shortcut methods for each currency, Money.usd/1, Money.gbp/1 etc clutter up the namespace.
I think we should move those to another module. Perhaps Money.Helpers.usd
What do you think?

Implement Enumerable for %Money{}

Currently get an error if i try to enumerate over a money object (easy fix on my side, but would be good to be part of the lib)

** (Protocol.UndefinedError) protocol Enumerable not implemented for %Money{amount: 0, currency: :USD}

Money.equals? should not raise on different currencies

Hi,

I understand a call to fail_currencies_must_be_equal when trying to add, subtract or compare two Money structs, but I think equals? should just returns false because there is nothing wrong with comparing USD with CAD, they are just different. Everytime I need to compare two Money's I need to do something like money1.currency == money2.currency && Money.equals?(money1, money2).

What do you think?

Thanks

Incorrect Parsing for USD

25¢ without a padded zero gets parsed as $25, even after setting the default separator and delimiter correctly.

iex(1)> Money.parse(".25", :USD) 
{:ok, %Money{amount: 2500, currency: :USD}}

Money.divide doesn't work with odd negative values

Even values appear to work correctly, but odd values are off.

iex(4)> Money.new(-5) |> Money.divide(2)
[%Money{amount: -1, currency: :USD}, %Money{amount: -1, currency: :USD}]
iex(5)> Money.new(-6) |> Money.divide(2)
[%Money{amount: -3, currency: :USD}, %Money{amount: -3, currency: :USD}]
iex(6)> Money.new(-7) |> Money.divide(2)
[%Money{amount: -2, currency: :USD}, %Money{amount: -2, currency: :USD}]

Support for parsing negative values

Trying to run
Money.parse("-3.4") will result in positive value:
{:ok, %Money{amount: 340, currency: :GBP}}
is it possible to generate negative ?
I know this can be achieved with Money.new(-....) but my input is float so need to use the parse

Potentially problematic money arithmetic

I noticed some potential pitfalls with arithmetic operations, both on a technical point and conceptually.

The technical issue applies to all (add/2, subtract/2, multiply/2) when allowing floats as an input type, see: https://github.com/liuggio/money/blob/master/lib/money.ex#L251

While the library is right in assuming you want to avoid using floats to store currency, exposing functionality like this is still just as much of a problem. Assume the floating point value is of the kind that is representable (i.e. the compiler isn't going to map it from one value into the closest representation, but it actually can represent it exactly), the issue arises in the operation itself. Since if you multiply it by 100, it may no longer be exactly representable (may incur some loss of precision).

An easy way to demonstrate this is with the last binary64 value that can still represent a fractional part: 4503599627370495.50 (exponent: 0x432, mantissa: 0xfffffffffffff). Any value greater than that will be a binary64 that can no longer represent a fractional part (its level of precision is now up to whole numbers). This causes very noticeable results when multiplying it by 100: 4503599627370495.50 * 100 = 450359962737049536.00. Now the subunit of that money is incorrect, it's at 36 cents instead of 50 cents.

value = Money.new(0, :USD)
Money.add(value, 4503599627370495.50) #=> %Money{amount: 450359962737049536, currency: :USD}

Worse still if we're working with values even bigger than this, say values large enough where there is a loss of precision in whole numbers then that operation will now cause the money value to be off by whole dollar amounts (and the higher you keep going the greater the range of error becomes).

The only safe way to handle this is to not do any operations with floating points. Rather extract the whole number portion (so using trunc/1) and then extract the fractional portion (unfortunately I'm not aware of an elixir or erlang function to do this, worst case would be extracting the exponent and mantissa portions as integers using a binary and then calculating it from that). Oh the joys of floating point numbers 😝.

The other potential problem I noticed (unsure if intentional or not) is just a conceptual problem. It looks like the library is assuming all currencies or monetary values will be in subunits of 1/100. The problem here is not all currencies share this common trait, some currencies have no subunits, while others go up to 1/1000, while others don't even use a subunit that's divisible by 10 (instead something like 5). The other problem is this is regarding typical denominations, electronically many currencies aren't restricted to these units. Some financial services will operate on money with many levels of precision (not just what that given currency's denominable subunit may be).

Custom currencies

What do you suggest we do for custom currencies, like cryptocurrencies?

Configuration for default values

One of @andrewtimberlake ideas is to create Money.new/1 with a default currency setting in config.
That would simplify things a great deal for projects that only use a single currency.
I only use Money.zar(100_00) because I don’t want to type Money.new(100_00, :ZAR) everywhere. If I could configure the default currency, then the convenience method becomes less important.
With Money.Helpers, you could do import Money.Helpers, only: [usd: 1] and then do usd(100_00) everywhere.

another feature would be adding:

:separator character between the whole and fraction amounts https://github.com/liuggio/money/blob/master/lib/money.ex#L338
:delimiter character between each thousands place https://github.com/liuggio/money/blob/master/lib/money.ex#L340

Amount gets multiplied by 100 when amount is parsed from a string

Hi again!

I've run into a strange error. I have a Purchase Ecto model, which has the following schema:

schema "purchases" do
  field :amount, Money.Ecto.Type

  timestamps
end

Adding an amount when the argument is an integer works fine:

Purchase.changeset(%Purchase{}, %{amount: 50})
# Ecto.Changeset<changes: %{amount: %Money{amount: 50, currency: :NOK}>

But, when I pass in params as strings (which would be the case when an HTML form is posted) something weird happens.

params = %{"amount" => "50"}
Purchase.changeset(%Purchase{}, params)
# Ecto.Changeset<changes: %{amount: %Money{amount: 5000, currency: :NOK}>

Any idea as to why this might happen?

Validating amount in Ecto changeset

Hello, and thanks for Money!

Unless I'm doing something wrong, it seems that validating the amount using Ecto's validate_number doesn't work, as negative integers are allowed through to the database despite greater_than: 0. Is this expected, or should the number validation work on the underlying integer?

schema "materials" do
  # ...
  field :price_sq_m, Money.Ecto.Type
end

@required_fields ~w(price_sq_m ...)a

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, @required_fields)
  |> validate_required(@required_fields)
  |> validate_number(:price_sq_m, [greater_than: 0])
end

Different casting on creation and retrieving from db

I think this commit introduced a bug.

MyApp.Records.create_record(%{amount: "$42"})

{:ok,
 %MyApp.Records.Records{
   __meta__: #Ecto.Schema.Metadata<:loaded, "records">,
   amount: 4200,
   id: 1,
   inserted_at: ~N[2019-10-02 16:30:32],
   updated_at: ~N[2019-10-02 16:30:32]
 }}

amount is 4200

MyApp.Records.list_records()

[
  %MyApp.Records.Record{
    __meta__: #Ecto.Schema.Metadata<:loaded, "records">,
    amount: %Money{amount: 4200, currency: :USD},
    id: 1,
    inserted_at: ~N[2019-10-02 16:30:32],
    updated_at: ~N[2019-10-02 16:30:32]
  }
]

amount is %Money{amount: 4200, currency: :USD}

This behaviour makes tests like this always fail

test "get_record!/1 returns the record with given id" do
  record = record_fixture()
  assert Records.get_record!(record.id) == record
end

protocol Phoenix.HTML.Safe not implemented

In docs:

If you are using Phoenix, you can include money objects directly into your output and they will be correctly escaped.
<%= Money.new(12345,67, :GBP) %>

but it gives an error

undefined function Money.new/3

I removed a comma from the number
<%= Money.new(1234567, :GBP) %>

it gives another error:

protocol Phoenix.HTML.Safe not implemented for %Money{amount: 1234567, currency: :GBP}

Implementing from_decimal and to_decimal

I think it would be nice and beneficial if there could be a way to accept Decimal on both input and provide on the output side. Many applications might be working or getting decimals and if then we want to use the money library it's kind of cumbersome. What do you think?

Supports for ExAdmin

Is there any plan to support ex_admin?
I've tried to give some support to make money work with ex_admin in my fork
What I've done currently is only implementing ExAdmin.Render, but for the form, we still need to explicitly specify string as the type(this where I hit the walls).

Can I incorporate floats

I want to be able to work with floats, any objection to this:

iex> Money.new(100.55, :USD) |> Money.add(10.12)
%Money{amount: 11067, currency: :USD}

Easy way to do a basic operation on a List of Moneys

Hello,
I'm sorry to ask a question on the Issues of the project, but I'm very often doing a basic operation on a list of Money elements, for example sum a List of Money elements. Right now I'm almost always writing utilities functions like this:

  def sum_moneys(moneys) do
    moneys
    |> Enum.reduce(Money.new(0), fn price, acc -> Money.add(acc, price) end)
  end

I'm wondering if this is the best way to do, and if it is common enough so that at some point, it would be possible to integrate directly in the library.

Money.new with integer

Hi,

Is there a special reason for Money.new/3 only accept float() and binary() as first argument? I'm working with an api which returns me some prices but when the value doesn't have decimal places they just return an integer (don't ask me why). So I can get 1.23 or 1 from the same endpoint.

Passing this to Money.new raises when the value is an integer, so I had to do the following code, which isn't a big deal, but ain't pretty either.

Money.new(value / 1, "USD")

What do you think about changing it to also accept an integer?

Thanks

Using Money.Ecto.Amount.Type in embedded schema

When using Money.Ecto.Amount.Type in embedded schema like this:

defmodule Outer do
  schema "outers" do
    embeds_many :inners, Inner
  end
end

defmodule Inner do
  embedded_schema do
    field :price, Money.Ecto.Amount.Type
  end
end

with outers.inners backed by a json database column it results with an error:

** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %Money{amount: 1500, currency: :USD} of type Money (a struct), Jason.Encoder protocol must always be explicitly implemented.

Implementing Jason.Encoder is straightforward (Protocol.derive(Jason.Encoder, Money)) but results in {"price": {"amount": 123, "currency": "XYZ"}} json object stored in database, which is not what the Money.Ecto.Amount.Type is supposed to do (which is to store only the amount as integer).

This is due to a fact that embedded schemas do not go through regular casting phase:

For example, PostgreSQL will store embeds on top of JSONB columns, which means types in embedded schemas won't go through the usual dump->DB->load cycle but rather encode->DB->decode->cast. This means that, when using embedded schemas with databases like PG or MySQL, make sure all of your types can be JSON encoded/decoded correctly. Ecto provides this guarantee for all built-in types.

However, I've found out that this is not 100% true, as one can use Ecto.Type's c:embed_as/1 to force ecto to use the dump/1 function of custom type resulting in the correct {"price": 123} json object stored in the database.

The implementation is fairly straightforward:

# lib/money/ecto/amount_type.ex
  def embed_as(_), do: :dump

embed_as/1 callback was added in ecto 3.2.0, but it can be added without causing backwards compatibility issues - it will simply not be called by ecto versions prior to 3.2.0

Support for multiple currencies (Ecto)?

Hi, love the library - great work! 👍

In the docs it says

This type expects you to use a single currency. The currency must be defined in your configuration.

Any plans on making it support multiple? Or would you perhaps be interested in a pull request?

Cheers

Convert a currency given an exchange rate

For example:

# Knowing that 1 USD is equivalent to 15 ARS...
usd = Money.new(100, :USD)
ars = Money.exchange from: usd, to: :ARS, rate: 15
assert ars == Money.new(1500, :ARS)

or

Money.rate(:USD, :ARS, 15)
usd = Money.new(100, :USD)
ars = Money.convert usd, to: :ARS
assert ars == Money.new(1500, :ARS)

Warn/raise when the currency passed in is not the currency in the config

Hi!

Thanks for the work here! Following up on the discussion on this PR, we observed a non-critical issue when dealing with different currencies other than the one specified in the config file.

Ex:

config :money,
  default_currency: :GDP,

iex(2)> Type.cast("R$ 1.234,00")
{:ok, %Money{amount: 123, currency: :GBP}}

I think the behavior here should to at least warn the user about the mismatch as opposed to converting the amount incorrectly and silently.

Please let me know if you agree with this. If not, no worries, please close this issue! If you do agree, I'd like to take a stab at it if possible.

Thanks for the work!

Infrastructure: Ecto Type

Would be great to add the Ecto behavior...

Adding value:
Designing applications with persistence ignorance is a good practice and delegate the Ecto type to the infrastructure layer would be a good practice...

Add a decimal precision money type

There are times when things like averages would be stored in the database with a precision > 2.

Would be useful to have a decimal precision money type for this in ecto. obviously its a bit more complicated when doing multiplications / divs

Another thought is having an algebraic numerator / demoninator factor list for so you have things like:

x = 1000
y = 66
z = x / y # this would be 15.151515...

But it could be stored as:

z = [[1000], [66]]

or

z = [[2, 500], [2, 33]]

and reduced as

z = [[500], [33]]

division by float

Is there a reason why division are only supported by integers? If not, I would try to fix it and submit a pull request.

to_string for negative values

If I create a negative value:
m = Money.new(-20000)
%Money{amount: -200, currency: :GBP}

and I try to convert it to string
Money.to_string(m)
I get a wrong format like
"-,200.00"

This happens when I try to convert values of hundreds such as:
200.00
200000.00
etc.

Possible problem when parsing floats

Since parse(float, currency, _opts) uses * 100 hardcoded, there's unexpected behaviour when using currencies like TND, where the conversion to the smaller unit (milim or millimes) is * 1000.
I haven't looked into other ones and just stumbled upon this while reading the code, but I have the feeling that there could be more currencies with that kind of problem.

Change Money.new/2 with string

I don’t think Money.new/2 should work as it does,

iex> Money.new("1000", :USD)
%Money{amount: 1000, currency: :USD}

Instead you should be able to do (this is important for working with form input)

iex> Money.new("$10.00", :USD)
%Money{amount: 1000, currency: :USD)
# This means that
iex> Money.new("10", :USD)
%Money{amount: 1000, currency: :USD}

We probably still need the currency atom because a number of currencies share symbols. $ is for USD, AUD, etc
Another option is to have Money.new/2 only take the amount as an integer and introduce Money.parse/2 to handle strings.

Surprising operands

Hi,

Thanks for the great work shared with the community.

While I was reading the readme, I got a little bit surprised by this in particular:

five_eur              = Money.new(500, :EUR)
ninety_nine_eur  = Money.subtract(hundred_eur, 1)   # %Money{amount: 99_00, currency: :EUR}

Can you explain the choice of using 500 to represent an actual value of 5 and at the same time using 1 to represent an actual value of 1? Or with code, why not?

ninety_nine_eur  = Money.subtract(hundred_eur, **100**)   # %Money{amount: 99_00, currency: :EUR}

Thanks for the clarification,
Seb

The latest release on Hex doesn't include recent features

It would be really great if we could get an updated version pushed to Hex. Specifically, I want to be able to use the Ecto type in an embedded schema stored in a jsonb column in Postgresql, but the version on Hex doesn't know how to cast %{amount: 1000, currency: "USD"} to %Money{}.

In the meantime, am just pointing to the latest commit ref using a github: dependency in my mix.exs, which seems to be working well.

Where currency rates come from?

Hello!

I read your docs and didn't realize what source of currency rates and what should I do to retrieve that rates.
Am I missed it?

Multiply or Divide Money with Money?

Does it make any sense to multiply or divide a Money with a Money?
I don’t usually say $5 * $5, it would be $5 * 5
I also don’t say $5 / $5 but rather $5 / 5

Make precision customizable

It should be possible to edit the default precision and pass precision as an option when parsing/converting to string.

My use case for this is that I'm working with cryptocurrencies, and precision for my use case needs to be quite high (i.e. right now, 1 XRP === $0.320442)

Related: #62

I'll be able to work on the PR above to customize it a bit more for my use case (making to_string add precision as an opt), but would like to know what the core team thinks about it

FunctionClauseError in Money.Ecto.Amount.Type load/1 when loading a map

After updating to version 1.7 today I'm getting a function clause error while attempting to load Money types.

Versions:
money -> 1.7.0
ecto -> 3.3.3
ecto_sql -> 3.3.4

Error:

2020-03-03T04:29:08.290119+00:00 app[web.1]: ** (FunctionClauseError) no function clause matching in Money.Ecto.Amount.Type.load/1
2020-03-03T04:29:08.290120+00:00 app[web.1]: (money) lib/money/ecto/amount_type.ex:68: Money.Ecto.Amount.Type.load(%{"amount" => 882800, "currency" => "USD"})
2020-03-03T04:29:08.290121+00:00 app[web.1]: (ecto) lib/ecto/schema/loader.ex:71: anonymous fn/5 in Ecto.Schema.Loader.unsafe_load/4
2020-03-03T04:29:08.290121+00:00 app[web.1]: (elixir) lib/enum.ex:1940: Enum."-reduce/3-lists^foldl/2-0-"/3
2020-03-03T04:29:08.290122+00:00 app[web.1]: (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
2020-03-03T04:29:08.290123+00:00 app[web.1]: (ecto) lib/ecto/type.ex:682: Ecto.Type.load_embed/3
2020-03-03T04:29:08.290124+00:00 app[web.1]: (ecto) lib/ecto/type.ex:923: Ecto.Type.process_loaders/3
2020-03-03T04:29:08.290124+00:00 app[web.1]: (ecto) lib/ecto/repo/queryable.ex:374: Ecto.Repo.Queryable.struct_load!/6
2020-03-03T04:29:08.290132+00:00 app[web.1]: (ecto) lib/ecto/repo/queryable.ex:206: anonymous fn/5 in Ecto.Repo.Queryable.preprocessor/3

I'll go ahead and create a PR.

Money.Sigils requires existence of the atom for the currency

Since the implementation of Money.Sigils relies on :erlang.list_to_existing_atom they can't be used without former usage of the atom for the currency:

Interactive Elixir (1.3.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import Money.Sigils
Money.Sigils
iex(2)> ~M[999]USD
** (ArgumentError) argument error
    :erlang.list_to_existing_atom('USD')
    lib/money/sigils.ex:19: Money.Sigils.sigil_M/2

Stumbled upon this problem, when some of my tests randomly failed.

If this is a WONTFIX problem, at least some hint in the README would be useful.

Allow currency convertion given an exchange rate

For example:

# Knowing that 1 USD is equivalent to 15 ARS...
usd = Money.new(100, :USD)
ars = Money.exchange from: usd, to: :ARS, rate: 15
assert ars == Money.new(1500, :ARS)

or

Money.rate(:USD, :ARS, 15)
usd = Money.new(100, :USD)
ars = Money.convert usd, to: :ARS
assert ars == Money.new(1500, :ARS)

change version in readme

Hi, you have following lines in README.MD:

def deps do
  [{:money, "~> 1.2.1"}]
end

Using 1.2.1 version with elixir 1.7.4., phoenix 1.4. ecto 3.0 I got an error after mix deps.get:

Resolving Hex dependencies...

Failed to use "ecto" because
  money (version 1.2.1) requires ~> 1.0 or ~> 2.0 or ~> 2.1 *
  phoenix_ecto (version 4.0.0) requires ~> 3.0

* This requirement does not match pre-releases. To match pre-releases include a pre-release in the requirement, such as: "~> 2.0-beta".

** (Mix) Hex dependency resolution failed, change the version requirements of your dependencies or unlock them (by using mix deps.update or mix deps.unlock). If you are unable to resolve the conflicts you can try overriding with {:dependency, "~> 1.0", override: true}

So, I changed version to 1.3.1 and it helped.

I try to make pull request with correct version, but got the following:

ERROR: Permission to liuggio/money.git denied to lyo5ha.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

UK VAT Precision during calculations

Hi

Is this a problem meeting legal requirements for invoices/ VAT Returns ?

Having a different precision between store and to_string would get close to fixing the problem.

From:

https://www.gov.uk/guidance/vat-guide-notice-700

Note:

Other jurisdictions are available such as GST in Australia, I'm sure the it will not be only the UK that has this or similar legally binding rules.

Quote

17.5.2 Calculation based on tax per unit or per article
If you want to work out the VAT per unit or per article
    (for example, for use in price lists), 
    you must work out the amounts in one of the following ways:

4 digits after the decimal point and then round to 3 digits 
- for example, if the VAT is £0.0024, it should be rounded to £0.002 (0.2p)

the nearest 1p or 0.5p 
- if you decide to do this, 
    you must not round the VAT down to ‘nil’ on any unit or article 
    that is liable at the standard or reduced rate, 
    for example, if the VAT is £0.0024 it should be rounded to £0.005 (0.5p)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.