diff --git a/addons/l10n_it_edi/tests/test_ir_mail_server.py b/addons/l10n_it_edi/tests/test_ir_mail_server.py
index 8d3487ccda1..7fd6afb4a1a 100644
--- a/addons/l10n_it_edi/tests/test_ir_mail_server.py
+++ b/addons/l10n_it_edi/tests/test_ir_mail_server.py
@@ -5,7 +5,7 @@ import datetime
import logging
from collections import namedtuple
from unittest.mock import patch
-import freezegun
+from freezegun import freeze_time
from odoo import tools
from odoo.tests import tagged
@@ -14,7 +14,6 @@ from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature
_logger = logging.getLogger(__name__)
-
@tagged('post_install_l10n', 'post_install', '-at_install')
class PecMailServerTests(AccountEdiTestCommon):
""" Main test class for the l10n_it_edi vendor bills XML import from a PEC mail account"""
@@ -51,7 +50,7 @@ class PecMailServerTests(AccountEdiTestCommon):
cls.company = cls.company_data_2['company']
# Initialize the company's codice fiscale
- cls.company.l10n_it_codice_fiscale = 'IT01234560157'
+ cls.company.l10n_it_codice_fiscale = '01234560157'
# Build test data.
# invoice_filename1 is used for vendor bill receipts tests
@@ -84,6 +83,11 @@ class PecMailServerTests(AccountEdiTestCommon):
'server_type': 'imap',
'l10n_it_is_pec': True})
+ cls.test_invoice_xmls = {k: cls._get_test_file_content(v) for k, v in [
+ ('normal_1', 'IT01234567890_FPR01.xml'),
+ ('signed', 'IT01234567890_FPR01.xml.p7m'),
+ ]}
+
@classmethod
def _get_test_file_content(cls, filename):
""" Get the content of a test file inside this module """
@@ -111,7 +115,7 @@ class PecMailServerTests(AccountEdiTestCommon):
def test_receive_signed_vendor_bill(self):
""" Test a signed (P7M) sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """
- with freezegun.freeze_time('2020-04-06'):
+ with freeze_time('2020-04-06'):
invoices = self._create_invoice(self.signed_invoice_content, self.signed_invoice_filename)
self.assertRecordValues(invoices, [{
'company_id': self.company.id,
diff --git a/addons/l10n_it_edi_sdicoop/tests/__init__.py b/addons/l10n_it_edi_sdicoop/tests/__init__.py
new file mode 100644
index 00000000000..ef57f8ba3da
--- /dev/null
+++ b/addons/l10n_it_edi_sdicoop/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_edi_xml
diff --git a/addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_basis.xml b/addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_basis.xml
new file mode 100644
index 00000000000..56c49b87938
--- /dev/null
+++ b/addons/l10n_it_edi_sdicoop/tests/expected_xmls/IT00470550013_basis.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ IT
+ 01234560157
+
+ ___ignore___
+ FPR12
+ 0000000
+
+
+
+
+
+
+ IT
+ 01234560157
+
+ 01234560157
+
+ company_2_data
+
+ RF01
+
+
+ 1234 Test Street
+ 12345
+ Prova
+ IT
+
+
+
+
+
+ IT
+ 00465840031
+
+
+ Alessi
+
+
+
+
+ Via Privata Alessi 6
+ 28887
+ Milan
+ IT
+
+
+
+
+
+
+ TD01
+ EUR
+ 2022-03-24
+ ___ignore___
+
+
+
+
+
+ TP02
+
+ MP05
+ 2022-03-24
+
+ ___ignore___
+
+
+
+
diff --git a/addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py b/addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py
new file mode 100644
index 00000000000..4aa2bb3ef15
--- /dev/null
+++ b/addons/l10n_it_edi_sdicoop/tests/test_edi_xml.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import datetime
+import logging
+from lxml import etree
+from freezegun import freeze_time
+
+from odoo import tools
+from odoo.tests import tagged
+from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
+
+_logger = logging.getLogger(__name__)
+
+@tagged('post_install_l10n', 'post_install', '-at_install')
+class TestItEdi(AccountEdiTestCommon):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass(chart_template_ref='l10n_it.l10n_it_chart_template_generic',
+ edi_format_ref='l10n_it_edi.edi_fatturaPA')
+
+ # Use the company_data_2 to test that the e-invoice is imported for the right company
+ cls.company = cls.company_data_2['company']
+
+ cls.company.l10n_it_codice_fiscale = '01234560157'
+ cls.company.vat = 'IT01234560157'
+ cls.test_bank = cls.env['res.partner.bank'].with_company(cls.company).create({
+ 'partner_id': cls.company.partner_id.id,
+ 'acc_number': 'IT1212341234123412341234123',
+ 'bank_name': 'BIG BANK',
+ 'bank_bic': 'BIGGBANQ',
+ })
+ cls.company.l10n_it_tax_system = "RF01"
+ cls.company.street = "1234 Test Street"
+ cls.company.zip = "12345"
+ cls.company.city = "Prova"
+ cls.company.country_id = cls.env.ref('base.it')
+
+ cls.price_included_tax = cls.env['account.tax'].create({
+ 'name': '22% price included tax',
+ 'amount': 22.0,
+ 'amount_type': 'percent',
+ 'price_include': True,
+ 'include_base_amount': True,
+ 'company_id': cls.company.id,
+ })
+
+ cls.italian_partner_a = cls.env['res.partner'].create({
+ 'name': 'Alessi',
+ 'vat': 'IT00465840031',
+ 'l10n_it_codice_fiscale': '00465840031',
+ 'country_id': cls.env.ref('base.it').id,
+ 'street': 'Via Privata Alessi 6',
+ 'zip': '28887',
+ 'city': 'Milan',
+ 'company_id': cls.company.id,
+ })
+
+ cls.standard_line = {
+ 'name': 'standard_line',
+ 'quantity': 1,
+ 'price_unit': 800.40,
+ 'tax_ids': [(6, 0, [cls.company.account_sale_tax_id.id])]
+ }
+
+ cls.price_included_invoice = cls.env['account.move'].with_company(cls.company).create({
+ 'move_type': 'out_invoice',
+ 'invoice_date': datetime.date(2022, 3, 24),
+ 'partner_id': cls.italian_partner_a.id,
+ 'partner_bank_id': cls.test_bank.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'something price included',
+ 'tax_ids': [(6, 0, [cls.price_included_tax.id])]
+ }),
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'something else price included',
+ 'tax_ids': [(6, 0, [cls.price_included_tax.id])]
+ }),
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'something not price included',
+ }),
+ ],
+ })
+
+ cls.partial_discount_invoice = cls.env['account.move'].with_company(cls.company).create({
+ 'move_type': 'out_invoice',
+ 'invoice_date': datetime.date(2022, 3, 24),
+ 'partner_id': cls.italian_partner_a.id,
+ 'partner_bank_id': cls.test_bank.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'no discount',
+ }),
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'special discount',
+ 'discount': 50,
+ }),
+ (0, 0, {
+ **cls.standard_line,
+ 'name': "an offer you can't refuse",
+ 'discount': 100,
+ }),
+ ],
+ })
+
+ cls.full_discount_invoice = cls.env['account.move'].with_company(cls.company).create({
+ 'move_type': 'out_invoice',
+ 'invoice_date': datetime.date(2022, 3, 24),
+ 'partner_id': cls.italian_partner_a.id,
+ 'partner_bank_id': cls.test_bank.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ **cls.standard_line,
+ 'name': 'nothing shady just a gift for my friend',
+ 'discount': 100,
+ }),
+ ],
+ })
+
+ # We create this because we are unable to post without a proxy user existing
+ cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({
+ 'id_client': 'l10n_it_edi_sdicoop_test',
+ 'company_id': cls.company.id,
+ 'edi_format_id': cls.edi_format.id,
+ 'edi_identification': 'l10n_it_edi_sdicoop_test',
+ 'private_key': 'l10n_it_edi_sdicoop_test',
+ })
+
+ # post the invoices
+ cls.price_included_invoice._post()
+ cls.partial_discount_invoice._post()
+ cls.full_discount_invoice._post()
+
+ cls.edi_basis_xml = cls._get_test_file_content('IT00470550013_basis.xml')
+
+ @classmethod
+ def _get_test_file_content(cls, filename):
+ """ Get the content of a test file inside this module """
+ path = 'l10n_it_edi_sdicoop/tests/expected_xmls/' + filename
+ with tools.file_open(path, mode='rb') as test_file:
+ return test_file.read()
+
+ @freeze_time('2020-03-24')
+ def test_price_included_taxes(self):
+ """ When the tax is price included, there should be a rounding value added to the xml, if the sum(subtotals) * tax_rate is not
+ equal to taxable base * tax rate (there is a constraint in the edi where taxable base * tax rate = tax amount, but also
+ taxable base = sum(subtotals) + rounding amount)
+ """
+
+ # In this case, the first two lines use a price_include tax the
+ # subtotals should be 800.40 / (100 + 22.0) * 100 = 656.065564..,
+ # where 22.0 is the tax rate.
+ #
+ # Since the subtotals are rounded we actually have 656.07
+ lines = self.price_included_invoice.line_ids
+ price_included_lines = lines.filtered(lambda line: line.tax_ids == self.price_included_tax)
+ self.assertEqual([line.price_subtotal for line in price_included_lines], [656.07, 656.07])
+ # So the taxable a base the edi expects (for this tax) is actually 1312.14
+ price_included_tax_line = lines.filtered(lambda line: line.tax_line_id == self.price_included_tax)
+ self.assertEqual(price_included_tax_line.tax_base_amount, 1312.14)
+
+ # The tax amount of the price included tax should be:
+ # per line: 800.40 - (800.40 / (100 + 22) * 100) = 144.33
+ # tax amount: 144.33 * 2 = 288.66
+ self.assertEqual(price_included_tax_line.price_total, 288.66)
+
+ expected_etree = self.with_applied_xpath(
+ etree.fromstring(self.edi_basis_xml),
+ '''
+
+
+
+ 1
+ something price included
+ 1.00
+ 656.070000
+ 656.07
+ 22.00
+
+
+ 2
+ something else price included
+ 1.00
+ 656.070000
+ 656.07
+ 22.00
+
+
+ 3
+ something not price included
+ 1.00
+ 800.400000
+ 800.40
+ 22.00
+
+
+ 22.00
+ -0.04909091
+ 1312.09
+ 288.66
+ I
+
+
+ 22.00
+ 800.40
+ 176.09
+ I
+
+
+
+
+ 2577.29
+
+ ''')
+ invoice_etree = etree.fromstring(self.price_included_invoice._export_as_xml())
+ # Remove the attachment and its details
+ invoice_etree = self.with_applied_xpath(invoice_etree, "")
+ self.assertXmlTreeEqual(invoice_etree, expected_etree)
+
+ @freeze_time('2020-03-24')
+ def test_partially_discounted_invoice(self):
+ # The EDI can account for discounts, but a line with, for example, a 100% discount should still have
+ # a corresponding tax with a base amount of 0
+
+ invoice_etree = etree.fromstring(self.partial_discount_invoice._export_as_xml())
+ expected_etree = self.with_applied_xpath(
+ etree.fromstring(self.edi_basis_xml),
+ '''
+
+
+
+ 1
+ no discount
+ 1.00
+ 800.400000
+ 800.40
+ 22.00
+
+
+ 2
+ special discount
+ 1.00
+ 800.400000
+
+ SC
+ 50.00
+
+ 400.20
+ 22.00
+
+
+ 3
+ an offer you can't refuse
+ 1.00
+ 800.400000
+
+ SC
+ 100.00
+
+ 0.00
+ 22.00
+
+
+ 22.00
+ 1200.60
+ 264.13
+ I
+
+
+
+
+ 1464.73
+
+ ''')
+ invoice_etree = self.with_applied_xpath(invoice_etree, "")
+ self.assertXmlTreeEqual(invoice_etree, expected_etree)
+
+ @freeze_time('2020-03-24')
+ def test_fully_discounted_inovice(self):
+ invoice_etree = etree.fromstring(self.full_discount_invoice._export_as_xml())
+ expected_etree = self.with_applied_xpath(
+ etree.fromstring(self.edi_basis_xml),
+ '''
+
+
+
+ 1
+ nothing shady just a gift for my friend
+ 1.00
+ 800.400000
+
+ SC
+ 100.00
+
+ 0.00
+ 22.00
+
+
+ 22.00
+ 0.00
+ 0.00
+ I
+
+
+
+
+ 0.00
+
+ ''')
+ invoice_etree = self.with_applied_xpath(invoice_etree, "")
+ self.assertXmlTreeEqual(invoice_etree, expected_etree)