Fix rounding errors when ordering multiple items
| Q | A |
|---|---|
| Bug fix? | yes |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | no |
Currently taxes are calculated based on unrounded calculations. According to http://www.123rechnung.com/kb/rundungsdifferenzen-bei-der-mwst-ust-berechnung/ this is wrong for items which do get sold in whole pieces and not by weight, length or something similar. For example when you have a product with net price 14.28 and 19% tax, you have a gross price of 16,99 EUR. When you buy 10 items of this, the total gross price will be 10 * 16.99 = 169.90 or in other formula round(net price * 1.19, 2) * 10 round(14.28 * 1.19, 2) * 10 = 169.90
Currently in https://github.com/coreshop/CoreShop/blob/440cb324feaeab07b0a8eaba66675cb9d0f41c4e/src/CoreShop/Component/Core/Order/Processor/CartItemProcessor.php#L61 and https://github.com/coreshop/CoreShop/blob/440cb324feaeab07b0a8eaba66675cb9d0f41c4e/src/CoreShop/Component/Core/Order/Processor/CartItemProcessor.php#L86 CoreShop calculates it in a different way, it does: round(net price * 10 * 1.19, 2) round(14.28 * 10 * 1.19, 2) = 169,93
The difference gets even bigger when someone orders 1000 items. With this pull request the tax is always calculated as the tax for one product (rounded to two decimals) multiplied by quantity.
Replaces #1842
please also fix tests, I will not merge it it they fail
@dpfaffenbauer Currently https://github.com/coreshop/CoreShop/blob/master/features/domain/cart/rules/discount_amount_action_tax_rounding.feature fails. Actually I think this is because of the rounding errors but I cannot understand the test at all. When we have a 10 EUR voucher and 20% tax, why is the net price 8.40 EUR in https://github.com/coreshop/CoreShop/blob/440cb324feaeab07b0a8eaba66675cb9d0f41c4e/features/domain/cart/rules/discount_amount_action_tax_rounding.feature#L36? Isn't it 10 / 1.2 = 8.33 ?
Or the other way around: 8.40 EUR net price * (1+20% tax) = 8.40 * 1.2 = 10.08
I am not a business expert, what is wrong in my calculation?
Note there is more than one product in the cart, product "Voucher" DOES NOT have a TaxRule applied, so Gross=Net there.
Discount applied to the Cart are calculated like:
| Item | Quantity | Price Net | Price Gross |
|---|---|---|---|
| Dinner | 1 | 49,17 | 59,00 |
| Summer | 2 | 16,67 | 20,00 |
| Winter | 1 | 4,17 | 5,00 |
| Potential | 1 | 175 | 210,00 |
| Voucher | 1 | 10,00 | 10,00 |
| TOTAL | 253,34 | 304 |
I then have a discount of € 10. So I take the 10€ and distribute them to ALL products:
| Item | Discount Amount Gross |
|---|---|
| Dinner | 1,94 |
| Summer | 0,65 |
| Winter | 0,16 |
| Potential | 6,86 |
| Voucher | 0,39 |
These discounts then get it's tax amount calculated:
| Item | Discount Amount Net |
|---|---|
| Dinner | 1,62 |
| Summer | 0,54 |
| Winter | 0,13 |
| Potential | 5,71 |
| Voucher | 0,39 |
So Net Amount 8,39. Due to rounding, it actually is 8,4
The resulting Net and Gross values (adjusted with missing cents if available), will then be distributed to the CartItem and taxes get changed accordingly.
Reason for this kind of calculation: You simply cannot apply the discount to a Tax Rate mixed Cart. We need to distribute it and calculate it according to each item.
It would be even better to split it per Quantity of CartItem, even there you can get rounding issues depending on how you calculate tax (item-based or quantity-based). But that is currently not implemented.
OK, thank you for explanation. I think the net total price of 253,34 is wrong in above calculation (you calculated 304 / 1,2 but this is wrong because product voucher has no tax) - when I sum up the net prices I get 255,01 as in https://github.com/coreshop/CoreShop/blob/1f26f9a49e814da6a8843b8c869c0a996b5407c4/features/domain/cart/rules/discount_amount_action_tax_rounding.feature#L39
I try to debug a bit. Will notify you if this PR is actually correct...
where did I calculate 304 / 1,2?
You calculated 304 / 1.2 in the first table in column Price Net in row TOTAL. Here 255,01 is correct (but does not matter)
Except for this, the calculation in above tables is correct but https://github.com/coreshop/CoreShop/blob/000e6e03f06eac8fc928c1219e58dc351df7d9d2/src/CoreShop/Component/Core/Order/Processor/CartItemProcessor.php#L65-L75 works differently:
Let's take product "Summer" from the test. Gross price is 10,00, quantity is 2
In https://github.com/coreshop/CoreShop/blob/000e6e03f06eac8fc928c1219e58dc351df7d9d2/src/CoreShop/Component/Core/Order/Processor/CartItemProcessor.php#L65
1000 * 2 = 2000
gets calculated
This gets used in https://github.com/coreshop/CoreShop/blob/000e6e03f06eac8fc928c1219e58dc351df7d9d2/src/CoreShop/Component/Taxation/Calculator/TaxRulesTaxCalculator.php#L87
to calculate a tax of round(2000 - (2000 / 1.2)) = 333 -> $totalTaxAmount = 333;
But this is not correct because the tax for a single item is round(1000 - 1000 / 1.2) = 1,67. This means that the tax for 2 items has to be 2 * 1,67 = 3,34.
In your table and current implementation first the total gross price gets calculated and from this the tax:
| Item | Quantity | Price Net | Price Gross |
|---|---|---|---|
| Summer | 2 | 16,67 | 20,00 |
This means that the tax is 20,00 - 16,67 = 3,33 But according to http://www.123rechnung.com/kb/rundungsdifferenzen-bei-der-mwst-ust-berechnung/ you have to calculate tax for one item first and multiply this by quantity.
With this PR the tax is based on single item's tax. Then the calculation table looks like this:
| Item | Quantity | Total Price Net | Total Price Gross | Total Tax |
|---|---|---|---|---|
| Dinner | 1 | 49,17 | 59,00 | 9,83 |
| Summer | 2 | 16,66 | 20,00 (2 * 10,00) | 3,34 (2 * 1,67) |
| Winter | 1 | 4,17 | 5,00 | 0,83 |
| Potential | 1 | 175 | 210,00 | 35 |
| Voucher | 1 | 10,00 | 10,00 | 0 |
| TOTAL | 255,00 | 304 | 49,00 |
And this is exactly what the test complained about.
You can even see that the test currently is wrong because in
https://github.com/coreshop/CoreShop/blob/000e6e03f06eac8fc928c1219e58dc351df7d9d2/features/cart/rules/discount_amount_action_tax_rounding.feature#L38
total tax is 47,38
but
the difference of total sum with tax and without tax in
https://github.com/coreshop/CoreShop/blob/000e6e03f06eac8fc928c1219e58dc351df7d9d2/features/cart/rules/discount_amount_action_tax_rounding.feature#L41-L42
is 294,00 - 246,61 = 47,39.
I adjusted the test.
The link you provided states both are valid calculations: based per item-unit, or based per item-total. It depends on the company and/or the ERP System. Some calculate it per unit, some per item-total.
So changing this like that fixes your use-case, but doesn't solve the underlying issue.
But: I will look into that a bit more and come back to you.
Exactly, both calculations are allowed but for products which are typically sold as whole items, we have to use tax from one item and multiply it by quantity. Only for products which are typically sold by length, weight or another physical unit tax gets calculated from total price. And as the normal use-case for online shops is to offer products which are sold by whole items, I thought we should adjust calculation.
But take your time, I have also read a lot how it is actually correct. Looking forward to your feedback...
Exactly, both calculations are allowed but for products which are typically sold as whole items, we have to use tax from one item and multiply it by quantity. Only for products which are typically sold by length, weight or another physical unit tax gets calculated from total price. And as the normal use-case for online shops is to offer products which are sold by whole items, I thought we should adjust calculation.
The Website suggests that. In reality business do it either this way or the other. Depending on history or how their ERP System does it. To make it 100% right, we need to make that configurable in CoreShop.
Right. Seems that this is also what Magento does: https://mage2.pro/t/topic/1151
closed due to inactivity. not sure if actually needed. If so, please let me know.