num-num / ubl-invoice

A modern object-oriented PHP library to create valid UBL and Peppol BIS files
MIT License
78 stars 69 forks source link

Rounding values #32

Closed stevenrombauts closed 3 months ago

stevenrombauts commented 1 year ago

Hi,

We are running into a little issue with rounding. We receive invoice data from one application, create a UBL file for it and then forward it to another accounting software.

However, the lineExtensionAmount value for each individual invoice lines will be rounded when serializing the XML (see InvoiceLine#247). Because the data we receive is keeping 4 decimals for quantities and monetary values, this will end up inserting a different value in some invoice lines in the generated UBL.

This results in a different total when you add them all up. That means that the total we pass to Invoice::setLegalMonetaryTotal() will not always match with the sum of each invoice line.

I was trying to find some guidelines on how many decimals are allowed according to the specs, but I could not find any definitive answers. Do you know if it is actually required to restrict these values to 2 decimals according to the standards definition?

Thanks, Steven

PS: for good measure, some example numbers:

Original    After number_format()
70.4145     70.41
54.1645     54.16
21.6645     21.66
330.4145    330.41
----------------------
476.658     476.64
stevenrombauts commented 1 year ago

@brtdv Based on my understanding so far, I think there can be any number of decimals for monetary amounts. For example, looking at the LineExtensionAmount property in the InvoiceLine specification, its data type is listed as Amount.Type. This type is defined as the base type xsd:decimal, which represents all decimal numbers with arbitrary lengths.

Is there a specific reason you rounded these values? If not, I'm happy to provide an MR that removes the number_format() calls. Alternatively, we could make the number of decimals configurable?

brtdv commented 1 year ago

Hi @stevenrombauts

The most important reason the number_format is included is because I want to also take into account some scenario's where PHP's setlocale() function has been used to set a different locale and where this would influence number out.

I would very much be open to a PR where we update the number_format usage to allow more precision but keep the formatting of the decimal point functionality.

TSimkus commented 5 months ago

Hey, @stevenrombauts , @brtdv Sadly all amounts must be rounded to 2 decimal places. Source: https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/tree/

If you would check all elements that contain amount - they are required to be rounded to 2 decimals.

We also had rounding issues on our system and tried to find a solution as we also store prices with 4 decimals. However, we could not find any other solution, but to adapt to rounding to 2 decimals. If you have any solutions - please let me know. 🙂

brtdv commented 3 months ago

@TSimkus thank you for your feedback.

So I guess it wouldn't be desirable for the num-num/invoice-ubl to start rounding numbers?

I guess there are two options:

  1. Keep using the number_format() function within num-num/invoice-ubl, without rounding and see it as the responsibility of the developer that is implementing the num-num/invoice-ubl library to pass in numbers that are rounded to 2 decimals, to prevent any rounding issues when generating UBL files?
  2. Update all occurances of number_format() within num-num/invoice-ubl and also make sure that numbers are always rounded.
TSimkus commented 3 months ago

@brtdv

I don't think this package should be responsible for rounding values. I would suggest to leave rounding for the developer as Peppol's will throw a fatal error [BR-DEC-12] when validating the invoice. So invoices should never go through.

Also leaving rounding for developer will prevent any feature issues, if Peppol's would change their validation and allow more than 2 decimals.

brtdv commented 3 months ago

OK, so I think we can keep the current implementation. As discussed before, number_format() is mainly used to output numbers with the correct decimal point and requires a $decimals argument. It's the responsability of the developer using the library to pass in numbers that have been rounded to 2 decimals to prevent any rounding related issues.