Sylius: [MoneyBundle] Configurable round mode

Created on 1 Mar 2019  Â·  23Comments  Â·  Source: Sylius/Sylius

Describe the proposed solution
At the moment, Sylius is storing currencies in hundredth of currency. That's a really good point for all the reasons we all know. But it should be allowed to store currencies in thousandth or ten thousandth or whatever we want to.

To do so, at the moment we have to override some files like MoneyType, MoneyFormatter at least.

It should be nice if we just had to change a configuration parameter in yml to change this rounding so that we have a better configuration.

The main use case I see is for side projects that needs to store an exact amount that might itself be rounded from an other one. And the hundredth rounding mode might lose some informations at this point.

Describe alternatives you've considered
I don't see any other alternative than overriding previously given files.

Additional context

Do not stale Enhancement Future RFC

Most helpful comment

Okay, this makes sense. I mean feel free to make a pull request. And see what the Sylius people think.

All 23 comments

Well, I don't see a use-case for that. Is there a currency where one unit is split into thousands?

I have 2 in mind where I've been forced to change the rounding mode.

  1. Give a VAT including price to be stored without taxes and regiven with taxes (like 24.99€ with 20% of vat). If you store this in cents, it will be stored as 2083 cents (rounded from 2082,5 cents) then redisplayed as 25€. You lost this cent that commercially speaking might be necessary.
    If you store in thousandth instead of hundredth. This problem will not occur anymore.

  2. For an ERP project, we have to store prices of items/KG.
    But, the product might be sold per gram. to achieve this, we had to create a custom price formatter. Storing in cents was not an option at all. If the product is 1.5€/Kg, then in database, it's impossible to store the price in €/gram (0.150 €) and database only stores integers.
    In this case, we stored the price divided by 100 000

Okay, this makes sense. I mean feel free to make a pull request. And see what the Sylius people think.

Coming back here with an update. Not entirely linked to this functionnality, but this might increase the following bug :

If we store a currency in ten thousands of a unit and this currency has crazy amounts, like Japanese Yen for example. If you sell an item at ¥499 777,27 (approx 4000 euros) or a cart total at this price. You'll reach the SQL limit.

I'm sure that at the moment it's not really a problem with the hundredth rounding mode and using currencies like euro, dollar or any currency that has the same value.

Any ideas ?
Changing the type in doctrine to bigint results in returning strings instead of int and that might be a lot of work to overcome this...

Valid point. For this we should use a different datatype but I'd suggest to use the BIGINT then.
Working with strings will make it very hard to handle currencies.

Doctrine does convert BIGINT to string in PHP

Never heard of this but looks like it. Then it doesn't matter.

As a matter of fact, it matters.
Changing integers to BIGINT will break PHP7.2 strict typing. All get/set will return/expect an int and will work with strings. So will calculators and so on...

Classic programming problem! So it seems that MySQL uses 32 bit signed integers (at least the max value of INT seems to match that). BIGINT can be bigger than the 64 bit max integer value in PHP.
It is common to use strings to solve that problem, however might not be very convienent as most users don't need those big numbers and you'll add a lot of complexity that way.

For some countries however the limit might be reached quite fast, just consider Vietnam for example:
https://www.xe.com/currencyconverter/convert/?Amount=50&From=EUR&To=VND

50 euro's is worth more than 1,000,000 dong.

In this case though the least significant bits are truly quite insignificant and people will ignore pretty much everything under a thousand, allowing them to divide by 1000 and use ecommerce platforms that can not really handle their currency (they typically place a k behind the price to indicate the kilo factor).

Not sure, but I don't think it is worth converting the system to work with strings, maybe there are other options?

I'm also quite against converting to strings. It might create huge problems...
But on the other hand, I don't see any other viable option...

It might be crazy, but what about splitting the price into 2 columns, one for the whole number and another for the decimal part? It might come as a feature flag and/or a plugin.

Like having a integerPriceand a decimalPrice but keeping the price in int in hundredth or thousandth or any other rounding mode but only for PHP ?
It would demand to explode the price in two parts when storing it in database (maybe via event listeners) but I think it wouldn't ask too much effort and nearly no refactor from an external POV of the class.

Thought about that as well, it would solve the problem for now (at least on 64 bit systems) it seems. Maybe a plugin would be better, because in most use cases you really don't need this. That way it's possible to try this out and see what the consequences are without being an integral part of the framework.

If it is in a plugin, I'd require it to be compatible with MoneyBundle used in standalone.

@Roshyo by having 2 fields I think there is no need to keep the price in hundredth or thousandth.
Currently in MySQL it's saved as a INT(11) signed, so it can store a price up to 8 digits, because it's multiplied by 100.
Splitting in 2 columns will allow storing prices up to 10 digits (max 2147483647 on 64 bit systems) regardless of the decimal part, because it will be stored separately.
Prices can also be stored as unsigned integers and the whole number can also be stored as BIGINT if possible.

@vvasiloi the original problem in this thread was : "Store currency in other rounding than hundredth" because of the above reasons.

Simply exploding price in 2 columns and remaining the simple "2 digits after the coma" does not solve the original issue. Or I did not understand what you said

@Roshyo if it will be stored in two columns, then there will be no need for rounding.
For example, on a 64 bit system, it will be possible to store a maximum price of 2147483647.2147483647.
But I see 2 problems which I'm not sure can be easy to overcome:

  • int typehints for prices
  • most if not all calculations related to prices are done with integers
    I was also thinking about a price class which will handle all the transformations and calculations.

I'm afraid I don't understand your point. the decimal part is an int, ok, but the value of 1 unit has to be defined. At the moment 1 unit in decimal is 1 cent (in € for example) but storing a cent and not a tenth of a cent will not solve the above problem. or the unit in your example for the decimal part is more one billionth ? Where 10.2147483647 = 10.21 € ?
But then we wouldn't be able to sell products at 10.50€ ?

Right, 10.50€ will be stored as 10 and 50 and the trailing zero will break the calculations.
Also, the leading zeroes will cause issues, ex: 10.05€.
Conclusion: storing decimal part as integer is a bad idea.

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in a week if no further activity occurs. Thank you for your contributions.

I am currently having an issue with roundig aswel.
In my case, having a 24% discount on every OrderItem.
Screenshot_20190703_124634

This makes the grand total inaccurate. A workaround could be applying the discount on the Order, but that causes issues with taxation.

@Roshyo
I'm not sure if this issue is the same as your initial issue, but I think this is an additional case for having more accurate storage of currency.

Integrating https://github.com/moneyphp/money might be a good idea.

I am currently having an issue with roundig aswel.
In my case, having a 24% discount on every OrderItem.
Screenshot_20190703_124634

This makes the grand total inaccurate. A workaround could be applying the discount on the Order, but that causes issues with taxation.

@Roshyo
I'm not sure if this issue is the same as your initial issue, but I think this is an additional case for having more accurate storage of currency.

As a followup to my comment above:
The correct solution is to use a Distributor to calculate the discount each unit should receive.
The result can be applied automatically to the order (items/units) by using an Applicator.

This can be seen in the Sylius docs: https://docs.sylius.com/en/1.6/cookbook/promotions/custom-promotion-action.html#create-a-new-promotion-action

Was this page helpful?
0 / 5 - 0 ratings