chore: import enterprise addons 19.0 bulk snapshot

This commit is contained in:
1b-agent committed 2026-09-16 10:13:08 +00:00
commit ca4af030d3
39951 files changed
+15570005

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_icp_sbr
from . import test_nl_xaf_export
from . import test_sales_report
from . import test_tax_report_sbr
from . import test_tax_returns
+215
View File
@@ -0,0 +1,215 @@
# pylint: disable=C0326
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import os
from freezegun import freeze_time
from unittest import skipIf
from odoo import fields
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
from odoo.addons.account_reports.tests.account_sales_report_common import AccountSalesReportCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestNlICPSBR(AccountSalesReportCommon):
@classmethod
@AccountSalesReportCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.env.company.write({
'vat': 'NL123456782B90',
'l10n_nl_reports_sbr_ob_nummer': '987654321B09',
})
cls.partner_c = cls.env['res.partner'].create({
'name': 'Partner C',
'country_id': cls.env.ref('base.de').id,
'vat': 'DE123456788',
})
@freeze_time('2019-02-23 18:45')
def test_icp_xbrl_export(self):
# Create a new partner for the representative and link it to the company.
representative = self.env['res.partner'].create({
'company_type': 'company',
'name': 'Fidu NL',
'street': 'Fidu Street 123',
'city': 'Amsterdam',
'zip': '1019',
'country_id': self.env.ref('base.nl').id,
'vat': 'NL123456782B90',
'phone': '+31470123456',
'email': '[email protected]',
})
self.env.company.account_representative_id = representative.id
self.env.user.phone = '+31432112345'
t_tax = self.env.ref(f'account.{self.env.company.id}_btw_X0_ABC_levering')
s_tax = self.env.ref(f'account.{self.env.company.id}_btw_X0_diensten')
product_service = self.env['product.product'].create({
'name': 'product_service',
'lst_price': 750.0,
'taxes_id': [s_tax.id],
'type': 'service',
})
product_triangular = self.env['product.product'].create({
'name': 'product_triangular',
'lst_price': 500.0,
'taxes_id': [t_tax.id],
})
self.init_invoice('out_invoice', partner=self.partner_a, invoice_date=fields.Date.from_string('2019-02-15'), post=True, products=[self.product_a, product_service, product_triangular])
self.init_invoice('out_invoice', partner=self.partner_b, invoice_date=fields.Date.from_string('2019-06-20'), post=True, products=[self.product_a, product_service])
# Credit note for a fictitious invoice in a previous month, tests that negative values are also included in the export
self.init_invoice('out_refund', partner=self.partner_c, invoice_date=fields.Date.from_string('2019-06-30'), post=True, products=[product_triangular])
self.env['account.move.line'].flush_model()
report = self.env.ref('l10n_nl_reports.dutch_icp_report')
date_from = fields.Date.from_string('2019-01-01')
date_to = fields.Date.from_string('2019-12-31')
options = self._generate_options(report, date_from, date_to)
wizard = self.env['l10n_nl_reports.sbr.icp.wizard'] \
.with_context(default_date_from=date_from, default_date_to=date_to, options=options) \
.create({'tax_consultant_number': '123456'})
# This call will add the wanted values in the options
wizard.action_download_xbrl_file()
generated_xbrl = self.get_xml_tree_from_string(self.env['l10n_nl_reports.ec.sales.report.handler'].export_icp_report_to_xbrl(options).get('file_content'))
expected_xbrl = self.get_xml_tree_from_string('''
<xbrli:xbrl xmlns:bd-i="http://www.nltaxonomie.nl/nt20/bd/20251210/dictionary/bd-data" xmlns:xbrldi="http://xbrl.org/2006/xbrldi" xmlns:bd-axes="http://www.nltaxonomie.nl/nt20/bd/20251210/validation/bd-axes" xmlns:link="http://www.xbrl.org/2003/linkbase" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xbrli="http://www.xbrl.org/2003/instance" xmlns:bd-domains="http://www.nltaxonomie.nl/nt20/bd/20251210/validation/bd-domains" xmlns:iso4217="http://www.xbrl.org/2003/iso4217" xml:lang="nl">
<link:schemaRef xlink:type="simple" xlink:href="http://www.nltaxonomie.nl/nt20/bd/20251210/entrypoints/bd-rpt-icp-opgaaf-2026.xsd" />
<xbrli:context id="CD_Opgaaf">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2019-01-01</xbrli:startDate>
<xbrli:endDate>2019-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:context id="Msg_PASN1">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2019-01-01</xbrli:startDate>
<xbrli:endDate>2019-12-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<xbrldi:typedMember dimension="bd-axes:ProfessionalAssociationSerialNumberDimension">
<bd-domains:ProfessionalAssociationSerialNumberDomain>1</bd-domains:ProfessionalAssociationSerialNumberDomain>
</xbrldi:typedMember>
</xbrli:scenario>
</xbrli:context>
<xbrli:context id="ICP_FR_23334175221">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2019-01-01</xbrli:startDate>
<xbrli:endDate>2019-12-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<xbrldi:typedMember dimension="bd-axes:VATNumberDimension">
<bd-domains:VATNumberDomain>23334175221</bd-domains:VATNumberDomain>
</xbrldi:typedMember>
<xbrldi:typedMember dimension="bd-axes:CountryCodeEUDimension">
<bd-domains:CountryCodeEUDomain>FR</bd-domains:CountryCodeEUDomain>
</xbrldi:typedMember>
</xbrli:scenario>
</xbrli:context>
<xbrli:context id="ICP_BE_0477472701">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2019-01-01</xbrli:startDate>
<xbrli:endDate>2019-12-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<xbrldi:typedMember dimension="bd-axes:VATNumberDimension">
<bd-domains:VATNumberDomain>0477472701</bd-domains:VATNumberDomain>
</xbrldi:typedMember>
<xbrldi:typedMember dimension="bd-axes:CountryCodeEUDimension">
<bd-domains:CountryCodeEUDomain>BE</bd-domains:CountryCodeEUDomain>
</xbrldi:typedMember>
</xbrli:scenario>
</xbrli:context>
<xbrli:context id="ICP_DE_123456788">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2019-01-01</xbrli:startDate>
<xbrli:endDate>2019-12-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<xbrldi:typedMember dimension="bd-axes:VATNumberDimension">
<bd-domains:VATNumberDomain>123456788</bd-domains:VATNumberDomain>
</xbrldi:typedMember>
<xbrldi:typedMember dimension="bd-axes:CountryCodeEUDimension">
<bd-domains:CountryCodeEUDomain>DE</bd-domains:CountryCodeEUDomain>
</xbrldi:typedMember>
</xbrli:scenario>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<bd-i:MessageReferenceSupplierICP contextRef="CD_Opgaaf">___ignore___</bd-i:MessageReferenceSupplierICP>
<bd-i:SoftwareVendorAccountNumber contextRef="CD_Opgaaf">swo02770</bd-i:SoftwareVendorAccountNumber>
<bd-i:SoftwarePackageName contextRef="CD_Opgaaf">Odoo</bd-i:SoftwarePackageName>
<bd-i:SoftwarePackageVersion contextRef="CD_Opgaaf">___ignore___</bd-i:SoftwarePackageVersion>
<bd-i:DateTimeCreation contextRef="CD_Opgaaf">201902231845</bd-i:DateTimeCreation>
<bd-i:ProfessionalAssociationForTaxServiceProvidersName contextRef="Msg_PASN1">NBA</bd-i:ProfessionalAssociationForTaxServiceProvidersName>
<bd-i:TaxConsultantNumber contextRef="CD_Opgaaf">123456</bd-i:TaxConsultantNumber>
<bd-i:ContactInitials contextRef="CD_Opgaaf">BIAA</bd-i:ContactInitials>
<bd-i:ContactPrefix contextRef="CD_Opgaaf">I am</bd-i:ContactPrefix>
<bd-i:ContactSurname contextRef="CD_Opgaaf">accountman!</bd-i:ContactSurname>
<bd-i:ContactTelephoneNumber contextRef="CD_Opgaaf">+31432112345</bd-i:ContactTelephoneNumber>
<bd-i:VATIdentificationNumberNLFiscalEntityDivision contextRef="CD_Opgaaf">123456782B90</bd-i:VATIdentificationNumberNLFiscalEntityDivision>
<bd-i:SuppliesAmount unitRef="EUR" decimals="INF" contextRef="ICP_FR_23334175221">1000</bd-i:SuppliesAmount>
<bd-i:SuppliesAmount unitRef="EUR" decimals="INF" contextRef="ICP_BE_0477472701">1000</bd-i:SuppliesAmount>
<bd-i:ServicesAmount unitRef="EUR" decimals="INF" contextRef="ICP_FR_23334175221">750</bd-i:ServicesAmount>
<bd-i:ServicesAmount unitRef="EUR" decimals="INF" contextRef="ICP_BE_0477472701">750</bd-i:ServicesAmount>
<bd-i:ABCSuppliesAmount unitRef="EUR" decimals="INF" contextRef="ICP_FR_23334175221">500</bd-i:ABCSuppliesAmount>
<bd-i:ABCSuppliesAmount unitRef="EUR" decimals="INF" contextRef="ICP_DE_123456788">-500</bd-i:ABCSuppliesAmount>
</xbrli:xbrl>
''')
self.assertXmlTreeEqual(generated_xbrl, expected_xbrl)
@tagged('external_l10n', 'post_install', '-at_install', '-standard', 'external')
@skipIf(not os.getenv("NL_SBR_CERT"), "No SBR certificate")
class TestNlSBRFlow(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.env.company.write({
'vat': 'NL123456782B90',
'l10n_nl_reports_sbr_ob_nummer': '987654321B09',
})
cls.NL_SBR_CERT = os.getenv("NL_SBR_CERT")
cls.NL_SBR_PWD = os.getenv("NL_SBR_PWD")
def test_sbr_flow(self):
# Load the certificate and key in the company
certificate = self.env['certificate.certificate'].create({
'name': 'SBR NL certificate',
'content': self.NL_SBR_CERT.encode(),
'pkcs12_password': self.NL_SBR_PWD,
'company_id': self.env.company.id,
})
self.assertTrue(certificate.private_key_id)
self.env.company.l10n_nl_reports_sbr_cert_id = certificate
date_from = fields.Date.from_string('2019-01-01')
date_to = fields.Date.from_string('2019-12-31')
report = self.env.ref('l10n_nl_reports.dutch_icp_report')
options = self._generate_options(report, date_from, date_to)
wizard = self.env['l10n_nl_reports.sbr.icp.wizard']\
.with_context(default_date_from=date_from, default_date_to=date_to, options=options)\
.create({'is_test': True})
res = wizard.send_xbrl()
self.assertTrue(res)
+716
View File
@@ -0,0 +1,716 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from freezegun import freeze_time
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
from odoo import fields
from odoo.tests import tagged
from odoo.tests.common import test_xsd
class TestNlXafExportCommon(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.env.company.write({
'vat': 'NL123456782B90',
})
products = [cls.product_a, cls.product_b]
# Verify if the country code validation when generating a XAF report at the very least allows
# the Netherlands itself on a partner when no XSD is downloaded
cls.partner_a.write({'country_id': cls.env.ref('base.nl').id})
# Create three invoices, one refund and one bill in 2019
partner_a_invoice1 = cls.init_invoice('out_invoice', products=products)
partner_a_invoice2 = cls.init_invoice('out_invoice', products=products)
partner_a_invoice3 = cls.init_invoice('out_invoice', products=products)
partner_a_refund = cls.init_invoice('out_refund', products=products)
partner_b_bill = cls.init_invoice('in_invoice', products=products, partner=cls.partner_b)
# Create one invoice for partner B in 2018
partner_b_invoice1 = cls.init_invoice('out_invoice', products=products, partner=cls.partner_b, invoice_date=fields.Date.from_string('2018-01-01'))
# Create one MISC entry in 2018
bank_account_id = cls.company_data['default_journal_bank'].default_account_id.id
receivable_account_id = cls.company_data['default_account_receivable'].id
partner_a_misc = cls.env['account.move'].create({
'move_type': 'entry',
'date': fields.Date.from_string('2018-01-01'),
'journal_id': cls.company_data['default_journal_misc'].id,
'line_ids': [
(0, 0, {'debit': 100.0, 'credit': 0.0, 'account_id': receivable_account_id, 'partner_id': cls.partner_a.id}),
(0, 0, {'debit': 0.0, 'credit': 100.0, 'account_id': bank_account_id, 'partner_id': cls.partner_a.id}),
],
})
# init_invoice has hardcoded 2019 year's date, we are resetting it to current year's one.
(partner_a_invoice1 + partner_a_invoice2 + partner_a_invoice3 + partner_b_invoice1 + partner_a_refund + partner_b_bill + partner_a_misc).action_post()
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestNlXafExport(TestNlXafExportCommon):
@freeze_time('2019-12-31')
def test_xaf_export(self):
report = self.env.ref('account_reports.general_ledger_report')
options = self._generate_options(report, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
expected_xaf = self.get_xml_tree_from_string('''
<auditfile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.auditfiles.nl/XAF/3.2">
<header>
<fiscalYear>2019</fiscalYear>
<startDate>2019-01-01</startDate>
<endDate>2019-12-31</endDate>
<curCode>EUR</curCode>
<dateCreated>2019-12-31</dateCreated>
<softwareDesc>Odoo</softwareDesc>
<softwareVersion>___ignore___</softwareVersion>
</header>
<company>
<companyName>company_1_data</companyName>
<taxRegistrationCountry>NL</taxRegistrationCountry>
<taxRegIdent>NL123456782B90</taxRegIdent>
<streetAddress>
<country>NL</country>
</streetAddress>
<customersSuppliers>
<customerSupplier>
<custSupID>___ignore___</custSupID>
<custSupName>partner_a</custSupName>
<custSupTp>S</custSupTp>
<streetAddress>
</streetAddress>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</customerSupplier><customerSupplier>
<custSupID>___ignore___</custSupID>
<custSupName>partner_b</custSupName>
<custSupTp>B</custSupTp>
<streetAddress>
</streetAddress>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</customerSupplier>
</customersSuppliers>
<generalLedger>
<ledgerAccount>
<accID>103001</accID>
<accDesc>Bank</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>110000</accID>
<accDesc>Debtors</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>110001</accID>
<accDesc>Debtors (copy)</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>130001</accID>
<accDesc>Creditors (copy)</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>150000</accID>
<accDesc>Deferred VAT high rate</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>152000</accID>
<accDesc>Pre-tax high</accDesc>
<accTp>B</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>400100.1</accID>
<accDesc>Gross wages</accDesc>
<accTp>P</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>800100</accID>
<accDesc>Turnover NL trade goods 1</accDesc>
<accTp>P</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount><ledgerAccount>
<accID>800100.1</accID>
<accDesc>Turnover NL trade goods 1</accDesc>
<accTp>P</accTp>
<changeInfo>
<userID>___ignore___</userID>
<changeDateTime>___ignore___</changeDateTime>
<changeDescription>___ignore___</changeDescription>
</changeInfo>
</ledgerAccount>
</generalLedger>
<vatCodes>
<vatCode>
<vatID>___ignore___</vatID>
<vatDesc>21% (copy)</vatDesc>
</vatCode><vatCode>
<vatID>___ignore___</vatID>
<vatDesc>21% ST</vatDesc>
</vatCode><vatCode>
<vatID>___ignore___</vatID>
<vatDesc>21% ST (copy)</vatDesc>
</vatCode>
</vatCodes>
<periods>
<period>
<periodNumber>01</periodNumber>
<periodDesc>January 2019</periodDesc>
<startDatePeriod>2019-01-01</startDatePeriod>
<endDatePeriod>2019-01-31</endDatePeriod>
</period><period>
<periodNumber>02</periodNumber>
<periodDesc>February 2019</periodDesc>
<startDatePeriod>2019-02-01</startDatePeriod>
<endDatePeriod>2019-02-28</endDatePeriod>
</period><period>
<periodNumber>03</periodNumber>
<periodDesc>March 2019</periodDesc>
<startDatePeriod>2019-03-01</startDatePeriod>
<endDatePeriod>2019-03-31</endDatePeriod>
</period><period>
<periodNumber>04</periodNumber>
<periodDesc>April 2019</periodDesc>
<startDatePeriod>2019-04-01</startDatePeriod>
<endDatePeriod>2019-04-30</endDatePeriod>
</period><period>
<periodNumber>05</periodNumber>
<periodDesc>May 2019</periodDesc>
<startDatePeriod>2019-05-01</startDatePeriod>
<endDatePeriod>2019-05-31</endDatePeriod>
</period><period>
<periodNumber>06</periodNumber>
<periodDesc>June 2019</periodDesc>
<startDatePeriod>2019-06-01</startDatePeriod>
<endDatePeriod>2019-06-30</endDatePeriod>
</period><period>
<periodNumber>07</periodNumber>
<periodDesc>July 2019</periodDesc>
<startDatePeriod>2019-07-01</startDatePeriod>
<endDatePeriod>2019-07-31</endDatePeriod>
</period><period>
<periodNumber>08</periodNumber>
<periodDesc>August 2019</periodDesc>
<startDatePeriod>2019-08-01</startDatePeriod>
<endDatePeriod>2019-08-31</endDatePeriod>
</period><period>
<periodNumber>09</periodNumber>
<periodDesc>September 2019</periodDesc>
<startDatePeriod>2019-09-01</startDatePeriod>
<endDatePeriod>2019-09-30</endDatePeriod>
</period><period>
<periodNumber>10</periodNumber>
<periodDesc>October 2019</periodDesc>
<startDatePeriod>2019-10-01</startDatePeriod>
<endDatePeriod>2019-10-31</endDatePeriod>
</period><period>
<periodNumber>11</periodNumber>
<periodDesc>November 2019</periodDesc>
<startDatePeriod>2019-11-01</startDatePeriod>
<endDatePeriod>2019-11-30</endDatePeriod>
</period><period>
<periodNumber>12</periodNumber>
<periodDesc>December 2019</periodDesc>
<startDatePeriod>2019-12-01</startDatePeriod>
<endDatePeriod>2019-12-31</endDatePeriod>
</period>
</periods>
<openingBalance>
<opBalDate>2019-01-01</opBalDate>
<linesCount>5</linesCount>
<totalDebit>1552.0</totalDebit>
<totalCredit>352.0</totalCredit>
<obLine>
<nr>___ignore___</nr>
<accID>110000</accID>
<amnt>100.0</amnt>
<amntTp>D</amntTp>
</obLine><obLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<amnt>252.0</amnt>
<amntTp>C</amntTp>
</obLine><obLine>
<nr>___ignore___</nr>
<accID>103001</accID>
<amnt>100.0</amnt>
<amntTp>C</amntTp>
</obLine><obLine>
<nr>___ignore___</nr>
<accID>110001</accID>
<amnt>1452.0</amnt>
<amntTp>D</amntTp>
</obLine>
</openingBalance>
<transactions>
<linesCount>25</linesCount>
<totalDebit>7137.6</totalDebit>
<totalCredit>7137.6</totalCredit>
<journal>
<jrnID>INV</jrnID>
<desc>Sales</desc>
<jrnTp>S</jrnTp>
<transaction>
<nr>___ignore___</nr>
<desc>INV/2019/00001</desc>
<periodNumber>01</periodNumber>
<trDt>2019-01-01</trDt>
<amnt>1494.0</amnt>
<trLine>
<nr>___ignore___</nr>
<accID>800100</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_a</desc>
<amnt>1000.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-1000.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>800100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_b</desc>
<amnt>200.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-200.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST</desc>
<amnt>252.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-252.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST (copy)</desc>
<amnt>42.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-42.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>110000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>INV/2019/00001</desc>
<amnt>1494.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>1494.0</curAmnt>
</currency>
</trLine>
</transaction><transaction>
<nr>___ignore___</nr>
<desc>INV/2019/00002</desc>
<periodNumber>01</periodNumber>
<trDt>2019-01-01</trDt>
<amnt>1494.0</amnt>
<trLine>
<nr>___ignore___</nr>
<accID>800100</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_a</desc>
<amnt>1000.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00002</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-1000.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>800100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_b</desc>
<amnt>200.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00002</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-200.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST</desc>
<amnt>252.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00002</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-252.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST (copy)</desc>
<amnt>42.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00002</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-42.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>110000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>INV/2019/00002</desc>
<amnt>1494.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00002</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>1494.0</curAmnt>
</currency>
</trLine>
</transaction><transaction>
<nr>___ignore___</nr>
<desc>INV/2019/00003</desc>
<periodNumber>01</periodNumber>
<trDt>2019-01-01</trDt>
<amnt>1494.0</amnt>
<trLine>
<nr>___ignore___</nr>
<accID>800100</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_a</desc>
<amnt>1000.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00003</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-1000.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>800100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_b</desc>
<amnt>200.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00003</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-200.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST</desc>
<amnt>252.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00003</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-252.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST (copy)</desc>
<amnt>42.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00003</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-42.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>110000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>INV/2019/00003</desc>
<amnt>1494.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>INV/2019/00003</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>1494.0</curAmnt>
</currency>
</trLine>
</transaction><transaction>
<nr>___ignore___</nr>
<desc>RINV/2019/00001</desc>
<periodNumber>01</periodNumber>
<trDt>2019-01-01</trDt>
<amnt>1494.0</amnt>
<trLine>
<nr>___ignore___</nr>
<accID>800100</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_a</desc>
<amnt>1000.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>RINV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>1000.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>800100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_b</desc>
<amnt>200.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>RINV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>200.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST</desc>
<amnt>252.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>RINV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>252.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>150000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% ST (copy)</desc>
<amnt>42.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>RINV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>42.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>110000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc></desc>
<amnt>1494.0</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>RINV/2019/00001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-1494.0</curAmnt>
</currency>
</trLine>
</transaction>
</journal><journal>
<jrnID>BILL</jrnID>
<desc>Purchases</desc>
<jrnTp>P</jrnTp>
<transaction>
<nr>___ignore___</nr>
<desc>BILL/2019/01/0001</desc>
<periodNumber>01</periodNumber>
<trDt>2019-01-01</trDt>
<amnt>1161.6</amnt>
<trLine>
<nr>___ignore___</nr>
<accID>400100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_a</desc>
<amnt>800.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>BILL/2019/01/0001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>800.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>400100.1</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>product_b</desc>
<amnt>160.0</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>BILL/2019/01/0001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>160.0</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>152000</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>21% (copy)</desc>
<amnt>201.6</amnt>
<amntTp>D</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>BILL/2019/01/0001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>201.6</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>130001</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>installment #1</desc>
<amnt>348.48</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>BILL/2019/01/0001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-348.48</curAmnt>
</currency>
</trLine><trLine>
<nr>___ignore___</nr>
<accID>130001</accID>
<docRef>/</docRef>
<effDate>2019-01-01</effDate>
<desc>installment #2</desc>
<amnt>813.12</amnt>
<amntTp>C</amntTp>
<custSupID>___ignore___</custSupID>
<invRef>BILL/2019/01/0001</invRef>
<currency>
<curCode>EUR</curCode>
<curAmnt>-813.12</curAmnt>
</currency>
</trLine>
</transaction>
</journal>
</transactions>
</company>
</auditfile>
''')
with self.enter_registry_test_mode():
# Set the batch size to 10 to make sure the generator will iterate more than once.
self.env['ir.config_parameter'].set_param('l10n_nl_reports.general_ledger_batch_size', 10)
xaf_stream = self.env[report.custom_handler_model_name].l10n_nl_reports_get_xaf(options).get('file_content')
generated_xaf = self.get_xml_tree_from_string(b''.join(xaf_stream))
self.assertXmlTreeEqual(generated_xaf, expected_xaf)
@tagged('external_l10n', 'post_install', '-at_install', '-standard', 'external')
class TestNlXafExportXmlValidity(TestNlXafExportCommon):
@test_xsd(url='https://www.softwarepakketten.nl/upload/auditfiles/xaf/20140402_AuditfileFinancieelVersie_3_2.zip')
def test_xml_validity(self):
report = self.env.ref('account_reports.general_ledger_report')
options = self._generate_options(report, fields.Date.from_string('2019-01-01'), fields.Date.from_string('2019-12-31'))
with self.enter_registry_test_mode():
xaf_stream = self.env[report.custom_handler_model_name].l10n_nl_reports_get_xaf(options).get('file_content')
generated_xaf = self.get_xml_tree_from_string(b''.join(xaf_stream))
return generated_xaf
+104
View File
@@ -0,0 +1,104 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from freezegun import freeze_time
from odoo import Command
from odoo.tests import tagged
from odoo.tools.misc import NON_BREAKING_SPACE
from odoo.addons.account_reports.tests.account_sales_report_common import AccountSalesReportCommon
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestNlEcSalesReport(AccountSalesReportCommon):
@classmethod
@AccountSalesReportCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.report = cls.env.ref('l10n_nl_reports.dutch_icp_report')
cls.company.vat = 'NL123456782B90'
cls.partner_b.update({
'country_id': cls.env.ref('base.de').id,
'vat': "DE123456788",
})
cls.goods_tax = cls.env.ref(f'account.{cls.env.company.id}_btw_X0_producten')
cls.goods_tag = cls.env.ref('l10n_nl.tax_report_rub_3bg_tag')
cls.services_tax = cls.env.ref(f'account.{cls.env.company.id}_btw_X0_diensten')
cls.services_tag = cls.env.ref('l10n_nl.tax_report_rub_3bs_tag')
cls.triangular_tax = cls.env.ref(f'account.{cls.env.company.id}_btw_X0_ABC_levering')
cls.triangular_tag = cls.env.ref('l10n_nl.tax_report_rub_3bt_tag')
def _create_invoice(self, invoice_date):
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_b.id,
'invoice_date': invoice_date,
'invoice_line_ids': [
Command.create({
'name': 'Goods Line',
'price_unit': 1799.0,
'product_id': self.product_a.id,
'quantity': 1.0,
'account_id': self.company_data['default_account_revenue'].id,
'tax_ids': [Command.set(self.goods_tax.ids)],
}),
Command.create({
'name': 'Services Line',
'price_unit': 200.0,
'product_id': False,
'quantity': 1.0,
'account_id': self.company_data['default_account_revenue'].id,
'tax_ids': [Command.set(self.services_tax.ids)],
}),
Command.create({
'name': 'Triangular Line',
'price_unit': 300.0,
'product_id': False,
'quantity': 1.0,
'account_id': self.company_data['default_account_revenue'].id,
'tax_ids': [Command.set(self.triangular_tax.ids)],
}),
]
})
invoice.action_post()
return invoice
@freeze_time('2019-12-31')
def test_ec_sales_report(self):
self._create_invoice('2019-12-01')
options = self.report.get_options({'date': {'mode': 'range', 'filter': 'this_month'}})
self.assertLinesValues(
self.report._get_lines(options),
# Line Name Amount Product Amount Services Amount Triangular Amount_total
[ 0, 4, 5, 6, 7], # noqa: E201
[
( self.partner_b.name, f'1,799.00{NON_BREAKING_SPACE}€', f'200.00{NON_BREAKING_SPACE}€', f'300.00{NON_BREAKING_SPACE}€', f'2,299.00{NON_BREAKING_SPACE}€'),
( 'Total', f'1,799.00{NON_BREAKING_SPACE}€', f'200.00{NON_BREAKING_SPACE}€', f'300.00{NON_BREAKING_SPACE}€', f'2,299.00{NON_BREAKING_SPACE}€'),
],
options,
)
def test_l10n_nl_ec_sales_report_use_taxes_instead_of_tags(self):
""" Ensure that if the tax report line is missing, making impossible to retrieve associated tags),
taxes are used as fallback to retrieve goods, services and triangular sales """
self._create_invoice('2025-01-01')
self.goods_tag.unlink()
options = self._generate_options(self.report, '2025-01-01', '2025-12-31')
expected_sales_report_taxes = {
'goods': [self.goods_tax.id],
'services': [self.services_tax.id],
'triangular': [self.triangular_tax.id],
'use_taxes_instead_of_tags': True,
}
self.assertEqual(options['sales_report_taxes'], expected_sales_report_taxes)
self.assertLinesValues(
self.report._get_lines(options),
# Line Name [ Amount Product ] [ Amount Services ] [ Amount Triangular ] [ Amount_total ]
[0, 4, 5, 6, 7],
[
(self.partner_b.name, 1799, 200, 300, 2299),
('Total', 1799, 200, 300, 2299),
],
options,
)
@@ -0,0 +1,391 @@
# pylint: disable=C0326
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import os
from freezegun import freeze_time
from unittest import skipIf
from odoo import Command, fields
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
from odoo.exceptions import RedirectWarning, UserError
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestNlTaxReportSBR(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.env.company.write({
'vat': 'NL123456782B90',
'l10n_nl_reports_sbr_ob_nummer': '987654321B09',
})
products = [cls.product_a, cls.product_b]
cls.init_invoice('out_invoice', products=products, invoice_date='2026-02-01').action_post()
cls.init_invoice('in_invoice', products=products, invoice_date='2026-02-01').action_post()
@classmethod
def _get_move_create_data(cls, move_data, line_data):
return {
'partner_id': cls.partner.id,
'invoice_date': '2025-04-15',
'journal_id': cls.company_data['default_journal_sale'].id,
'invoice_line_ids': [
Command.create({
'name': 'Test',
'quantity': 1,
**line_data,
}),
],
**move_data,
}
@freeze_time('2026-02-23 18:45')
def test_xbrl_export(self):
# Create a new partner for the representative and link it to the company.
representative = self.env['res.partner'].create({
'company_type': 'company',
'name': 'Fidu NL',
'street': 'Fidu Street 123',
'city': 'Amsterdam',
'zip': '1019',
'country_id': self.env.ref('base.nl').id,
'vat': 'NL123456782B90',
'phone': '+31470123456',
'email': '[email protected]',
})
self.env.company.account_representative_id = representative.id
self.env.user.phone = '+31432112345'
report = self.env.ref('l10n_nl.tax_report')
date_from = fields.Date.from_string('2026-01-01')
date_to = fields.Date.from_string('2026-12-31')
options = self._generate_options(report, date_from, date_to)
wizard = self.env['l10n_nl_reports.sbr.tax.report.wizard']\
.with_context(default_date_from=date_from, default_date_to=date_to, options=options)\
.create({'tax_consultant_number': '123456'})
# This call will add the wanted values in the options
wizard.action_download_xbrl_file()
generated_xbrl = self.get_xml_tree_from_string(self.env['l10n_nl_reports.tax.report.handler'].export_tax_report_to_xbrl(options).get('file_content'))
expected_xbrl = self.get_xml_tree_from_string('''
<xbrli:xbrl xmlns:bd-i="http://www.nltaxonomie.nl/nt20/bd/20251210/dictionary/bd-data" xmlns:bd-t="http://www.nltaxonomie.nl/nt20/bd/20251210/dictionary/bd-tuples" xmlns:xbrldi="http://xbrl.org/2006/xbrldi" xmlns:bd-axes="http://www.nltaxonomie.nl/nt20/bd/20251210/validation/bd-axes" xmlns:bd-domains="http://www.nltaxonomie.nl/nt20/bd/20251210/validation/bd-domains" xmlns:link="http://www.xbrl.org/2003/linkbase" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:iso4217="http://www.xbrl.org/2003/iso4217" xmlns:xbrli="http://www.xbrl.org/2003/instance" xml:lang="nl">
<link:schemaRef xlink:type="simple" xlink:href="http://www.nltaxonomie.nl/nt20/bd/20251210/entrypoints/bd-rpt-ob-aangifte-2026.xsd"/>
<xbrli:context id="Msg">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2026-01-01</xbrli:startDate>
<xbrli:endDate>2026-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:context id="Msg_PASN1">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2026-01-01</xbrli:startDate>
<xbrli:endDate>2026-12-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<xbrldi:typedMember dimension="bd-axes:ProfessionalAssociationSerialNumberDimension">
<bd-domains:ProfessionalAssociationSerialNumberDomain>1</bd-domains:ProfessionalAssociationSerialNumberDomain>
</xbrldi:typedMember>
</xbrli:scenario>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<bd-i:ContactInitials contextRef="Msg">BIAA</bd-i:ContactInitials>
<bd-i:ContactPrefix contextRef="Msg">I am</bd-i:ContactPrefix>
<bd-i:ContactSurname contextRef="Msg">accountman!</bd-i:ContactSurname>
<bd-i:ContactTelephoneNumber contextRef="Msg">+31432112345</bd-i:ContactTelephoneNumber>
<bd-i:ContactType contextRef="Msg">BPL</bd-i:ContactType>
<bd-i:DateTimeCreation contextRef="Msg">202602231845</bd-i:DateTimeCreation>
<bd-i:MessageReferenceSupplierVAT contextRef="Msg">___ignore___</bd-i:MessageReferenceSupplierVAT>
<bd-i:ProfessionalAssociationForTaxServiceProvidersName contextRef="Msg_PASN1">NBA</bd-i:ProfessionalAssociationForTaxServiceProvidersName>
<bd-i:SoftwarePackageName contextRef="Msg">Odoo</bd-i:SoftwarePackageName>
<bd-i:SoftwarePackageVersion contextRef="Msg">___ignore___</bd-i:SoftwarePackageVersion>
<bd-i:SoftwareVendorAccountNumber contextRef="Msg">swo02770</bd-i:SoftwareVendorAccountNumber>
<bd-i:TaxConsultantNumber contextRef="Msg">123456</bd-i:TaxConsultantNumber>
<bd-i:InstallationDistanceSalesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:InstallationDistanceSalesWithinTheEC>
<bd-i:SuppliesServicesNotTaxed decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesServicesNotTaxed>
<bd-i:SuppliesToCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesToCountriesOutsideTheEC>
<bd-i:SuppliesToCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesToCountriesWithinTheEC>
<bd-i:TaxedTurnoverPrivateUse decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverPrivateUse>
<bd-i:TaxedTurnoverSuppliesServicesGeneralTariff decimals="INF" contextRef="Msg" unitRef="EUR">1200</bd-i:TaxedTurnoverSuppliesServicesGeneralTariff>
<bd-i:TaxedTurnoverSuppliesServicesOtherRates decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverSuppliesServicesOtherRates>
<bd-i:TaxedTurnoverSuppliesServicesReducedTariff decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverSuppliesServicesReducedTariff>
<bd-i:TurnoverFromTaxedSuppliesFromCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverFromTaxedSuppliesFromCountriesOutsideTheEC>
<bd-i:TurnoverFromTaxedSuppliesFromCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverFromTaxedSuppliesFromCountriesWithinTheEC>
<bd-i:TurnoverSuppliesServicesByWhichVATTaxationIsTransferred decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverSuppliesServicesByWhichVATTaxationIsTransferred>
<bd-i:ValueAddedTaxOnInput decimals="INF" contextRef="Msg" unitRef="EUR">235</bd-i:ValueAddedTaxOnInput>
<bd-i:ValueAddedTaxOnSuppliesFromCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxOnSuppliesFromCountriesOutsideTheEC>
<bd-i:ValueAddedTaxOnSuppliesFromCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxOnSuppliesFromCountriesWithinTheEC>
<bd-i:ValueAddedTaxOwed decimals="INF" contextRef="Msg" unitRef="EUR">294</bd-i:ValueAddedTaxOwed>
<bd-i:ValueAddedTaxOwedToBePaidBack decimals="INF" contextRef="Msg" unitRef="EUR">59</bd-i:ValueAddedTaxOwedToBePaidBack>
<bd-i:ValueAddedTaxPrivateUse decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxPrivateUse>
<bd-i:ValueAddedTaxSuppliesServicesByWhichVATTaxationIsTransferred decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesByWhichVATTaxationIsTransferred>
<bd-i:ValueAddedTaxSuppliesServicesGeneralTariff decimals="INF" contextRef="Msg" unitRef="EUR">294</bd-i:ValueAddedTaxSuppliesServicesGeneralTariff>
<bd-i:ValueAddedTaxSuppliesServicesOtherRates decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesOtherRates>
<bd-i:ValueAddedTaxSuppliesServicesReducedTariff decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesReducedTariff>
</xbrli:xbrl>
''')
self.assertXmlTreeEqual(generated_xbrl, expected_xbrl)
def test_suppletie_xbrl_export_and_closing_move(self):
company_id = self.env.company.id
tax_21_g_sale = self.env['account.tax'].search(
[('type_tax_use', '=', 'sale'), ('name', '=', '21% ST'), ('company_id', '=', company_id)],
limit=1,
)
self.env['account.move'].create([
self._get_move_create_data(
move_data={'move_type': 'out_invoice'},
line_data={'price_unit': 1000, 'tax_ids': [Command.link(tax_21_g_sale.id)]},
),
])._post()
# Create the regular VAT return for April
april_return = self.env['account.return'].create({
'name': "VAT April 2025",
'date_from': '2025-04-01',
'date_to': '2025-04-30',
'type_id': self.env.ref('l10n_nl_reports.nl_tax_return_type').id,
'company_id': company_id,
})
with self.allow_pdf_render():
april_return.action_validate(bypass_failing_tests=True)
self.assertRecordValues(
april_return.closing_move_ids.line_ids,
[
{
'account_id': tax_21_g_sale.repartition_line_ids.account_id.id,
'balance': 210,
},
{
'account_id': tax_21_g_sale.tax_group_id.tax_receivable_account_id.id,
'balance': -210,
},
],
)
# Reset tax lock date
self.company.tax_lock_date = False
# Add another entry for April
self.env['account.move'].create([
self._get_move_create_data(
move_data={'move_type': 'out_invoice'},
line_data={'price_unit': 250, 'tax_ids': [Command.link(tax_21_g_sale.id)]},
),
])._post()
# Create the suppletie VAT return for April
april_suppletie_return = self.env['account.return'].create({
'name': "VAT Correction April 2025",
'date_from': '2025-04-01',
'date_to': '2025-04-30',
'type_id': self.env.ref('l10n_nl_reports.nl_tax_correction_return_type').id,
'company_id': company_id,
})
with self.allow_pdf_render():
april_suppletie_return.action_validate(bypass_failing_tests=True)
# Check whether the closing entry is correct
self.assertRecordValues(
april_suppletie_return.closing_move_ids.line_ids,
[
{
'account_id': tax_21_g_sale.repartition_line_ids.account_id.id,
'balance': 52.5,
},
{
'account_id': self.env.company.l10n_nl_rounding_difference_profit_account_id.id,
'balance': -0.5,
},
# TODO: Simplify next two lines when the account is split up: task-5045769
{
'account_id': tax_21_g_sale.tax_group_id.tax_receivable_account_id.id,
'balance': 210.0,
},
{
'account_id': tax_21_g_sale.tax_group_id.tax_receivable_account_id.id,
'balance': -262.0,
},
],
)
# Generate the XBRL to check whether it is correct
report_options = april_suppletie_return._get_closing_report_options()
wizard = self.env['l10n_nl_reports.sbr.tax.report.wizard'].with_context(
default_date_from=report_options['date']['date_from'],
default_date_to=report_options['date']['date_to'],
options=report_options,
).create({})
wizard.action_download_xbrl_file()
generated_xbrl = self.get_xml_tree_from_string(self.env['l10n_nl_reports.tax.report.handler'].export_tax_report_to_xbrl(report_options).get('file_content'))
expected_xbrl = self.get_xml_tree_from_string('''
<xbrli:xbrl xmlns:bd-i="http://www.nltaxonomie.nl/nt19/bd/20241211/dictionary/bd-data" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:link="http://www.xbrl.org/2003/linkbase" xmlns:xbrli="http://www.xbrl.org/2003/instance" xmlns:iso4217="http://www.xbrl.org/2003/iso4217" xmlns:bd-t="http://www.nltaxonomie.nl/nt19/bd/20241211/dictionary/bd-tuples" xml:lang="nl">
<link:schemaRef xlink:type="simple" xlink:href="http://www.nltaxonomie.nl/nt19/bd/20241211/entrypoints/bd-rpt-ob-suppletie-2025.xsd"/>
<xbrli:context id="Msg">
<xbrli:entity>
<xbrli:identifier scheme="www.belastingdienst.nl/omzetbelastingnummer">987654321B09</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2025-04-01</xbrli:startDate>
<xbrli:endDate>2025-04-30</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<bd-i:ContactInitials contextRef="Msg">___ignore___</bd-i:ContactInitials>
<bd-i:ContactPrefix contextRef="Msg">___ignore___</bd-i:ContactPrefix>
<bd-i:ContactSurname contextRef="Msg">___ignore___</bd-i:ContactSurname>
<bd-i:ContactType contextRef="Msg">___ignore___</bd-i:ContactType>
<bd-i:DateTimeCreation contextRef="Msg">___ignore___</bd-i:DateTimeCreation>
<bd-i:InstallationDistanceSalesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:InstallationDistanceSalesWithinTheEC>
<bd-i:MessageReferenceSupplierVAT contextRef="Msg">___ignore___</bd-i:MessageReferenceSupplierVAT>
<bd-i:SoftwarePackageName contextRef="Msg">Odoo</bd-i:SoftwarePackageName>
<bd-i:SoftwarePackageVersion contextRef="Msg">___ignore___</bd-i:SoftwarePackageVersion>
<bd-i:SoftwareVendorAccountNumber contextRef="Msg">swo02770</bd-i:SoftwareVendorAccountNumber>
<bd-i:SuppliesServicesNotTaxed decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesServicesNotTaxed>
<bd-i:SuppliesToCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesToCountriesOutsideTheEC>
<bd-i:SuppliesToCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:SuppliesToCountriesWithinTheEC>
<bd-i:TaxedTurnoverPrivateUse decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverPrivateUse>
<bd-i:TaxedTurnoverSuppliesServicesGeneralTariff decimals="INF" contextRef="Msg" unitRef="EUR">1250</bd-i:TaxedTurnoverSuppliesServicesGeneralTariff>
<bd-i:TaxedTurnoverSuppliesServicesOtherRates decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverSuppliesServicesOtherRates>
<bd-i:TaxedTurnoverSuppliesServicesReducedTariff decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TaxedTurnoverSuppliesServicesReducedTariff>
<bd-i:TurnoverFromTaxedSuppliesFromCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverFromTaxedSuppliesFromCountriesOutsideTheEC>
<bd-i:TurnoverFromTaxedSuppliesFromCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverFromTaxedSuppliesFromCountriesWithinTheEC>
<bd-i:TurnoverSuppliesServicesByWhichVATTaxationIsTransferred decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:TurnoverSuppliesServicesByWhichVATTaxationIsTransferred>
<bd-i:ValueAddedTaxAmountTotalNew decimals="INF" contextRef="Msg" unitRef="EUR">262</bd-i:ValueAddedTaxAmountTotalNew>
<bd-i:ValueAddedTaxAmountTotalOld decimals="INF" contextRef="Msg" unitRef="EUR">210</bd-i:ValueAddedTaxAmountTotalOld>
<bd-i:ValueAddedTaxOnInput decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxOnInput>
<bd-i:ValueAddedTaxOnSuppliesFromCountriesOutsideTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxOnSuppliesFromCountriesOutsideTheEC>
<bd-i:ValueAddedTaxOnSuppliesFromCountriesWithinTheEC decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxOnSuppliesFromCountriesWithinTheEC>
<bd-i:ValueAddedTaxOwed decimals="INF" contextRef="Msg" unitRef="EUR">262</bd-i:ValueAddedTaxOwed>
<bd-i:ValueAddedTaxPrivateUse decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxPrivateUse>
<bd-i:ValueAddedTaxSuppliesServicesByWhichVATTaxationIsTransferred decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesByWhichVATTaxationIsTransferred>
<bd-i:ValueAddedTaxSuppliesServicesGeneralTariff decimals="INF" contextRef="Msg" unitRef="EUR">262</bd-i:ValueAddedTaxSuppliesServicesGeneralTariff>
<bd-i:ValueAddedTaxSuppliesServicesOtherRates decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesOtherRates>
<bd-i:ValueAddedTaxSuppliesServicesReducedTariff decimals="INF" contextRef="Msg" unitRef="EUR">0</bd-i:ValueAddedTaxSuppliesServicesReducedTariff>
<bd-i:ValueAddedTaxToBePaidAdditionalToBePaidBack decimals="INF" contextRef="Msg" unitRef="EUR">52</bd-i:ValueAddedTaxToBePaidAdditionalToBePaidBack>
</xbrli:xbrl>
''')
self.assertXmlTreeEqual(generated_xbrl, expected_xbrl)
def test_xbrl_identifier(self):
report = self.env.ref('l10n_nl.tax_report')
date_from = fields.Date.from_string('2026-02-01')
date_to = fields.Date.from_string('2026-02-28')
representative = self.env['res.partner'].create({
'company_type': 'company',
'name': 'Fidu NL',
'country_id': self.env.ref('base.nl').id,
'vat': 'NL123456782B90',
})
self.env.company.account_representative_id = representative
# Ensure the identifier comes from the selected VAT source, not from the override field.
self.env.company.l10n_nl_reports_sbr_ob_nummer = False
def _get_msg_identifier(options):
wizard = self.env['l10n_nl_reports.sbr.tax.report.wizard'] \
.with_context(default_date_from=date_from, default_date_to=date_to, options=options) \
.create({})
wizard.action_download_xbrl_file()
generated_xbrl = self.get_xml_tree_from_string(self.env['l10n_nl_reports.tax.report.handler'].export_tax_report_to_xbrl(options).get('file_content'))
identifier = generated_xbrl.xpath("//*[local-name()='context' and @id='Msg']/*[local-name()='entity']/*[local-name()='identifier']/text()")
return identifier[0] if identifier else ''
# Case 1: company VAT.
self.env.company.vat = 'NL123456782B90'
options = self._generate_options(report, date_from, date_to)
self.assertEqual(_get_msg_identifier(options), '123456782B90')
# Case 2: fiscal unit VAT.
company_2 = self.company_data_2['company']
company_2.currency_id = self.env.company.currency_id
tax_unit = self.env['account.tax.unit'].create({
'name': 'NL Tax Unit',
'country_id': self.env.ref('base.nl').id,
'vat': 'NL123456782B91',
'company_ids': [(6, 0, (self.env.company + company_2).ids)],
'main_company_id': self.env.company.id,
})
options = self._generate_options(report, date_from, date_to)
options['tax_unit'] = tax_unit.id
self.assertEqual(_get_msg_identifier(options), '123456782B91')
# Case 2b: Tax Unit VAT must take precedence over the company's ob_nummer.
self.env.company.l10n_nl_reports_sbr_ob_nummer = '987654321B12'
options = self._generate_options(report, date_from, date_to)
options['tax_unit'] = tax_unit.id
self.assertEqual(_get_msg_identifier(options), '123456782B91')
self.env.company.l10n_nl_reports_sbr_ob_nummer = False
# Case 3: no VAT should block export.
self.env.company.vat = False
options = self._generate_options(report, date_from, date_to)
options['tax_unit'] = 'company_only'
with self.assertRaises(RedirectWarning):
_get_msg_identifier(options)
@tagged('external_l10n', 'post_install', '-at_install', '-standard', 'external')
class TestNlSBR(TransactionCase):
def test_root_certificate_url(self):
try:
root_cert = self.env.company._l10n_nl_get_server_root_certificate_bytes()
self.assertTrue(root_cert)
self.assertEqual(base64.b64encode(root_cert), self.env.company.l10n_nl_reports_sbr_server_root_cert_id.pem_certificate)
except UserError:
raise AssertionError("The link to the root certificate is dead or unresponsive. Check https://cert.pkioverheid.nl/ to find the link to the 'Staat der Nederlanden Private Root CA - G1' certificate.")
@tagged('external_l10n', 'post_install', '-at_install', '-standard', 'external')
@skipIf(not os.getenv("NL_SBR_CERT"), "No SBR certificate")
class TestNlSBRFlow(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.env.company.write({
'vat': 'NL123456782B90',
'l10n_nl_reports_sbr_ob_nummer': '987654321B09',
})
cls.NL_SBR_CERT = os.getenv("NL_SBR_CERT")
cls.NL_SBR_PWD = os.getenv("NL_SBR_PWD")
def test_sbr_flow(self):
certificate = self.env['certificate.certificate'].create({
'name': 'SBR NL certificate',
'content': self.NL_SBR_CERT.encode(),
'pkcs12_password': self.NL_SBR_PWD,
'company_id': self.env.company.id,
})
self.assertTrue(certificate.private_key_id)
self.env.company.l10n_nl_reports_sbr_cert_id = certificate
date_from = fields.Date.from_string('2019-01-01')
date_to = fields.Date.from_string('2019-12-31')
report = self.env.ref('l10n_nl.tax_report')
options = self._generate_options(report, date_from, date_to)
wizard = self.env['l10n_nl_reports.sbr.tax.report.wizard']\
.with_context(default_date_from=date_from, default_date_to=date_to, options=options)\
.create({'is_test': True})
res = wizard.send_xbrl()
self.assertTrue(res)
+62
View File
@@ -0,0 +1,62 @@
from unittest.mock import patch
from freezegun import freeze_time
from odoo import Command
from odoo.tests import tagged
from odoo.tools import float_compare
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
def patched_generate_all_returns(account_return_type, country_code, main_company, tax_unit=None):
TestTaxReturn.basic_return_type._try_create_returns_for_fiscal_year(main_company, tax_unit)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestTaxReturn(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('nl')
def setUpClass(cls):
super().setUpClass()
cls.company_data['company'].write({
'vat': 'NL123456782B90',
'phone': '123456789',
'email': '[email protected]',
})
cls.partner_a.country_id = cls.env.ref('base.nl')
cls.invoice = cls.env['account.move'].create({
'move_type': 'out_invoice',
'invoice_date': '2024-01-01',
'partner_id': cls.partner_a.id,
'invoice_line_ids': [Command.create({
'name': f"{i}",
'product_id': cls.product_a.id,
'price_unit': 0.1,
}) for i in range(100)],
})
cls.invoice.action_post()
cls.basic_return_type = cls.env.ref('l10n_nl_reports.nl_tax_return_type')
cls.startClassPatcher(freeze_time('2024-01-16'))
with cls._patch_returns_generation():
cls.env.company.account_opening_date = '2024-02-01' # This will make the first return to be generated in January
@classmethod
def _patch_returns_generation(cls):
return patch.object(cls.registry['account.return.type'], '_generate_all_returns', patched_generate_all_returns)
def test_vat_closing_moves_with_difference_from_rounding_taxes(self):
""" Checks a closing entry with rounding difference """
first_return = self.env['account.return'].search([
('type_id', '=', self.basic_return_type.id),
('company_id', '=', self.env.company.id),
], order='date_from', limit=1)
with self.allow_pdf_render():
first_return.action_validate()
self.assertTrue(first_return.closing_move_ids)
move_line_with_difference_from_rounding_taxes = next(l for l in first_return.closing_move_ids.invoice_line_ids if l.name == 'Difference from rounding taxes')
self.assertFalse(float_compare(abs(move_line_with_difference_from_rounding_taxes.balance), self.invoice.amount_tax, 2) == 0)