From fbc77a6ecdc94d13815d6083e57126d014d9f345 Mon Sep 17 00:00:00 2001 From: "Benjamin Frantzen (bfr)" Date: Thu, 4 Feb 2021 08:36:09 +0000 Subject: [PATCH] [FIX] account_edi: a failed import of an attachment with account_edi now allows OCR to run automatically. Before this commit, account_invoice_extract and account_edi where independent in parsing files uploaded or added as attachment in an invoice. Some tricks where used to avoid clash, but they were not perfect and some bugs appeared like the OCR not triggering automatically or an attachment being parsed twice by account_edi when parsing failed. The goal of this commit is to unify the import of files between the two features and ensure that they will not clash. For more information about potential problems that appeared before : See https://github.com/odoo/odoo/pull/61169 See https://github.com/odoo/enterprise/pull/15124/ closes odoo/odoo#65660 X-original-commit: 8435d0e8990509ee67425977f970ab27bea8133a Related: odoo/enterprise#16172 Signed-off-by: Laurent Smet Signed-off-by: bfr-o --- addons/account/models/account_journal.py | 16 ++++-- addons/account/models/account_move.py | 51 +++++++++++++++++++ .../wizard/account_tour_upload_bill.py | 2 +- addons/account_edi/models/account_journal.py | 9 ---- addons/account_edi/models/account_move.py | 37 +++----------- addons/account_edi/tests/common.py | 2 +- 6 files changed, 73 insertions(+), 44 deletions(-) diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index ae6eac6ae5e..701f4069a29 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -613,7 +613,16 @@ class AccountJournal(models.Model): invoices = self.env['account.move'] for attachment in attachments: attachment.write({'res_model': 'mail.compose.message'}) - invoices += self._create_invoice_from_single_attachment(attachment) + decoders = self.env['account.move']._get_create_invoice_from_attachment_decoders() + invoice = False + for decoder in sorted(decoders, key=lambda d: d[0]): + invoice = decoder[1](attachment) + if invoice: + break + if not invoice: + invoice = self.env['account.move'].create({}) + invoice.with_context(no_new_invoice=True).message_post(attachment_ids=[attachment.id]) + invoices += invoice action_vals = { 'name': _('Generated Documents'), @@ -638,12 +647,11 @@ class AccountJournal(models.Model): def _create_invoice_from_single_attachment(self, attachment): """ Creates an invoice and post the attachment. If the related modules are installed, it will trigger OCR or the import from the EDI. + DEPRECATED : use create_invoice_from_attachment instead :returns: the created invoice. """ - invoice = self.env['account.move'].create({}) - invoice.message_post(attachment_ids=[attachment.id]) - return invoice + return self.create_invoice_from_attachment(attachment.ids) def _create_secure_sequence(self, sequence_fields): """This function creates a no_gap sequence on each journal in self that will ensure diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 1665ab0424f..58f77fffc2c 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -2887,6 +2887,57 @@ class AccountMove(models.Model): return rslt + @api.returns('mail.message', lambda value: value.id) + def message_post(self, **kwargs): + # OVERRIDE + # When posting a message, check the attachment to see if it's an invoice and update with the imported data. + res = super().message_post(**kwargs) + + attachment_ids = kwargs.get('attachment_ids', []) + if len(self) != 1 or not attachment_ids or self.env.context.get('no_new_invoice') or not self.is_invoice(include_receipts=True): + return res + + attachments = self.env['ir.attachment'].browse(attachment_ids) + odoobot = self.env.ref('base.partner_root') + if attachments and self.state != 'draft': + self.message_post(body='The invoice is not a draft, it was not updated from the attachment.', + message_type='comment', + subtype_xmlid='mail.mt_note', + author_id=odoobot.id) + return res + if attachments and self.line_ids: + self.message_post(body='The invoice already contains lines, it was not updated from the attachment.', + message_type='comment', + subtype_xmlid='mail.mt_note', + author_id=odoobot.id) + return res + + decoders = self.env['account.move']._get_update_invoice_from_attachment_decoders(self) + for decoder in sorted(decoders, key=lambda d: d[0]): + # start with message_main_attachment_id, that way if OCR is installed, only that one will be parsed. + # this is based on the fact that the ocr will be the last decoder. + for attachment in attachments.sorted(lambda x: x != self.message_main_attachment_id): + invoice = decoder[1](attachment, self) + if invoice: + break + + return res + + def _get_create_invoice_from_attachment_decoders(self): + """ Returns a list of method that are able to create an invoice from an attachment and a priority. + + :returns: A list of tuples (priority, method) where method takes an attachment as parameter. + """ + return [] + + def _get_update_invoice_from_attachment_decoders(self, invoice): + """ Returns a list of method that are able to create an invoice from an attachment and a priority. + + :param invoice: The invoice on which to update the data. + :returns: A list of tuples (priority, method) where method takes an attachment as parameter. + """ + return [] + class AccountMoveLine(models.Model): _name = "account.move.line" _description = "Journal Item" diff --git a/addons/account/wizard/account_tour_upload_bill.py b/addons/account/wizard/account_tour_upload_bill.py index 4c0209d370f..ef0f9c08247 100644 --- a/addons/account/wizard/account_tour_upload_bill.py +++ b/addons/account/wizard/account_tour_upload_bill.py @@ -65,7 +65,7 @@ class AccountTourUploadBill(models.TransientModel): 'res_model': 'mail.compose.message', 'datas': self.sample_bill_preview, }) - bill = purchase_journal.with_context(default_journal_id=purchase_journal.id, default_move_type='in_invoice')._create_invoice_from_single_attachment(attachment) + bill = purchase_journal.with_context(default_journal_id=purchase_journal.id, default_move_type='in_invoice').create_invoice_from_attachment(attachment.ids) if self.selection == 'sample': bill.write({ 'partner_id': self.env.ref('base.main_partner').id, diff --git a/addons/account_edi/models/account_journal.py b/addons/account_edi/models/account_journal.py index 21503fe004f..a40a90b27bd 100644 --- a/addons/account_edi/models/account_journal.py +++ b/addons/account_edi/models/account_journal.py @@ -51,12 +51,3 @@ class AccountJournal(models.Model): for journal in self: journal.edi_format_ids += edi_formats.filtered(lambda e: e._is_compatible_with_journal(journal)) - - def _create_invoice_from_single_attachment(self, attachment): - # OVERRIDE - invoice = self.env['account.edi.format'].search([])._create_invoice_from_attachment(attachment) - if invoice: - # with_context: we don't want to import the attachment since the invoice was just created from it. - invoice.with_context(no_new_invoice=True).message_post(attachment_ids=attachment.ids) - return invoice - return super(AccountJournal, self.with_context(no_new_invoice=True))._create_invoice_from_single_attachment(attachment) diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py index 1f2bc32b67a..170e5fa6efd 100644 --- a/addons/account_edi/models/account_move.py +++ b/addons/account_edi/models/account_move.py @@ -216,37 +216,16 @@ class AccountMove(models.Model): # Import Electronic Document #################################################### - @api.returns('mail.message', lambda value: value.id) - def message_post(self, **kwargs): + def _get_create_invoice_from_attachment_decoders(self): # OVERRIDE - # When posting a message, analyse the attachment to check if it is an EDI document and update the invoice - # with the imported data. - res = super().message_post(**kwargs) - - if len(self) != 1 or self.env.context.get('no_new_invoice') or not self.is_invoice(include_receipts=True): - return res - - attachments = self.env['ir.attachment'].browse(kwargs.get('attachment_ids', [])) - odoobot = self.env.ref('base.partner_root') - if attachments and self.state != 'draft': - self.message_post(body='The invoice is not a draft, it was not updated from the attachment.', - message_type='comment', - subtype_xmlid='mail.mt_note', - author_id=odoobot.id) - return res - if attachments and self.line_ids: - self.message_post(body='The invoice already contains lines, it was not updated from the attachment.', - message_type='comment', - subtype_xmlid='mail.mt_note', - author_id=odoobot.id) - return res - - edi_formats = self.env['account.edi.format'].search([]) - for attachment in attachments: - invoice = edi_formats._update_invoice_from_attachment(attachment, self) - if invoice: - break + res = super()._get_create_invoice_from_attachment_decoders() + res.append((10, self.env['account.edi.format'].search([])._create_invoice_from_attachment)) + return res + def _get_update_invoice_from_attachment_decoders(self, invoice): + # OVERRIDE + res = super()._get_update_invoice_from_attachment_decoders(invoice) + res.append((10, self.env['account.edi.format'].search([])._update_invoice_from_attachment)) return res #################################################### diff --git a/addons/account_edi/tests/common.py b/addons/account_edi/tests/common.py index 37c7eb3702e..b3151b95570 100644 --- a/addons/account_edi/tests/common.py +++ b/addons/account_edi/tests/common.py @@ -67,7 +67,7 @@ class AccountEdiTestCommon(AccountTestInvoicingCommon): }) journal_id = self.company_data['default_journal_sale'] - journal_id.with_context(default_move_type='in_invoice')._create_invoice_from_single_attachment(attachment) + journal_id.with_context(default_move_type='in_invoice').create_invoice_from_attachment(attachment.ids) def assert_generated_file_equal(self, invoice, expected_values, applied_xpath=None): invoice.action_post()