From ae2219d6f6c38c4f5af1c2996ab5faf3a538c1bd Mon Sep 17 00:00:00 2001 From: "Anh Thao Pham (pta)" Date: Wed, 29 Nov 2023 16:33:49 +0100 Subject: [PATCH] [FIX] account: fix single bill creation with PDF and XML from alias Steps to reproduce: - Install Accounting - Configure an Email alias for Vendor Bills journal - Send an email to the alias with an PDF containing an embedded EDI XML and the EDI XML corresponding to the PDF as attachments Issue: - 2 bills are created (one with the PDF as attachment and one with the XML), instead of one with both PDF and XML as attachments - The PDF with the embedded XML is linked to the invoice created by the attached XML file and not the embedded one - The embedded XML is decoded before the attached XML file. Cause: The order in which the documents are decoded. opw-3604697 closes odoo/odoo#144243 Signed-off-by: Laurent Smet (las) --- addons/account/models/account_move.py | 6 +- addons/account/models/ir_attachment.py | 7 ++- .../test_account_incoming_supplier_invoice.py | 56 ++++++++++++++++--- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index d0a9d1d98cb..92685ed167a 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -3100,7 +3100,7 @@ class AccountMove(models.Model): def add_file_data_results(file_data, invoice): passed_file_data_list.append(file_data) - attachment = file_data.get('attachment') + attachment = file_data.get('attachment') or file_data.get('originator_pdf') if attachment: if attachments_by_invoice[attachment]: attachments_by_invoice[attachment] |= invoice @@ -3123,8 +3123,8 @@ class AccountMove(models.Model): close_file(file_data) continue - # When receiving an xml plus a pdf, since both are representing the same invoice, both needs - # to be linked to the same invoice. + # When receiving multiple files, if they have a different type, we supposed they are all linked + # to the same invoice. if ( passed_file_data_list and passed_file_data_list[-1]['filename'] != file_data['filename'] diff --git a/addons/account/models/ir_attachment.py b/addons/account/models/ir_attachment.py index bbb3fd50c14..8e1c43fa366 100644 --- a/addons/account/models/ir_attachment.py +++ b/addons/account/models/ir_attachment.py @@ -46,7 +46,6 @@ class IrAttachment(models.Model): 'attachment': self, 'filename': filename, 'content': content, - 'attachment': self, 'xml_tree': xml_tree, 'sort_weight': 10, 'type': 'xml', @@ -69,7 +68,11 @@ class IrAttachment(models.Model): to_process = [] try: for xml_name, xml_content in pdf_reader.getAttachments(): - to_process.extend(self.env['ir.attachment']._decode_edi_xml(xml_name, xml_content)) + embedded_files = self.env['ir.attachment']._decode_edi_xml(xml_name, xml_content) + for file_data in embedded_files: + file_data['sort_weight'] += 1 + file_data['originator_pdf'] = self + to_process.extend(embedded_files) except (NotImplementedError, StructError, PdfReadError) as e: _logger.warning("Unable to access the attachments of %s. Tried to decrypt it, but %s.", filename, e) diff --git a/addons/account/tests/test_account_incoming_supplier_invoice.py b/addons/account/tests/test_account_incoming_supplier_invoice.py index b417563680f..ba15f3f32ce 100644 --- a/addons/account/tests/test_account_incoming_supplier_invoice.py +++ b/addons/account/tests/test_account_incoming_supplier_invoice.py @@ -56,27 +56,41 @@ class TestAccountIncomingSupplierInvoice(AccountTestInvoicingCommon): @contextmanager def with_success_decoder(self, omit=None): + decoded_files = set() + def get_edi_decoder(_record, file_data, new=False): - return lambda *args, **kwargs: not omit or file_data['attachment'].name not in omit + + def decoder(*args, **kwargs): + return not omit or file_data['attachment'].name not in omit + + if decoder(): + decoded_files.add(file_data['filename']) + return decoder with patch.object(type(self.env['account.move']), '_get_edi_decoder', get_edi_decoder): - yield + yield decoded_files @contextmanager def with_simulated_embedded_xml(self, pdf): super_decode_edi_pdf = type(self.env['ir.attachment'])._decode_edi_pdf + xml_filename = f"{pdf.name}_xml" def decode_edi_pdf(record, filename, content): results = super_decode_edi_pdf(record, filename, content) if filename == pdf.name: - results += pdf._decode_edi_xml(f"{pdf.name}_xml", '') + embedded_files = self.env['ir.attachment']._decode_edi_xml(xml_filename, '') + for file_data in embedded_files: + file_data['sort_weight'] += 1 + file_data['originator_pdf'] = pdf + results += embedded_files return results with patch.object(type(self.env['ir.attachment']), '_decode_edi_pdf', decode_edi_pdf): - yield + yield xml_filename def _assert_extend_with_attachments(self, expected_values, new=False): attachments = self.env['ir.attachment'].browse([x.id for x in expected_values]) + nb_moves_before = self.env['account.move'].search_count([('company_id', '=', self.env.company.id)]) results = self.env['account.move']\ .with_context(default_move_type='out_invoice', default_journal_id=self.company_data['default_journal_sale'].id)\ ._extend_with_attachments(attachments, new=new) @@ -92,6 +106,9 @@ class TestAccountIncomingSupplierInvoice(AccountTestInvoicingCommon): self.assertEqual(current_values, {k.name: v for k, v in expected_values.items()}) + nb_moves_after = self.env['account.move'].search_count([('company_id', '=', self.env.company.id)]) + self.assertEqual(nb_moves_before + invoice_number, nb_moves_after) + def test_supplier_invoice_mailed_from_supplier(self): message_parsed = { 'message_id': 'message-id-dead-beef', @@ -182,17 +199,42 @@ class TestAccountIncomingSupplierInvoice(AccountTestInvoicingCommon): gif2 = self._create_dummy_gif_attachment() xml1 = self._create_dummy_xml_attachment() xml2 = self._create_dummy_xml_attachment() - with self.with_success_decoder(): + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, pdf2: 1}, new=False) + self.assertEqual(decoded_files, {pdf1.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, pdf2: 2}, new=True) + self.assertEqual(decoded_files, {pdf1.name, pdf2.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, pdf2: 1, gif1: 1, gif2: 1}, new=False) + self.assertEqual(decoded_files, {pdf1.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, pdf2: 2, gif1: 3, gif2: 4}, new=True) + self.assertEqual(decoded_files, {pdf1.name, pdf2.name, gif1.name, gif2.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, xml1: 1}, new=False) + self.assertEqual(decoded_files, {xml1.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({pdf1: 1, xml1: 1}, new=True) + self.assertEqual(decoded_files, {xml1.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({xml1: 1, xml2: 1}, new=False) + self.assertEqual(decoded_files, {xml1.name}) + with self.with_success_decoder() as decoded_files: self._assert_extend_with_attachments({xml1: 1, xml2: 2}, new=True) - with self.with_success_decoder(omit={pdf1.name}): + self.assertEqual(decoded_files, {xml1.name, xml2.name}) + with self.with_success_decoder(omit={pdf1.name}) as decoded_files: self._assert_extend_with_attachments({pdf1: 1, pdf2: 2}, new=True) - with self.with_success_decoder(), self.with_simulated_embedded_xml(pdf1): + self.assertEqual(decoded_files, {pdf2.name}) + with self.with_success_decoder() as decoded_files, self.with_simulated_embedded_xml(pdf1) as xml_filename: self._assert_extend_with_attachments({pdf1: 1, pdf2: 1}, new=False) + self.assertEqual(decoded_files, {xml_filename}) + with self.with_success_decoder() as decoded_files, self.with_simulated_embedded_xml(pdf1) as xml_filename: self._assert_extend_with_attachments({pdf1: 1, pdf2: 2}, new=True) + self.assertEqual(decoded_files, {xml_filename, pdf2.name}) + with self.with_success_decoder() as decoded_files, self.with_simulated_embedded_xml(pdf1): + self._assert_extend_with_attachments({pdf1: 1, xml1: 1}, new=False) + self.assertEqual(decoded_files, {xml1.name}) + with self.with_success_decoder() as decoded_files, self.with_simulated_embedded_xml(pdf1): + self._assert_extend_with_attachments({pdf1: 1, xml1: 1}, new=True) + self.assertEqual(decoded_files, {xml1.name})