horstoeko / zugferd

ZUGFeRD/XRechnung/Factur-X Library
MIT License
142 stars 25 forks source link

[QUESTION] How to use more than 2 decimal places for prices? #59

Closed danielmarschall closed 4 months ago

danielmarschall commented 4 months ago

This document describes at page 14 you can use up to 11 decimal places for describing a unit price that https://www.e-rechnung-bund.de/wp-content/uploads/2023/04/Uebersichtslisten-Eingabefelder-OZG-RE.pdf

But when I set $document->setDocumentPositionNetPrice(1.23456) (or any other price-things like taxes, summations), I always get 2 decimal places in the XML.

        <ram:NetPriceProductTradePrice>
          <ram:ChargeAmount>1.23</ram:ChargeAmount>
        </ram:NetPriceProductTradePrice>

I spent several hours trying to find the place where the code does that rounding. I searching for all round( calls in the zugferd library, no success.

I hope you can help me find the issue, because I really need 5+ decimal places for the prices. Thank you very much!

Profile : XRechnung3

horstoeko commented 4 months ago

Hi @danielmarschall ,

Please Look for ZugferdSettings...

It is described in the README.md and in the Wiki...

Have a nice evening...

danielmarschall commented 4 months ago

Thank you for the quick reply!

Unfortunately, the method ZugferdSettings::setAmountDecimals is very useless, because different prices attributes allow different amount of decimals.

When I do setAmountDecimals(11), then I get the error:

BR-DEC-19 The allowed maximum number of decimals for the VAT category taxable amount (BT-116) is 2.

So, I think what I actually need is to have single unit prices to be with high precision and summarization prices as 2 decimals (since the standard defines it this way).

For now, my only workaround is this (but it is very dirty):

    public function serializeAmountType(XmlSerializationVisitor $visitor, $data, array $type, Context $context)
    {
        $node = $visitor->getDocument()->createTextNode(
            number_format(
                $data->value(),
                // Dirty workaround
                $visitor->getCurrentNode()->localName=='ChargeAmount'?5:2,//ZugferdSettings::getAmountDecimals(),
                ZugferdSettings::getDecimalSeparator(),
                ZugferdSettings::getThousandsSeparator()
            )
        );

Do you have an idea how to make this solution cleaner? Maybe I can try to contribute.

horstoeko commented 4 months ago

Hi,

You are welcome to suggest another solution, even as a PR. However, there was no such requirement in the recent and even in the older past.

danielmarschall commented 4 months ago

Ok, I will think about a solution which is a bit cleaner.

Just to explain why more than 2 decimal places are needed in my case.

Customer pays gross prices ("Brutto-Kunde"). They buy 500 pieces of 0.15 EUR gross price. Price to pay = 500x0.15EUR=75.00EUR (taxes 19% included)

In the ZUGFeRD invoice, since the tax is applied on top of the the sum of the item prices, this means that item prices needs to be net, not gross. So I have to convert the single item prices from gross to net: 0.15 EUR / 1.19 = 0.126050420168067EUR If I round to 0.13, then the customer will pay 500 x 0.13EUR * 1.19 = 77.35EUR (taxes 19% included). So we have a rounding error of -2.35EUR, that's a lot!

danielmarschall commented 4 months ago

Fixed in #62

Example usage:

ZugferdSettings::setAmountDecimals(2);
ZugferdSettings::setAmountDecimals(5, "ram:ChargeAmount");
horstoeko commented 4 months ago

Hi @danielmarschall,

please review #64 ...

Thanks and best regards