From eb0c1a34ea8b950151f1e7d5d7157677f4818e10 Mon Sep 17 00:00:00 2001 From: Julien Van Roy <juvr@odoo.com> Date: Fri, 5 May 2023 15:30:03 +0000 Subject: [PATCH] [FIX] account_edi_ubl_cii: unit prices should not be rounded When unit prices have more than 2 digits, it is currently not reflected in the UBL formats. Consequently, the line amounts are not equal to the unit price * quantity (assume there is no discount, charges or allowance) and it raises validation errors: "Invoice line net amount MUST equal (Invoiced quantity * (Item net price/item price base quantity) + Sum of invoice line charge amount - sum of invoice line allowance amount". To fix this, we no longer round the unit prices. NB: the decimal accuracy should be set in the settings (otherwise, the default is 2 digits for unit prices). See https://docs.peppol.eu/poacc/billing/3.0/bis/#_rounding opw-3290035 task-3302904 closes odoo/odoo#121559 X-original-commit: bd7955934994a949055b6f272e3426cd1bed39c1 Signed-off-by: Laurent Smet <las@odoo.com> Signed-off-by: Julien Van Roy <juvr@odoo.com> --- .../data/ubl_20_templates.xml | 2 +- .../models/account_edi_xml_ubl_20.py | 3 +- .../from_odoo/bis3_out_invoice_rounding.xml | 126 ++++++++++++++++++ .../tests/test_xml_ubl_be.py | 27 ++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_rounding.xml diff --git a/addons/account_edi_ubl_cii/data/ubl_20_templates.xml b/addons/account_edi_ubl_cii/data/ubl_20_templates.xml index ae2a54445ca5..61c96f387431 100644 --- a/addons/account_edi_ubl_cii/data/ubl_20_templates.xml +++ b/addons/account_edi_ubl_cii/data/ubl_20_templates.xml @@ -248,7 +248,7 @@ <t t-set="vals" t-value="vals.get('price_vals', {})"/> <cbc:PriceAmount t-att-currencyID="vals['currency'].name" - t-out="format_float(vals.get('price_amount'), vals.get('currency_dp'))"/> + t-out="format_float(vals.get('price_amount'), vals.get('product_price_dp'))"/> <!-- nbr of item units to which the price applies), i.e.: 1 Dozen = 12 units, not mandatory --> <cbc:BaseQuantity t-att="vals.get('base_quantity_attrs', {})" diff --git a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py index 158e6c91bf11..80af378a1e0b 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py +++ b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py @@ -289,7 +289,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): else: gross_price_subtotal = net_price_subtotal / (1.0 - (line.discount or 0.0) / 100.0) # Price subtotal with discount / quantity: - gross_price_unit = line.currency_id.round((gross_price_subtotal / line.quantity) if line.quantity else 0.0) + gross_price_unit = gross_price_subtotal / line.quantity if line.quantity else 0.0 uom = super()._get_uom_unece_code(line) @@ -299,6 +299,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): # The price of an item, exclusive of VAT, after subtracting item price discount. 'price_amount': gross_price_unit, + 'product_price_dp': self.env['decimal.precision'].precision_get('Product Price'), # The number of item units to which the price applies. # setting to None -> the xml will not comprise the BaseQuantity (it's not mandatory) diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_rounding.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_rounding.xml new file mode 100644 index 000000000000..50c135d002f0 --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_out_invoice_rounding.xml @@ -0,0 +1,126 @@ +<?xml version='1.0' encoding='UTF-8'?> +<Invoice xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" + xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" + xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> + <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + </cbc:CustomizationID> + <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID> + <cbc:ID>___ignore___</cbc:ID> + <cbc:IssueDate>2017-01-01</cbc:IssueDate> + <cbc:DueDate>2017-02-28</cbc:DueDate> + <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> + <cbc:Note>test narration</cbc:Note> + <cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode> + <cbc:BuyerReference>ref_partner_2</cbc:BuyerReference> + <cac:OrderReference> + <cbc:ID>___ignore___</cbc:ID> + </cac:OrderReference> + <cac:AccountingSupplierParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0202239951</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_1</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Chaussée de Namur 40</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_1</cbc:RegistrationName> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_1</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingSupplierParty> + <cac:AccountingCustomerParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0477472701</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_2</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_2</cbc:RegistrationName> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_2</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingCustomerParty> + <cac:PaymentMeans> + <cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode> + <cbc:PaymentID>___ignore___</cbc:PaymentID> + <cac:PayeeFinancialAccount> + <cbc:ID>BE15001559627230</cbc:ID> + </cac:PayeeFinancialAccount> + </cac:PaymentMeans> + <cac:PaymentTerms> + <cbc:Note>30% Advance End of Following Month</cbc:Note> + </cac:PaymentTerms> + <cac:TaxTotal> + <cbc:TaxAmount currencyID="USD">959.07</cbc:TaxAmount> + <cac:TaxSubtotal> + <cbc:TaxableAmount currencyID="USD">4567.00</cbc:TaxableAmount> + <cbc:TaxAmount currencyID="USD">959.07</cbc:TaxAmount> + <cac:TaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:TaxCategory> + </cac:TaxSubtotal> + </cac:TaxTotal> + <cac:LegalMonetaryTotal> + <cbc:LineExtensionAmount currencyID="USD">4567.00</cbc:LineExtensionAmount> + <cbc:TaxExclusiveAmount currencyID="USD">4567.00</cbc:TaxExclusiveAmount> + <cbc:TaxInclusiveAmount currencyID="USD">5526.07</cbc:TaxInclusiveAmount> + <cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount> + <cbc:PayableAmount currencyID="USD">5526.07</cbc:PayableAmount> + </cac:LegalMonetaryTotal> + <cac:InvoiceLine> + <cbc:ID>___ignore___</cbc:ID> + <cbc:InvoicedQuantity unitCode="C62">10000.0</cbc:InvoicedQuantity> + <cbc:LineExtensionAmount currencyID="USD">4567.00</cbc:LineExtensionAmount> + <cac:Item> + <cbc:Description>product_a</cbc:Description> + <cbc:Name>product_a</cbc:Name> + <cac:ClassifiedTaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:ClassifiedTaxCategory> + </cac:Item> + <cac:Price> + <cbc:PriceAmount currencyID="USD">0.4567</cbc:PriceAmount> + </cac:Price> + </cac:InvoiceLine> +</Invoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py index 2f976df64051..56cdd1bf96e5 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py @@ -256,6 +256,33 @@ class TestUBLBE(TestUBLCommon): expected_file='from_odoo/bis3_out_invoice_public_admin.xml', ) + def test_rounding_price_unit(self): + """ OpenPeppol states that: + * All document level amounts shall be rounded to two decimals for accounting + * Invoice line net amount shall be rounded to two decimals + See: https://docs.peppol.eu/poacc/billing/3.0/bis/#_rounding + Do not round the unit prices. It allows to obtain the correct line amounts when prices have more than 2 + digits. + """ + # Set the allowed number of digits for the price_unit + decimal_precision = self.env['decimal.precision'].search([('name', '=', 'Product Price')], limit=1) + self.assertTrue(bool(decimal_precision), "The decimal precision for Product Price is required for this test") + decimal_precision.digits = 4 + + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 10000, + 'price_unit': 0.4567, + 'tax_ids': [(6, 0, self.tax_21.ids)], + } + ], + ) + self._assert_invoice_attachment(invoice, xpaths=None, expected_file='from_odoo/bis3_out_invoice_rounding.xml') #################################################### # Test import -- GitLab