[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) <las@odoo.com>
This commit is contained in:
Anh Thao Pham (pta)
2024-01-11 13:42:56 +00:00
parent 179980768c
commit ae2219d6f6
3 changed files with 57 additions and 12 deletions
+3 -3
View File
@@ -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']
+5 -2
View File
@@ -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)
@@ -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", '<test></test>')
embedded_files = self.env['ir.attachment']._decode_edi_xml(xml_filename, '<test></test>')
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})