# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import uuid from freezegun import freeze_time from unittest.mock import patch from odoo import fields, sql_db, tools, Command from odoo.tests import new_test_user, tagged from odoo.addons.l10n_it_edi.tests.common import TestItEdi import logging _logger = logging.getLogger(__name__) @tagged('post_install_l10n', 'post_install', '-at_install') class TestItEdiImport(TestItEdi): """ Main test class for the l10n_it_edi vendor bills XML import""" fake_test_content = """ TWICE_TEST 01234560157 TD02 """ # ----------------------------- # Vendor bills # ----------------------------- def test_receive_invalid_xml(self): xml_decode = self.env['ir.attachment']._decode_edi_l10n_it_edi with tools.mute_logger("odoo.addons.l10n_it_edi.models.ir_attachment"): self.assertEqual([], xml_decode("none.xml", None)) self.assertEqual([], xml_decode("empty.xml", "")) self.assertEqual([], xml_decode("invalid.xml", "invalid")) self.assertEqual([], xml_decode("invalid2.xml", "")) def test_receive_vendor_bill(self): """ Test a sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ self._assert_import_invoice('IT01234567890_FPR01.xml', [{ 'move_type': 'in_invoice', 'invoice_date': fields.Date.from_string('2014-12-18'), 'amount_untaxed': 5.0, 'amount_tax': 1.1, 'invoice_line_ids': [{ 'quantity': 5.0, 'price_unit': 1.0, 'debit': 5.0, }], }]) def test_receive_negative_vendor_bill(self): """ Same vendor bill as test_receive_vendor_bill but negative unit price """ self._assert_import_invoice('IT01234567890_FPR02.xml', [{ 'move_type': 'in_invoice', 'invoice_date': fields.Date.from_string('2014-12-18'), 'amount_untaxed': -5.0, 'amount_tax': -1.1, 'invoice_line_ids': [{ 'quantity': 5.0, 'price_unit': -1.0, 'credit': 5.0, }], }]) 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 """ self._assert_import_invoice('IT01234567890_FPR01.xml.p7m', [{ 'ref': '01234567890', 'invoice_date': fields.Date.from_string('2014-12-18'), 'amount_untaxed': 5.0, 'amount_tax': 1.1, 'invoice_line_ids': [{ 'quantity': 5.0, 'price_unit': 1.0, }], }]) def test_receive_wrongly_signed_vendor_bill(self): """ Some of the invoices (i.e. those from Servizio Elettrico Nazionale, the ex-monopoly-of-energy company) have custom signatures that rely on an old OpenSSL implementation that breaks the current one that sees them as malformed, so we cannot read those files. Also, we couldn't find an alternative way to use OpenSSL to just get the same result without getting the error. A new fallback method has been added that reads the ASN1 file structure and takes the encoded pkcs7-data tag content out of it, regardless of the signature. Being a non-optimized pure Python implementation, it takes about 2x the time than the regular method, so it's better used as a fallback. We didn't use an existing library not to further pollute the dependencies space. task-3502910 """ with freeze_time('2019-01-01'): self._assert_import_invoice('IT09633951000_NpFwF.xml.p7m', [{ 'ref': '333333333333333', 'invoice_date': fields.Date.from_string('2023-09-08'), 'amount_untaxed': 57.54, 'amount_tax': 3.95, }]) def test_receive_bill_sequence(self): """ Ensure that the received bill gets assigned the right sequence. """ def mock_commit(self): pass super_create = self.env.registry['account.move'].create created_moves = [] def mock_create(self, vals_list): moves = super_create(self, vals_list) created_moves.extend(moves) return moves filename = 'IT01234567890_FPR02.xml' with (patch.object(self.proxy_user.__class__, '_decrypt_data', return_value=self.fake_test_content), patch.object(sql_db.Cursor, "commit", mock_commit), patch.object(self.env.registry['account.move'], 'create', mock_create), freeze_time('2019-01-01')): self.env['account.move'].with_company(self.company)._l10n_it_edi_process_downloads({ '999999999': { 'filename': filename, 'file': self.fake_test_content, 'key': str(uuid.uuid4()), }}, self.proxy_user, ) self.assertEqual(len(created_moves), 1) def test_cron_receives_bill_from_another_company(self): """ Ensure that when from one of your company, you bill the other, the import isn't impeded because of conflicts with the filename """ other_company = self.company_data['company'] filename = 'IT01234567890_FPR02.xml' def mock_commit(self): pass invoice = self.env['account.move'].with_company(other_company).create({ 'move_type': 'out_invoice', 'invoice_line_ids': [ Command.create({ 'name': "something not price included", 'price_unit': 800.40, 'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)], }), ], }) self.env['ir.attachment'].with_company(other_company).create({ 'name': filename, 'datas': self.fake_test_content, 'res_model': 'account.move', 'res_id': invoice.id, 'res_field': 'l10n_it_edi_attachment_file', }) with (patch.object(self.proxy_user.__class__, '_decrypt_data', return_value=self.fake_test_content), patch.object(sql_db.Cursor, "commit", mock_commit)): self.env['account.move'].with_company(self.company)._l10n_it_edi_process_downloads( {'999999999': { 'filename': filename, 'file': self.fake_test_content, 'key': str(uuid.uuid4()), }}, self.proxy_user, ) attachment = self.env['ir.attachment'].search([ ('name', '=', 'IT01234567890_FPR02.xml'), ('res_model', '=', 'account.move'), ('res_field', '=', 'l10n_it_edi_attachment_file'), ('company_id', '=', self.company.id), ]) self.assertTrue(attachment) self.assertTrue(self.env['account.move'].browse(attachment.res_id)) def test_receive_same_vendor_bill_twice(self): """ Test that the second time we are receiving an SdiCoop invoice, the second is discarded """ # Our test content is not encrypted ProxyUser = self.env['account_edi_proxy_client.user'] proxy_user = ProxyUser.create({ 'company_id': self.company.id, 'proxy_type': 'l10n_it_edi', 'id_client': str(uuid.uuid4()), 'edi_identification': ProxyUser._get_proxy_identification(self.company, 'l10n_it_edi'), 'private_key_id': self.private_key_id.id, }) filename = 'IT01234567890_FPR02.xml' def mock_commit(self): pass with (patch.object(proxy_user.__class__, '_decrypt_data', return_value=self.fake_test_content), patch.object(sql_db.Cursor, "commit", mock_commit), tools.mute_logger("odoo.addons.l10n_it_edi.models.account_move")): for dummy in range(2): processed = self.env['account.move']._l10n_it_edi_process_downloads({ '999999999': { 'filename': filename, 'file': self.fake_test_content, 'key': str(uuid.uuid4()), }}, proxy_user, ) # The Proxy ACK must be sent in both cases of import success and failure. self.assertEqual(processed['proxy_acks'], ['999999999']) # There should be one attachement with this filename attachments = self.env['ir.attachment'].search([ ('name', '=', 'IT01234567890_FPR02.xml'), ('res_model', '=', 'account.move'), ('res_field', '=', 'l10n_it_edi_attachment_file'), ]) self.assertEqual(len(attachments), 1) invoices = self.env['account.move'].search([('payment_reference', '=', 'TWICE_TEST')]) self.assertEqual(len(invoices), 1) def test_receive_bill_with_global_discount(self): applied_xml = """ SC 2 """ self._assert_import_invoice('IT01234567890_FPR01.xml', [{ 'invoice_date': fields.Date.from_string('2014-12-18'), 'amount_untaxed': 3.0, 'amount_tax': 1.1, 'invoice_line_ids': [ { 'quantity': 5.0, 'name': 'DESCRIZIONE DELLA FORNITURA', 'price_unit': 1.0, }, { 'quantity': 1.0, 'name': 'SCONTO', 'price_unit': -2, } ], }], applied_xml) def test_receive_bill_with_multiple_discounts_in_line(self): applied_xml = """ SC 50.00 SC 25.00 SC 20.00 1.50000000 """ self._assert_import_invoice('IT01234567890_FPR01.xml', [{ 'invoice_date': fields.Date.from_string('2014-12-18'), 'amount_untaxed': 1.5, 'amount_tax': 0.33, 'invoice_line_ids': [ { 'quantity': 5.0, 'name': 'DESCRIZIONE DELLA FORNITURA', 'price_unit': 1.0, 'discount': 70.0, } ], }], applied_xml) def test_invoice_user_can_compute_is_self_invoice(self): """Ensure that a user having only group_account_invoice can compute field l10n_it_edi_is_self_invoice""" user = new_test_user(self.env, login='jag', groups='account.group_account_invoice') move = self.env['account.move'].create({'move_type': 'in_invoice'}) move.with_user(user).read(['l10n_it_edi_is_self_invoice']) # should not raise