diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 0552636c84f..3923e14eeef 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -4076,12 +4076,29 @@ class AccountMove(models.Model):
'checkbox_download': False,
}
- def _generate_pdf_and_send_invoice(self, template, from_cron=True):
+ def _generate_pdf_and_send_invoice(self, template, from_cron=True, allow_fallback_pdf=True):
+ """ Generate the pdf for the current invoices and send them by mail using the send & print wizard.
+
+ :param from_cron: Flag indicating if the method is called from a cron. In that case, we avoid raising any
+ error.
+ :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
+ proforma PDF report instead.
+ """
composer_vals = self._get_pdf_and_send_invoice_vals(template)
composer = self.env['account.move.send'].create(composer_vals)
# from_cron=True to log errors in chatter instead of raise
- composer.action_send_and_print(from_cron=from_cron)
+ composer.action_send_and_print(from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
+
+ def _get_invoice_pdf_report_filename(self):
+ """ Get the filename of the generated PDF invoice report. """
+ self.ensure_one()
+ return f"{self.name.replace('/', '_')}.pdf"
+
+ def _get_invoice_proforma_pdf_report_filename(self):
+ """ Get the filename of the generated proforma PDF invoice report. """
+ self.ensure_one()
+ return f"{self.name.replace('/', '_')}_proforma.pdf"
# -------------------------------------------------------------------------
# TOOLING
@@ -4300,7 +4317,7 @@ class AccountMove(models.Model):
def _get_mail_thread_data_attachments(self):
res = super()._get_mail_thread_data_attachments()
# else, attachments with 'res_field' get excluded
- return res | self.env['account.move.send']._get_linked_attachments(self)
+ return res | self.env['account.move.send']._get_invoice_extra_attachments(self)
# -------------------------------------------------------------------------
# TOOLING
diff --git a/addons/account/static/src/components/mail_attachments/mail_attachments.js b/addons/account/static/src/components/mail_attachments/mail_attachments.js
index 6ad085ea87b..a8d46ac5c56 100644
--- a/addons/account/static/src/components/mail_attachments/mail_attachments.js
+++ b/addons/account/static/src/components/mail_attachments/mail_attachments.js
@@ -47,6 +47,7 @@ export class MailAttachments extends Component {
name: file.filename,
mimetype: file.mimetype,
placeholder: false,
+ manual: true,
});
}
this.props.record.update({ [this.props.name]: this.getValue().concat(extraFiles) });
diff --git a/addons/account/tests/test_account_move_send.py b/addons/account/tests/test_account_move_send.py
index 8e34866fa97..7b3f7493b0a 100644
--- a/addons/account/tests/test_account_move_send.py
+++ b/addons/account/tests/test_account_move_send.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from ast import literal_eval
-
from datetime import date
+from unittest.mock import patch
+from odoo import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.addons.mail.tests.common import MailCommon
from odoo.tests.common import Form, users, warmup
@@ -13,7 +13,7 @@ from odoo.tools import formataddr, mute_logger
@tagged('post_install_l10n', 'post_install', '-at_install')
-class AccountMoveSendBase(AccountTestInvoicingCommon, MailCommon):
+class TestAccountComposerPerformance(AccountTestInvoicingCommon, MailCommon):
@classmethod
def setUpClass(cls):
@@ -131,11 +131,6 @@ class AccountMoveSendBase(AccountTestInvoicingCommon, MailCommon):
self.patch(self.env.registry, 'ready', True)
self.flush_tracking()
-
-@tagged('post_install_l10n', 'post_install', '-at_install')
-class TestAccountComposerPerformance(AccountMoveSendBase):
- """ Test performance of custom composer for moves. """
-
def test_assert_initial_values(self):
""" Test initial values to ease understanding of results and notifications """
for move in self.test_account_moves:
@@ -239,7 +234,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app():
- composer.action_send_and_print()
+ composer.action_send_and_print(allow_fallback_pdf=True)
self.env.cr.flush() # force tracking message
self.assertEqual(len(self._new_msgs), 2, 'Should produce 2 messages: one for posting template, one for tracking')
@@ -254,7 +249,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# print: template-based message
- self.assertEqual(len(print_msg.attachment_ids), 3) # + factur-x.xml in tests
+ self.assertEqual(len(print_msg.attachment_ids), 3)
self.assertNotIn(self.attachments, print_msg.attachment_ids,
'Attachments should be duplicated, not just linked')
self.assertEqual(print_msg.author_id, self.user_account_other.partner_id,
@@ -301,7 +296,6 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# composer configuration
- self.assertEqual(len(composer.mail_attachments_widget), 3)
self.assertIn(f'TemplateBody for {test_move.name}', composer.mail_body)
self.assertEqual(composer.move_ids, test_move)
self.assertTrue(composer.checkbox_send_mail)
@@ -342,7 +336,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# print: template-based message
- self.assertEqual(len(print_msg.attachment_ids), 3) # + factur-x.xml in tests
+ self.assertEqual(len(print_msg.attachment_ids), 3)
self.assertNotIn(self.attachments, print_msg.attachment_ids,
'Attachments should be duplicated, not just linked')
self.assertEqual(print_msg.author_id, self.user_account_other.partner_id,
@@ -389,7 +383,6 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# composer configuration
- self.assertEqual(len(composer.mail_attachments_widget), 3)
self.assertIn(f'SpanishBody for {test_move.name}', composer.mail_body,
'Should be translated, based on template')
self.assertEqual(composer.move_ids, test_move)
@@ -435,3 +428,295 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
author=self.user_account_other.partner_id,
content='access_token='
)
+
+@tagged('post_install_l10n', 'post_install', '-at_install')
+class TestAccountMoveSendCommon(AccountTestInvoicingCommon):
+
+ @classmethod
+ def setUpClass(cls, chart_template_ref=None):
+ super().setUpClass(chart_template_ref=chart_template_ref)
+ cls.partner_a.email = "partner_a@tsointsoin"
+ cls.partner_b.email = "partner_b@tsointsoin"
+
+ def _assert_mail_attachments_widget(self, wizard, expected_values_list):
+ self.assertEqual(len(wizard.mail_attachments_widget), len(expected_values_list))
+ for values, expected_values in zip(wizard.mail_attachments_widget, expected_values_list):
+ try:
+ int(values['id'])
+ check_id_needed = True
+ except ValueError:
+ check_id_needed = False
+ self.assertDictEqual(
+ {k: v for k, v in values.items() if not check_id_needed and k != 'id'},
+ {k: v for k, v in expected_values.items() if not check_id_needed and k != 'id'},
+ )
+
+ def create_send_and_print(self, invoices, **kwargs):
+ template = self.env.ref(invoices._get_mail_template())
+ return self.env['account.move.send']\
+ .with_context(active_model='account.move', active_ids=invoices.ids)\
+ .create({
+ 'mail_template_id': template.id,
+ **kwargs,
+ })
+
+ def _get_mail_message(self, move):
+ return self.env['mail.message'].search([('model', '=', move._name), ('res_id', '=', move.id)], limit=1)
+
+@tagged('post_install_l10n', 'post_install', '-at_install')
+class TestAccountMoveSend(TestAccountMoveSendCommon):
+
+ def test_send_mail_warning_message(self):
+ invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
+ self.partner_a.email = None
+ wizard = self.create_send_and_print(invoice)
+ self.assertTrue(wizard.send_mail_warning_message)
+
+ # Fix the partner.
+ self.partner_a.email = "turlututu@tsointsoin"
+ wizard.invalidate_recordset(fnames=['send_mail_warning_message'])
+ self.assertFalse(wizard.send_mail_warning_message)
+
+ def test_invoice_single(self):
+ invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
+ wizard = self.create_send_and_print(invoice)
+ self.assertRecordValues(wizard, [{
+ 'mode': 'invoice_single',
+ 'enable_download': True,
+ 'checkbox_download': True,
+ 'enable_send_mail': True,
+ 'display_mail_composer': True,
+ 'send_mail_readonly': False,
+ 'checkbox_send_mail': True,
+ 'mail_lang': 'en_US',
+ 'mail_partner_ids': wizard.move_ids.partner_id.ids,
+ }])
+ self.assertTrue(wizard.mail_subject)
+ self.assertTrue(wizard.mail_body)
+ self._assert_mail_attachments_widget(wizard, [{
+ 'mimetype': 'application/pdf',
+ 'name': 'INV_2019_00001.pdf',
+ 'placeholder': True,
+ }])
+
+ # Process.
+ results = wizard.action_send_and_print()
+ self.assertEqual(results['type'], 'ir.actions.act_url')
+ self.assertRecordValues(wizard, [{'mode': 'done'}])
+
+ # The PDF has been successfully generated.
+ pdf_report = invoice.invoice_pdf_report_id
+ self.assertTrue(pdf_report)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice._name),
+ ('res_id', '=', invoice.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+
+ # Send it again. The PDF must not be created again.
+ wizard = self.create_send_and_print(invoice)
+ results = wizard.action_send_and_print()
+ self.assertEqual(results['type'], 'ir.actions.act_url')
+ self.assertRecordValues(wizard, [{'mode': 'done'}])
+ self.assertRecordValues(invoice, [{'invoice_pdf_report_id': pdf_report.id}])
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice._name),
+ ('res_id', '=', invoice.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+
+ def test_invoice_multi(self):
+ invoice1 = self.init_invoice("out_invoice", partner=self.partner_a, amounts=[1000], post=True)
+ invoice2 = self.init_invoice("out_invoice", partner=self.partner_b, amounts=[1000], post=True)
+
+ wizard = self.create_send_and_print(invoice1 + invoice2)
+ wizard_values = {
+ 'mode': 'invoice_multi',
+ 'enable_download': False,
+ 'checkbox_download': False,
+ 'enable_send_mail': True,
+ 'display_mail_composer': False,
+ 'send_mail_readonly': False,
+ 'checkbox_send_mail': True,
+ 'mail_lang': 'en_US',
+ 'mail_partner_ids': [],
+ 'mail_subject': False,
+ 'mail_body': False,
+ 'mail_attachments_widget': False,
+ }
+ self.assertRecordValues(wizard, [wizard_values])
+
+ # Process.
+ results = wizard.action_send_and_print()
+ self.assertEqual(results['type'], 'ir.actions.act_window_close')
+ self.assertRecordValues(wizard, [{'mode': 'invoice_multi'}])
+
+ # Awaiting the CRON.
+ self.assertFalse(invoice1.invoice_pdf_report_id)
+ self.assertFalse(invoice2.invoice_pdf_report_id)
+
+ # Run the CRON.
+ wizard.action_send_and_print(from_cron=True)
+ self.assertTrue(invoice1.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice1._name),
+ ('res_id', '=', invoice1.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ self.assertTrue(invoice2.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice2._name),
+ ('res_id', '=', invoice2.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+
+ # Mix already sent invoice with a new one.
+ invoice3 = self.init_invoice("out_invoice", partner=self.partner_b, amounts=[1000], post=True)
+ wizard = self.create_send_and_print(invoice1 + invoice2 + invoice3)
+ self.assertRecordValues(wizard, [wizard_values])
+ wizard.action_send_and_print(from_cron=True)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice1._name),
+ ('res_id', '=', invoice1.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice2._name),
+ ('res_id', '=', invoice2.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+ self.assertTrue(invoice1.invoice_pdf_report_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice3._name),
+ ('res_id', '=', invoice3.id),
+ ('res_field', '=', 'invoice_pdf_report_file'),
+ ])
+ self.assertEqual(len(invoice_attachments), 1)
+
+ def test_invoice_mail_attachments_widget(self):
+ invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
+
+ # Add a new attachment on the mail_template.
+ template = self.env.ref(invoice._get_mail_template())
+ extra_attachment = self.env['ir.attachment'].create({'name': "extra_attachment", 'raw': b'bar'})
+ template.attachment_ids = [Command.link(extra_attachment.id)]
+
+ wizard = self.create_send_and_print(invoice)
+ pdf_report_values = {
+ 'mimetype': 'application/pdf',
+ 'name': 'INV_2019_00001.pdf',
+ 'placeholder': True,
+ }
+ extra_attachment_values = {
+ 'mimetype': 'application/octet-stream',
+ 'name': extra_attachment.name,
+ 'placeholder': False,
+ 'mail_template_id': template.id,
+ }
+ self._assert_mail_attachments_widget(wizard, [pdf_report_values, extra_attachment_values])
+
+ # Add a new attachment manually.
+ manual_attachment = self.env['ir.attachment'].create({'name': "manual_attachment", 'raw': b'foo'})
+ manual_attachment_values = {
+ 'id': manual_attachment.id,
+ 'name': manual_attachment.name,
+ 'mimetype': manual_attachment.mimetype,
+ 'placeholder': False,
+ 'manual': True,
+ }
+ wizard.mail_attachments_widget = wizard.mail_attachments_widget + [manual_attachment_values]
+
+ # Add an attachment to a new mail_template and change it.
+ new_mail_template = wizard.mail_template_id.copy()
+ extra_attachment2 = self.env['ir.attachment'].create({'name': "extra_attachment2", 'raw': b'bar'})
+ new_mail_template.attachment_ids = [Command.set(extra_attachment2.ids)]
+ extra_attachment2_values = {
+ 'mimetype': 'application/octet-stream',
+ 'name': extra_attachment2.name,
+ 'placeholder': False,
+ 'mail_template_id': new_mail_template.id,
+ }
+
+ wizard.mail_template_id = new_mail_template
+ self._assert_mail_attachments_widget(wizard, [
+ pdf_report_values,
+ extra_attachment2_values,
+ manual_attachment_values,
+ ])
+
+ # Send.
+ wizard.action_send_and_print()
+ message = self._get_mail_message(invoice)
+ self.assertRecordValues(message, [{
+ 'attachment_ids': [
+ manual_attachment.id,
+ invoice.invoice_pdf_report_id.id,
+ extra_attachment2.id,
+ ],
+ }])
+
+ # Resend.
+ wizard = self.create_send_and_print(invoice)
+ pdf_report_values['id'] = invoice.invoice_pdf_report_id.id
+ self._assert_mail_attachments_widget(wizard, [
+ pdf_report_values,
+ extra_attachment_values,
+ ])
+
+ # Switch the template.
+ wizard.mail_template_id = new_mail_template
+ self._assert_mail_attachments_widget(wizard, [
+ pdf_report_values,
+ extra_attachment2_values,
+ ])
+
+ # Send.
+ wizard.action_send_and_print()
+ message = self._get_mail_message(invoice)
+ self.assertRecordValues(message, [{
+ 'attachment_ids': [
+ invoice.invoice_pdf_report_id.id,
+ extra_attachment2.id,
+ ],
+ }])
+
+ def test_proforma_pdf(self):
+ invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
+ wizard = self.create_send_and_print(invoice)
+
+ def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
+ invoice_data['error'] = 'test_proforma_pdf'
+
+ # Process.
+ with patch.object(type(wizard), '_hook_invoice_document_before_pdf_report_render', _hook_invoice_document_before_pdf_report_render):
+ results = wizard.action_send_and_print(allow_fallback_pdf=True)
+
+ self.assertEqual(results['type'], 'ir.actions.act_url')
+ self.assertRecordValues(wizard, [{'mode': 'done'}])
+
+ # The PDF is not generated but a proforma.
+ self.assertFalse(invoice.invoice_pdf_report_id)
+
+ def test_error_but_continue(self):
+ invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
+ wizard = self.create_send_and_print(invoice)
+
+ def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
+ invoice_data['error'] = 'prout'
+ invoice_data['error_but_continue'] = True
+
+ # Process.
+ with patch.object(type(wizard), '_hook_invoice_document_before_pdf_report_render', _hook_invoice_document_before_pdf_report_render):
+ results = wizard.action_send_and_print(allow_fallback_pdf=True)
+
+ self.assertEqual(results['type'], 'ir.actions.act_url')
+ self.assertRecordValues(wizard, [{'mode': 'done'}])
+
+ # The PDF is generated even in case of error.
+ self.assertTrue(invoice.invoice_pdf_report_id)
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index d9ea609a5e6..c8c9602445d 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -51,6 +51,7 @@
+ PROFORMA
Invoice
Draft Invoice
Cancelled Invoice
diff --git a/addons/account/wizard/account_move_send.py b/addons/account/wizard/account_move_send.py
index ece637ad895..f980135edf6 100644
--- a/addons/account/wizard/account_move_send.py
+++ b/addons/account/wizard/account_move_send.py
@@ -95,45 +95,66 @@ class AccountMoveSend(models.Model):
.with_context(lang=lang)\
._render_field(field, move.ids, **kwargs)[move._origin.id]
- @api.model
- def _get_default_mode_from_moves(self, moves):
- if all(x.is_invoice(include_receipts=True) for x in moves):
- return 'invoice_single' if len(moves) == 1 else 'invoice_multi'
- return None
+ def _get_available_field_values_in_multi(self, move):
+ self.ensure_one()
+ return {
+ 'move_ids': [Command.set(move.ids)],
+ 'mail_template_id': self.mail_template_id.id,
+ 'checkbox_send_mail': self.checkbox_send_mail,
+ }
+
+ def _get_placeholder_mail_attachments_data(self, move):
+ """ Returns all the placeholder data.
+ Should be extended to add placeholder based on the checkboxes.
+
+ :param: move: The current move.
+ :returns: A list of dictionary for each placeholder.
+ * id: str: The (fake) id of the attachment, this is needed in rendering in t-key.
+ * name: str: The name of the attachment.
+ * mimetype: str: The mimetype of the attachment.
+ * placeholder bool: Should be true to prevent download / deletion.
+ """
+ if move.invoice_pdf_report_id:
+ return []
+
+ filename = move._get_invoice_pdf_report_filename()
+ return [{
+ 'id': f'placeholder_{filename}',
+ 'name': filename,
+ 'mimetype': 'application/pdf',
+ 'placeholder': True,
+ }]
@api.model
- def _get_default_lang(self, mail_template, move):
- if mail_template:
- return mail_template._render_lang([move.id]).get(move.id)
- else:
- return get_lang(self.env).code
+ def _get_invoice_extra_attachments(self, move):
+ return move.invoice_pdf_report_id
@api.model
- def _get_default_mail_partners(self, mail_template, lang, move):
- partners = self.env['res.partner'].with_company(move.company_id)
- if mail_template.email_to:
- for mail_data in tools.email_split(mail_template.email_to):
- partners |= partners.find_or_create(mail_data)
- if mail_template.email_cc:
- for mail_data in tools.email_split(mail_template.email_cc):
- partners |= partners.find_or_create(mail_data)
- if mail_template.partner_to:
- partner_to = self._get_mail_default_field_value_from_template(mail_template, lang, move, 'partner_to')
- partner_ids = [int(pid) for pid in partner_to.split(',') if pid]
- partners |= self.env['res.partner'].sudo().browse(partner_ids).exists()
- return partners
+ def _get_invoice_extra_attachments_data(self, move):
+ return [
+ {
+ 'id': attachment.id,
+ 'name': attachment.name,
+ 'mimetype': attachment.mimetype,
+ 'placeholder': False,
+ }
+ for attachment in self._get_invoice_extra_attachments(move)
+ ]
@api.model
- def _get_default_mail_body(self, mail_template, lang, move):
- return self._get_mail_default_field_value_from_template(mail_template, lang, move, 'body_html', options={'post_process': True})
-
- @api.model
- def _get_default_mail_subject(self, mail_template, lang, move):
- return self._get_mail_default_field_value_from_template(mail_template, lang, move, 'subject')
-
- @api.model
- def _get_default_email_from(self, mail_template, lang, move):
- return self._get_mail_default_field_value_from_template(mail_template, lang, move, 'email_from')
+ def _get_mail_template_attachments_data(self, mail_template):
+ """ Returns all the placeholder data and mail template data
+ """
+ return [
+ {
+ 'id': attachment.id,
+ 'name': attachment.name,
+ 'mimetype': attachment.mimetype,
+ 'placeholder': False,
+ 'mail_template_id': mail_template.id,
+ }
+ for attachment in mail_template.attachment_ids
+ ]
# -------------------------------------------------------------------------
# COMPUTE METHODS
@@ -149,10 +170,10 @@ class AccountMoveSend(models.Model):
@api.depends('move_ids')
def _compute_mode(self):
for wizard in self:
- if wizard.move_ids:
- wizard.mode = wizard._get_default_mode_from_moves(wizard.move_ids)
- else:
- wizard.mode = 'done'
+ mode = 'done'
+ if wizard.move_ids and all(x.is_invoice(include_receipts=True) for x in wizard.move_ids):
+ mode = 'invoice_single' if len(wizard.move_ids) == 1 else 'invoice_multi'
+ wizard.mode = mode
@api.depends('move_ids')
def _compute_enable_download(self):
@@ -162,7 +183,7 @@ class AccountMoveSend(models.Model):
@api.depends('enable_download')
def _compute_checkbox_download(self):
for wizard in self:
- wizard.checkbox_download = wizard.company_id.invoice_is_download
+ wizard.checkbox_download = wizard.enable_download and wizard.company_id.invoice_is_download
@api.depends('move_ids')
def _compute_send_mail_extra_fields(self):
@@ -196,16 +217,31 @@ class AccountMoveSend(models.Model):
@api.depends('mail_template_id')
def _compute_mail_lang(self):
for wizard in self:
- if wizard.mode == 'invoice_single':
- wizard.mail_lang = wizard._get_default_lang(wizard.mail_template_id, wizard.move_ids)
+ if wizard.mail_template_id and wizard.mode == 'invoice_single':
+ move = wizard.move_ids
+ wizard.mail_lang = wizard.mail_template_id._render_lang([move.id]).get(move.id)
else:
- wizard.mail_lang = False
+ wizard.mail_lang = get_lang(self.env).code
@api.depends('mail_template_id', 'mail_lang')
def _compute_mail_partner_ids(self):
for wizard in self:
if wizard.mail_template_id and wizard.mode == 'invoice_single':
- wizard.mail_partner_ids = wizard._get_default_mail_partners(wizard.mail_template_id, wizard.mail_lang, wizard.move_ids)
+ move = wizard.move_ids
+ partners = self.env['res.partner'].with_company(move.company_id)
+ mail_template = wizard.mail_template_id
+ if mail_template.email_to:
+ for mail_data in tools.email_split(mail_template.email_to):
+ partners |= partners.find_or_create(mail_data)
+ if mail_template.email_cc:
+ for mail_data in tools.email_split(mail_template.email_cc):
+ partners |= partners.find_or_create(mail_data)
+ if mail_template.partner_to:
+ partner_to = wizard._get_mail_default_field_value_from_template(mail_template, wizard.mail_lang, move,
+ 'partner_to')
+ partner_ids = [int(pid) for pid in partner_to.split(',') if pid]
+ partners |= self.env['res.partner'].sudo().browse(partner_ids).exists()
+ wizard.mail_partner_ids = partners
else:
wizard.mail_partner_ids = []
@@ -213,7 +249,12 @@ class AccountMoveSend(models.Model):
def _compute_mail_subject(self):
for wizard in self:
if wizard.mail_template_id and wizard.mode == 'invoice_single':
- wizard.mail_subject = wizard._get_default_mail_subject(wizard.mail_template_id, wizard.mail_lang, wizard.move_ids)
+ wizard.mail_subject = wizard._get_mail_default_field_value_from_template(
+ wizard.mail_template_id,
+ wizard.mail_lang,
+ wizard.move_ids,
+ 'subject',
+ )
else:
wizard.mail_subject = None
@@ -221,112 +262,147 @@ class AccountMoveSend(models.Model):
def _compute_mail_body(self):
for wizard in self:
if wizard.mail_template_id and wizard.mode == 'invoice_single':
- wizard.mail_body = wizard._get_default_mail_body(wizard.mail_template_id, wizard.mail_lang, wizard.move_ids)
+ wizard.mail_body = wizard._get_mail_default_field_value_from_template(
+ wizard.mail_template_id,
+ wizard.mail_lang,
+ wizard.move_ids,
+ 'body_html',
+ options={'post_process': True},
+ )
else:
wizard.mail_body = None
@api.depends('mail_template_id', 'mail_lang')
def _compute_mail_attachments_widget(self):
for wizard in self:
- if wizard.mail_attachments_widget: # When checkboxes are ticked in UI
- wizard.mail_attachments_widget = wizard._get_placeholder_mail_attachments_data(wizard.move_ids)\
- + wizard._get_selected_attachments_data()
- elif wizard.mode == 'invoice_single':
- wizard.mail_attachments_widget = wizard\
- ._get_default_mail_attachments_data(wizard.mail_template_id, wizard.move_ids)
+ if wizard.mode == 'invoice_single':
+ manual_attachments_data = [x for x in wizard.mail_attachments_widget or [] if x.get('manual')]
+ wizard.mail_attachments_widget = \
+ wizard._get_placeholder_mail_attachments_data(wizard.move_ids) \
+ + wizard._get_invoice_extra_attachments_data(wizard.move_ids) \
+ + wizard._get_mail_template_attachments_data(wizard.mail_template_id) \
+ + manual_attachments_data
else:
wizard.mail_attachments_widget = []
- # -------------------------------------------------------------------------
- # ATTACHMENTS
- # -------------------------------------------------------------------------
-
- @api.model
- def _get_linked_attachments(self, move):
- """ Returns all the attachments ids linked to the move to be sent in the email.
- Should be extended to add attachments based on the checkboxes.
- """
- return move.invoice_pdf_report_id
-
- def _get_mail_attachments_ids(self, mail_template, move):
- """ Returns all the attachments ids linked to the move to be sent in the email
- AND the attachments selected by the user or the one from the template (invoice multi)
- """
- self.ensure_one()
- if self.mode == 'invoice_single':
- return self._get_selected_attachments_ids() + self._get_linked_attachments(move).ids
- else:
- return mail_template.attachment_ids.ids + self._get_linked_attachments(move).ids
-
- def _get_selected_attachments_ids(self):
- """ Returns all the attachments ids selected by the user (invoice single)
- """
- return [vals['id'] for vals in self._get_selected_attachments_data()]
-
- def _get_selected_attachments_data(self):
- """ Returns all the attachments data selected by the user (invoice single)
- """
- return list(filter(lambda x: not x.get('placeholder'), self.mail_attachments_widget))
-
- def _get_placeholder_mail_attachments_data(self, move):
- """ Returns all the placeholder data.
- Should be extended to add placeholder based on the checkboxes.
-
- :returns: A list of dictionary for each placeholder.
- * id: str: The (fake) id of the attachment, this is needed in rendering in t-key.
- * name: str: The name of the attachment.
- * mimetype: str: The mimetype of the attachment.
- * placeholder bool: should be true to prevent download / deletion
- """
- self.ensure_one()
- filename = self._get_invoice_pdf_report_filename(move)
- return [{
- 'id': f'placeholder_{filename}',
- 'name': filename,
- 'mimetype': 'application/pdf',
- 'placeholder': True,
- }]
-
- def _get_default_mail_attachments_data(self, mail_template, move):
- """ Returns all the placeholder data and mail template data
- """
- results = []
-
- for attachment in mail_template.attachment_ids:
- results.append({
- 'id': attachment.id,
- 'name': attachment.name,
- 'mimetype': attachment.mimetype,
- 'placeholder': False,
- })
-
- return self._get_placeholder_mail_attachments_data(move) + results
-
# -------------------------------------------------------------------------
# BUSINESS ACTIONS
# -------------------------------------------------------------------------
- @api.model
- def _get_invoice_pdf_report_filename(self, move):
- return f"{move.name.replace('/', '_')}.pdf"
+ def _need_invoice_document(self, invoice):
+ """ Determine if we need to generate the documents for the invoice passed as parameter.
- @api.model
- def _need_document(self, move):
- return not move.invoice_pdf_report_id and move.state == 'posted'
+ :param invoice: An account.move record.
+ :return: True if the PDF / electronic documents must be generated, False otherwise.
+ """
+ self.ensure_one()
+ return self.mode == 'invoice_single' and not invoice.invoice_pdf_report_id and invoice.state == 'posted'
- @api.model
- def _download(self, move):
- """ Download the PDF. """
- return {
- 'type': 'ir.actions.act_url',
- 'url': f'/web/content/{move.invoice_pdf_report_id.id}?download=true',
- 'close': True, # close the wizard
+ def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
+ """ Hook allowing to add some extra data for the invoice passed as parameter before the rendering of the pdf
+ report.
+
+ :param invoice: An account.move record.
+ :param invoice_data: The collected data for the invoice so far.
+ """
+ self.ensure_one()
+
+ def _prepare_invoice_pdf_report(self, invoice, invoice_data):
+ """ Prepare the pdf report for the invoice passed as parameter.
+
+ :param invoice: An account.move record.
+ :param invoice_data: The collected data for the invoice so far.
+ """
+ self.ensure_one()
+
+ if invoice.invoice_pdf_report_id:
+ return
+
+ content, _report_format = self.env['ir.actions.report']._render('account.account_invoices', invoice.ids)
+
+ invoice_data['pdf_attachment_values'] = {
+ 'raw': content,
+ 'name': invoice._get_invoice_pdf_report_filename(),
+ 'mimetype': 'application/pdf',
+ 'res_model': invoice._name,
+ 'res_id': invoice.id,
+ 'res_field': 'invoice_pdf_report_file', # Binary field
}
- @api.model
- def _send_mail(self, mail_template, move, **kwargs):
- """ Send the journal entries passed as parameter by mail. """
+ def _prepare_invoice_proforma_pdf_report(self, invoice, invoice_data):
+ """ Prepare the proforma pdf report for the invoice passed as parameter.
+
+ :param invoice: An account.move record.
+ :param invoice_data: The collected data for the invoice so far.
+ """
+ self.ensure_one()
+
+ content, _report_format = self.env['ir.actions.report']._render('account.account_invoices', invoice.ids, data={'proforma': True})
+
+ invoice_data['proforma_pdf_attachment_values'] = {
+ 'raw': content,
+ 'name': invoice._get_invoice_proforma_pdf_report_filename(),
+ 'mimetype': 'application/pdf',
+ 'res_model': invoice._name,
+ 'res_id': invoice.id,
+ }
+
+ def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data):
+ """ Hook allowing to add some extra data for the invoice passed as parameter after the rendering of the
+ (proforma) pdf report.
+
+ :param invoice: An account.move record.
+ :param invoice_data: The collected data for the invoice so far.
+ """
+ self.ensure_one()
+
+ def _link_invoice_documents(self, invoice, invoice_data):
+ """ Create the attachments containing the pdf/electronic documents for the invoice passed as parameter.
+
+ :param invoice: An account.move record.
+ :param invoice_data: The collected data for the invoice so far.
+ """
+ # create an attachment that will become 'invoice_pdf_report_file'
+ # note: Binary is used for security reason
+ invoice.message_main_attachment_id = self.env['ir.attachment'].create(invoice_data['pdf_attachment_values'])
+ invoice.invalidate_recordset(fnames=['invoice_pdf_report_id', 'invoice_pdf_report_file'])
+ self.env.add_to_compute(invoice._fields['is_move_sent'], invoice)
+
+ def _hook_if_errors(self, moves_data, from_cron=False, allow_fallback_pdf=False):
+ """ Process errors found so far when generating the documents.
+
+ :param from_cron: Flag indicating if the method is called from a cron. In that case, we avoid raising any
+ error.
+ :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
+ proforma PDF report instead.
+ """
+ allow_raising = not from_cron and not allow_fallback_pdf
+ for move, move_data in moves_data.items():
+ error = move_data['error']
+ if allow_raising:
+ raise UserError(error)
+
+ move.with_context(no_new_invoice=True).message_post(body=error)
+
+ def _hook_if_success(self, moves_data, from_cron=False, allow_fallback_pdf=False):
+ """ Process successful documents.
+
+ :param from_cron: Flag indicating if the method is called from a cron. In that case, we avoid raising any
+ error.
+ :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
+ proforma PDF report instead.
+ """
+ send_mail = self.enable_send_mail and self.checkbox_send_mail
+
+ # Send mail.
+ if send_mail:
+ self._send_mails(moves_data)
+
+ def _send_mail(self, move, **kwargs):
+ """ Send the journal entry passed as parameter by mail. """
+ self.ensure_one()
partner_ids = kwargs.get('partner_ids', [])
+ mail_template = self.mail_template_id
move\
.with_context(
@@ -345,135 +421,158 @@ class AccountMoveSend(models.Model):
},
)
- def _send_mails(self, moves):
+ def _get_mail_params(self, move):
+ self.ensure_one()
+
+ if self.mode != 'invoice_single':
+ return
+
+ # We must ensure the newly created PDF are added. At this point, the PDF has been generated but not added
+ # to 'mail_attachments_widget'.
+ attachment_ids = list(set([
+ x['id']
+ for x in self.mail_attachments_widget + self._get_invoice_extra_attachments_data(self.move_ids)
+ if not x.get('placeholder')
+ ]))
+
+ return {
+ 'body': self.mail_body,
+ 'subject': self.mail_subject,
+ 'partner_ids': self.mail_partner_ids.ids,
+ 'attachment_ids': attachment_ids,
+ }
+
+ def _send_mails(self, moves_data):
+ self.ensure_one()
subtype = self.env.ref('mail.mt_comment')
mail_template = self.mail_template_id
- for move in moves:
- attachment_ids = self._get_mail_attachments_ids(mail_template, move)
- mail_lang = self.mail_lang or self._get_default_lang(mail_template, move)
- partners = self.mail_partner_ids or self._get_default_mail_partners(mail_template, mail_lang, move)
- body = self.mail_body or self._get_default_mail_body(mail_template, mail_lang, move)
- subject = self.mail_subject or self._get_default_mail_subject(mail_template, mail_lang, move)
- email_from = self._get_default_email_from(mail_template, mail_lang, move)
+ for move, move_data in moves_data.items():
+ form = move_data['_form']
+ mail_params = form._get_mail_params(move)
+ if not mail_params:
+ continue
+
+ if move_data.get('proforma_pdf_attachment'):
+ mail_params['attachment_ids'].append(move_data['proforma_pdf_attachment'].id)
+
+ mail_lang = form.mail_lang
+ email_from = form._get_mail_default_field_value_from_template(mail_template, mail_lang, move, 'email_from')
model_description = move.with_context(lang=mail_lang).type_name
- self._send_mail(
- mail_template,
+ form._send_mail(
move,
- body=body,
- subject=subject,
- email_from=email_from,
subtype_id=subtype.id,
- partner_ids=partners.ids,
- attachment_ids=attachment_ids,
model_description=model_description,
+ email_from=email_from,
+ **mail_params,
)
- def _generate_documents_success_hook(self, moves, from_cron):
- send_mail = self.enable_send_mail and self.checkbox_send_mail
-
- # Send mail.
- if send_mail:
- self._send_mails(moves)
-
- return moves
-
- def _generate_documents_failed_hook(self, errors, from_cron):
- for move, error in errors:
- if from_cron:
- move\
- .with_context(no_new_invoice=True)\
- .message_post(body=error)
- else:
- raise UserError(error)
-
- def _link_document(self, invoice, prepared_data):
- """ Link prepared_data to the record
- """
- # create an attachment that will become 'invoice_pdf_report_file'
- # note: Binary is used for security reason
- self.env['ir.attachment'].create(prepared_data['pdf_attachment_values'])
- invoice.invalidate_model(fnames=['invoice_pdf_report_id', 'invoice_pdf_report_file'])
- self.env.add_to_compute(invoice._fields['is_move_sent'], invoice)
-
- def _postprocess_document(self, invoice, prepared_data):
- """ Postprocess prepared_data before it gets linked to the record
- """
+ def _call_web_service(self, invoices_data):
+ # TO OVERRIDE
self.ensure_one()
- def _render_document(self, invoice, prepared_data):
- """ Extend prepared_data with the rendered documents
+ def _generate_invoice_documents(self, invoices_data, allow_fallback_pdf=False):
+ """ Generate the invoice PDF and electronic documents.
+
+ :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
+ proforma PDF report instead.
+ :param invoices_data: The collected data for invoices so far.
"""
- self.ensure_one()
+ for invoice, invoice_data in invoices_data.items():
+ form = invoice_data['_form']
+ if form._need_invoice_document(invoice):
+ form._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
+ invoice_data['blocking_error'] = invoice_data.get('error') \
+ and not (allow_fallback_pdf and invoice_data.get('error_but_continue'))
+ if invoice_data['blocking_error']:
+ continue
- content, _report_format = self.env['ir.actions.report']._render('account.account_invoices', invoice.ids)
+ invoices_data_web_service = {
+ invoice: invoice_data
+ for invoice, invoice_data in invoices_data.items()
+ if not invoice_data.get('error')
+ }
+ if invoices_data_web_service:
+ self._call_web_service(invoices_data_web_service)
- prepared_data['pdf_attachment_values'] = {
- 'raw': content,
- 'name': self._get_invoice_pdf_report_filename(invoice),
- 'mimetype': 'application/pdf',
- 'res_model': invoice._name,
- 'res_id': invoice.id,
- 'res_field': 'invoice_pdf_report_file', # Binary field
+ invoices_data_pdf = {
+ invoice: invoice_data
+ for invoice, invoice_data in invoices_data.items()
+ if not invoice_data.get('error') or not invoice_data.get('blocking_error', True)
+ }
+ for invoice, invoice_data in invoices_data_pdf.items():
+ form = invoice_data['_form']
+ if form._need_invoice_document(invoice):
+ form._prepare_invoice_pdf_report(invoice, invoice_data)
+ form._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
+ form._link_invoice_documents(invoice, invoice_data)
+
+ def _generate_invoice_fallback_documents(self, invoices_data):
+ """ Generate the invoice PDF and electronic documents.
+
+ :param invoices_data: The collected data for invoices so far.
+ """
+ for invoice, invoice_data in invoices_data.items():
+ if self._need_invoice_document(invoice) and invoice_data.get('error'):
+ invoice_data.pop('error')
+ self._prepare_invoice_proforma_pdf_report(invoice, invoice_data)
+ self._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
+ invoice_data['proforma_pdf_attachment'] = self.env['ir.attachment']\
+ .create(invoice_data.pop('proforma_pdf_attachment_values'))
+
+ @api.model
+ def _download(self, attachment_id):
+ """ Download the PDF. """
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': f'/web/content/{attachment_id}?download=true',
+ 'close': True, # close the wizard
}
- def _prepare_document(self, invoice):
- """ To be overridden by modules adding support for different action in send&print.
- :return: 'prepared_data'
- { 'error' : str - if any, will be raised
- 'xxx_values': {} - prefered naming for object to be linked as attachments
- 'xxx_options': {} - prefered naming for object to be used later on }
+ def action_send_and_print(self, from_cron=False, allow_fallback_pdf=False):
+ """ Create the documents and send them to the end customers.
+
+ :param from_cron: Flag indicating if the method is called from a cron. In that case, we avoid raising any
+ error.
+ :param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
+ proforma PDF report instead.
"""
self.ensure_one()
- return {}
-
- def _call_web_service(self, prepared_data_list):
- # TO OVERRIDE
- return
-
- def _generate_documents(self, moves, from_cron):
- """ Main entry point to generate the invoice pdf and the related attachments.
- """
- prepared_data_list = []
- success, errors = self.env['account.move'], []
- for move in moves:
- try:
- if self._need_document(move):
- prepared_data = self._prepare_document(move)
- if prepared_data.get('error'):
- raise UserError(prepared_data['error'])
- else:
- prepared_data_list.append((move, prepared_data))
- except UserError as e:
- errors.append((move, str(e)))
-
- try:
- self._call_web_service(prepared_data_list)
- except UserError as e:
- for move, _prepared_data in prepared_data_list:
- errors.append((move, str(e)))
- return success, errors
-
- for move, prepared_data in prepared_data_list:
- self._render_document(move, prepared_data)
- self._postprocess_document(move, prepared_data)
- self._link_document(move, prepared_data)
- success |= move
-
- return success, errors
-
- def action_send_and_print(self, from_cron=False):
- self.ensure_one()
download = self.enable_download and self.checkbox_download
- generate_documents = self.mode == 'invoice_single' or from_cron
+ generate_invoice_documents = self.mode == 'invoice_single' or (self.mode == 'invoice_multi' and from_cron)
+ moves_data = {move: {} for move in self.move_ids}
- if generate_documents:
- success_moves, errors = self._generate_documents(self.move_ids, from_cron)
+ if len(moves_data) == 1:
+ moves_data[self.move_ids]['_form'] = self
+ else:
+ for move in self.move_ids:
+ moves_data[move]['_form'] = self.new(self._get_available_field_values_in_multi(move))
- self._generate_documents_success_hook(success_moves, from_cron)
- self._generate_documents_failed_hook(errors, from_cron)
+ if generate_invoice_documents:
+ # Generate all invoice documents.
+ self._generate_invoice_documents(moves_data, allow_fallback_pdf=allow_fallback_pdf)
+
+ # Manage errors.
+ errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
+ if errors:
+ self._hook_if_errors(errors, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
+
+ # Cleanup the error if we don't want to block the regular pdf generation.
+ for move_data in errors.values():
+ if move_data.get('pdf_attachment_values'):
+ move_data.pop('error')
+
+ # Fallback in case of error.
+ errors = {move: move_data for move, move_data in moves_data.items() if move_data.get('error')}
+ if allow_fallback_pdf and errors:
+ self._generate_invoice_fallback_documents(errors)
+
+ # Send mail.
+ success = {move: move_data for move, move_data in moves_data.items() if not move_data.get('error')}
+ if success:
+ self._hook_if_success(success, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
self.mode = 'done'
@@ -481,7 +580,11 @@ class AccountMoveSend(models.Model):
self.env.ref('account.ir_cron_account_move_send')._trigger()
if download:
- return self._download(self.move_ids)
+ attachment = self.move_ids.invoice_pdf_report_id
+ if not attachment:
+ attachment = list(moves_data.values())[0].get('proforma_pdf_attachment')
+ if attachment:
+ return self._download(attachment.id)
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/account_edi_ubl_cii/models/account_move_send.py b/addons/account_edi_ubl_cii/models/account_move_send.py
index 0bb050d2e42..4c20056a663 100644
--- a/addons/account_edi_ubl_cii/models/account_move_send.py
+++ b/addons/account_edi_ubl_cii/models/account_move_send.py
@@ -22,7 +22,16 @@ class AccountMoveSend(models.Model):
@api.model
def _get_default_enable_ubl_cii_xml(self, move):
- return not move.invoice_pdf_report_id and move.is_sale_document() and move.partner_id.ubl_cii_format
+ return not move.invoice_pdf_report_id \
+ and not move.ubl_cii_xml_id \
+ and move.is_sale_document() \
+ and bool(move.partner_id.ubl_cii_format)
+
+ def _get_available_field_values_in_multi(self, move):
+ # EXTENDS 'account'
+ values = super()._get_available_field_values_in_multi(move)
+ values['checkbox_ubl_cii_xml'] = self.checkbox_ubl_cii_xml
+ return values
# -------------------------------------------------------------------------
# COMPUTE METHODS
@@ -31,18 +40,18 @@ class AccountMoveSend(models.Model):
@api.depends('move_ids')
def _compute_checkbox_ubl_cii_label(self):
for wizard in self:
- code_to_label = dict(wizard.move_ids.partner_id._fields['ubl_cii_format'].selection)
- codes = wizard.move_ids.partner_id.mapped('ubl_cii_format')
- if any(codes):
- wizard.checkbox_ubl_cii_label = ", ".join(code_to_label[c] for c in set(codes) if c)
- else:
- wizard.checkbox_ubl_cii_label = False
+ wizard.checkbox_ubl_cii_label = False
+ if wizard.mode in ('invoice_single', 'invoice_multi'):
+ code_to_label = dict(wizard.move_ids.partner_id._fields['ubl_cii_format'].selection)
+ codes = wizard.move_ids.partner_id.mapped('ubl_cii_format')
+ if any(codes):
+ wizard.checkbox_ubl_cii_label = ", ".join(code_to_label[c] for c in set(codes) if c)
def _compute_send_mail_extra_fields(self):
# EXTENDS 'account'
super()._compute_send_mail_extra_fields()
for wizard in self:
- wizard.enable_ubl_cii_xml = any(self._get_default_enable_ubl_cii_xml(m) for m in wizard.move_ids)
+ wizard.enable_ubl_cii_xml = any(wizard._get_default_enable_ubl_cii_xml(m) for m in wizard.move_ids)
@api.depends('checkbox_ubl_cii_xml')
def _compute_mail_attachments_widget(self):
@@ -52,16 +61,16 @@ class AccountMoveSend(models.Model):
@api.depends('enable_ubl_cii_xml')
def _compute_checkbox_ubl_cii_xml(self):
for wizard in self:
- wizard.checkbox_ubl_cii_xml = wizard.company_id.invoice_is_ubl_cii
+ wizard.checkbox_ubl_cii_xml = wizard.enable_ubl_cii_xml and wizard.company_id.invoice_is_ubl_cii
# -------------------------------------------------------------------------
# ATTACHMENTS
# -------------------------------------------------------------------------
@api.model
- def _get_linked_attachments(self, move):
+ def _get_invoice_extra_attachments(self, move):
# EXTENDS 'account'
- return super()._get_linked_attachments(move) + move.ubl_cii_xml_id
+ return super()._get_invoice_extra_attachments(move) + move.ubl_cii_xml_id
def _needs_ubl_cii_placeholder(self):
return self.enable_ubl_cii_xml and self.checkbox_ubl_cii_xml
@@ -70,7 +79,7 @@ class AccountMoveSend(models.Model):
# EXTENDS 'account'
results = super()._get_placeholder_mail_attachments_data(move)
- if self._needs_ubl_cii_placeholder():
+ if self.mode == 'invoice_single' and self._needs_ubl_cii_placeholder():
builder = move.partner_id._get_edi_builder()
filename = builder._export_invoice_filename(move)
results.append({
@@ -86,11 +95,11 @@ class AccountMoveSend(models.Model):
# BUSINESS ACTIONS
# -------------------------------------------------------------------------
- def _prepare_document(self, invoice):
+ def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
# EXTENDS 'account'
- results = super()._prepare_document(invoice)
+ super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
- if self.checkbox_ubl_cii_xml and self._get_default_enable_ubl_cii_xml(invoice):
+ if self.mode == 'invoice_single' and self.checkbox_ubl_cii_xml and self._get_default_enable_ubl_cii_xml(invoice):
builder = invoice.partner_id._get_edi_builder()
xml_content, errors = builder._export_invoice(invoice)
@@ -98,41 +107,38 @@ class AccountMoveSend(models.Model):
# Failed.
if errors:
- return {
- 'error': "".join([
- _("Errors occured while creating the EDI document (format: %s):", builder._description),
- "\n",
- "
" + "".join(errors) + "" if self.mode == 'invoice_multi' \
- else "\n".join(errors)
- ]),
+ invoice_data['error'] = "".join([
+ _("Errors occured while creating the EDI document (format: %s):", builder._description),
+ "\n",
+ "
" + "".join(errors) + "" if self.mode == 'invoice_multi' \
+ else "\n".join(errors)
+ ])
+ invoice_data['error_but_continue'] = True
+ else:
+ invoice_data['ubl_cii_xml_attachment_values'] = {
+ 'name': filename,
+ 'raw': xml_content,
+ 'mimetype': 'application/xml',
+ 'res_model': invoice._name,
+ 'res_id': invoice.id,
+ 'res_field': 'ubl_cii_xml_file', # Binary field
+ }
+ invoice_data['ubl_cii_xml_options'] = {
+ 'ubl_cii_format': invoice.partner_id.ubl_cii_format,
+ 'builder': builder,
}
- results['ubl_cii_xml_attachment_values'] = {
- 'name': filename,
- 'raw': xml_content,
- 'mimetype': 'application/xml',
- 'res_model': invoice._name,
- 'res_id': invoice.id,
- 'res_field': 'ubl_cii_xml_file', # Binary field
- }
- results['ubl_cii_xml_options'] = {
- 'ubl_cii_format': invoice.partner_id.ubl_cii_format,
- 'builder': builder,
- }
-
- return results
-
- def _postprocess_document(self, invoice, prepared_data):
+ def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data):
# EXTENDS 'account'
- super()._postprocess_document(invoice, prepared_data)
+ super()._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
# Add PDF to XML
- if 'ubl_cii_xml_options' in prepared_data and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] != 'facturx':
- self._postprocess_invoice_ubl_xml(invoice, prepared_data)
+ if 'ubl_cii_xml_options' in invoice_data and invoice_data['ubl_cii_xml_options']['ubl_cii_format'] != 'facturx':
+ self._postprocess_invoice_ubl_xml(invoice, invoice_data)
# Always silently generate a Factur-X and embed it inside the PDF (inter-portability)
- if 'ubl_cii_xml_options' in prepared_data and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx':
- xml_facturx = prepared_data['ubl_cii_xml_attachment_values']['raw']
+ if 'ubl_cii_xml_options' in invoice_data and invoice_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx':
+ xml_facturx = invoice_data['ubl_cii_xml_attachment_values']['raw']
else:
xml_facturx = self.env['account.edi.xml.cii']._export_invoice(invoice)[0]
@@ -147,7 +153,7 @@ class AccountMoveSend(models.Model):
return
# Read pdf content.
- reader_buffer = io.BytesIO(prepared_data['pdf_attachment_values']['raw'])
+ reader_buffer = io.BytesIO(invoice_data['pdf_attachment_values']['raw'])
reader = OdooPdfFileReader(reader_buffer, strict=False)
# Post-process.
@@ -157,8 +163,8 @@ class AccountMoveSend(models.Model):
writer.addAttachment('factur-x.xml', xml_facturx, subtype='text/xml')
# PDF-A.
- if 'ubl_cii_xml_options' in prepared_data \
- and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx' \
+ if 'ubl_cii_xml_options' in invoice_data \
+ and invoice_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx' \
and not writer.is_pdfa:
try:
writer.convert_to_pdfa()
@@ -178,19 +184,19 @@ class AccountMoveSend(models.Model):
# Replace the current content.
writer_buffer = io.BytesIO()
writer.write(writer_buffer)
- prepared_data['pdf_attachment_values']['raw'] = writer_buffer.getvalue()
+ invoice_data['pdf_attachment_values']['raw'] = writer_buffer.getvalue()
reader_buffer.close()
writer_buffer.close()
- def _postprocess_invoice_ubl_xml(self, invoice, prepared_data):
+ def _postprocess_invoice_ubl_xml(self, invoice, invoice_data):
# Add PDF to XML
- tree = etree.fromstring(prepared_data['ubl_cii_xml_attachment_values']['raw'])
+ tree = etree.fromstring(invoice_data['ubl_cii_xml_attachment_values']['raw'])
anchor_elements = tree.xpath("//*[local-name()='AccountingSupplierParty']")
if not anchor_elements:
return
- filename = prepared_data['pdf_attachment_values']['name']
- content = prepared_data['pdf_attachment_values']['raw']
+ filename = invoice_data['pdf_attachment_values']['name']
+ content = invoice_data['pdf_attachment_values']['raw']
to_inject = f'''
\n" \
+ invoice_data['ubl_cii_xml_attachment_values']['raw'] = b"\n" \
+ etree.tostring(cleanup_xml_node(tree))
- def _link_document(self, invoice, prepared_data):
+ def _link_invoice_documents(self, invoice, invoice_data):
# EXTENDS 'account'
- super()._link_document(invoice, prepared_data)
+ super()._link_invoice_documents(invoice, invoice_data)
- attachment_vals = prepared_data.get('ubl_cii_xml_attachment_values')
+ attachment_vals = invoice_data.get('ubl_cii_xml_attachment_values')
if attachment_vals:
self.env['ir.attachment'].create(attachment_vals)
- invoice.invalidate_model(fnames=['ubl_cii_xml_id', 'ubl_cii_xml_file'])
+ invoice.invalidate_recordset(fnames=['ubl_cii_xml_id', 'ubl_cii_xml_file'])
diff --git a/addons/account_peppol/wizard/account_move_send.py b/addons/account_peppol/wizard/account_move_send.py
index 906ace33e01..01279e14a0c 100644
--- a/addons/account_peppol/wizard/account_move_send.py
+++ b/addons/account_peppol/wizard/account_move_send.py
@@ -17,10 +17,11 @@ class AccountMoveSend(models.Model):
help='Send the invoice via PEPPOL',
)
peppol_proxy_state = fields.Selection(related='company_id.account_peppol_proxy_state')
+ # technical field needed for computing a warning text about the peppol configuration
peppol_warning = fields.Char(
string="Warning",
compute="_compute_peppol_warning",
- ) # technical field needed for computing a warning text about the peppol configuration
+ )
# -------------------------------------------------------------------------
# COMPUTE METHODS
@@ -62,7 +63,7 @@ class AccountMoveSend(models.Model):
# BUSINESS ACTIONS
# -------------------------------------------------------------------------
- def action_send_and_print(self, from_cron=False):
+ def action_send_and_print(self, from_cron=False, allow_fallback_pdf=False):
# Extends the wizard to mark the move to be sent via PEPPOL first
self.ensure_one()
@@ -71,72 +72,60 @@ class AccountMoveSend(models.Model):
if not move.peppol_move_state:
move.peppol_move_state = 'to_send'
- return super().action_send_and_print(from_cron)
+ return super().action_send_and_print(from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
- def _call_web_service(self, prepared_data_list):
+ def _call_web_service(self, invoices_data):
# Overrides 'account'
+ super()._call_web_service(invoices_data)
+
if not self.checkbox_send_peppol:
- return super()._call_web_service(prepared_data_list)
+ return
params = {'documents': []}
- all_moves = self.env['account.move']
- if not prepared_data_list:
- # if a user tries to resend an invoice that has a peppol error state,
- # the prepared_data_list is going to be empty, as the xml has been aready generated
- error_moves = self.move_ids.filtered(lambda m: m.peppol_move_state == 'error')
- if not error_moves:
- return
-
- for error_move in error_moves:
- receiver_identification = f"{error_move.partner_id.peppol_eas}:{error_move.partner_id.peppol_endpoint}"
- if not error_move.ubl_cii_xml_id:
- # this happens when a user manually deletes the xml file
- # but doesn't delete the pdf file
- # they should delete both if they want to regenerate attachments
- raise UserError(_('The following invoice does not have an XML attachment: %s', error_move.name))
-
- params['documents'].append({
- 'filename': error_move.name,
- 'receiver': receiver_identification,
- 'ubl': b64encode(error_move.ubl_cii_xml_id.raw).decode(),
- })
- all_moves |= error_move
-
- for move, prepared_data in prepared_data_list:
- if not prepared_data:
- move.peppol_move_state = False
- raise UserError(_('Please select the UBL format'))
-
- if move.peppol_move_state not in ('to_send', 'error'):
+ invoices_data_peppol = {}
+ for invoice, invoice_data in invoices_data.items():
+ if invoice.peppol_move_state not in ('to_send', 'error'):
continue
- xml_file = prepared_data['ubl_cii_xml_attachment_values']['raw']
- filename = prepared_data['ubl_cii_xml_attachment_values']['name']
+ if invoice_data.get('ubl_cii_xml_attachment_values'):
+ xml_file = invoice_data['ubl_cii_xml_attachment_values']['raw']
+ filename = invoice_data['ubl_cii_xml_attachment_values']['name']
+ elif invoice.ubl_cii_xml_id:
+ xml_file = invoice.ubl_cii_xml_id.raw
+ filename = invoice.ubl_cii_xml_id.name
+ else:
+ continue
- if not move.partner_id.peppol_eas or not move.partner_id.peppol_endpoint:
+ if not invoice.partner_id.peppol_eas or not invoice.partner_id.peppol_endpoint:
# should never happen but in case it does, we need to handle it
- move.peppol_move_state = 'error'
- move._message_log(body=_('The partner is missing Peppol EAS and/or Endpoint identifier.'))
+ invoice.peppol_move_state = 'error'
+ invoice_data['error'] = _('The partner is missing Peppol EAS and/or Endpoint identifier.')
continue
- if not move.partner_id.account_peppol_is_endpoint_valid:
- move.peppol_move_state = 'error'
- move._message_log(body=_('Please verify partner configuration in partner settings.'))
+ if not invoice.partner_id.account_peppol_is_endpoint_valid:
+ invoice.peppol_move_state = 'error'
+ invoice_data['error'] = _('Please verify partner configuration in partner settings.')
continue
- receiver_identification = f"{move.partner_id.peppol_eas}:{move.partner_id.peppol_endpoint}"
+ receiver_identification = f"{invoice.partner_id.peppol_eas}:{invoice.partner_id.peppol_endpoint}"
params['documents'].append({
'filename': filename,
'receiver': receiver_identification,
'ubl': b64encode(xml_file).decode(),
})
- all_moves |= move
+ invoices_data_peppol[invoice] = invoice_data
- edi_user = self.env['account_edi_proxy_client.user'].search([
- ('company_id', '=', self.company_id.id),
- ('proxy_type', '=', 'peppol'),
- ])
+ if not params['documents']:
+ return
+
+ edi_user = self.env['account_edi_proxy_client.user'].search(
+ [
+ ('company_id', '=', self.company_id.id),
+ ('proxy_type', '=', 'peppol'),
+ ],
+ limit=1,
+ )
try:
response = edi_user._make_request(
@@ -145,18 +134,20 @@ class AccountMoveSend(models.Model):
)
if response.get('error'):
# at the moment the only error that can happen here is ParticipantNotReady error
- all_moves._message_log_batch(bodies=dict((move.id, response['error']['message']) for move in all_moves))
- all_moves.peppol_move_state = 'error'
- raise UserError(response['error']['message'])
+ for invoice, invoice_data in invoices_data_peppol.items():
+ invoice.peppol_move_state = 'error'
+ invoice_data['error'] = response['error']['message']
except AccountEdiProxyError as e:
- all_moves._message_log_batch(bodies=dict((move.id, e.message) for move in all_moves))
- all_moves.peppol_move_state = 'error'
- raise UserError(e.message)
+ for invoice, invoice_data in invoices_data_peppol.items():
+ invoice.peppol_move_state = 'error'
+ invoice_data['error'] = e.message
else:
# the response only contains message uuids,
# so we have to rely on the order to connect peppol messages to account.move
- for i, move in enumerate(all_moves):
- move.peppol_message_uuid = response['messages'][i]['message_uuid']
- move.peppol_move_state = 'processing'
+ invoices = self.env['account.move']
+ for i, (invoice, invoice_data) in enumerate(invoices_data_peppol.items()):
+ invoice.peppol_message_uuid = response['messages'][i]['message_uuid']
+ invoice.peppol_move_state = 'processing'
+ invoices |= invoice
log_message = _('The document has been sent to the Peppol Access Point for processing')
- all_moves._message_log_batch(bodies=dict((move.id, log_message) for move in all_moves))
+ invoices._message_log_batch(bodies=dict((invoice.id, log_message) for invoice in invoices_data_peppol))
diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py
index e9a93ef1e14..1109706c9d3 100644
--- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py
+++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py
@@ -184,7 +184,7 @@ class TestUBLCommon(AccountTestInvoicingCommon):
# -------------------------------------------------------------------------
@freeze_time('2017-01-01')
- def _generate_move(self, seller, buyer, **invoice_kwargs):
+ def _generate_move(self, seller, buyer, send=True, **invoice_kwargs):
"""
Create and post an account.move.
"""
@@ -221,7 +221,8 @@ class TestUBLCommon(AccountTestInvoicingCommon):
})
account_move.action_post()
- account_move._generate_pdf_and_send_invoice(self.move_template)
+ if send:
+ account_move._generate_pdf_and_send_invoice(self.move_template)
return account_move
def _assert_invoice_attachment(self, attachment, xpaths, expected_file):
diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py
index b4323d3b028..c47643e6598 100644
--- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py
+++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
+from odoo import Command
from odoo.addons.l10n_account_edi_ubl_cii_tests.tests.common import TestUBLCommon
+from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon
from odoo.tests import tagged
import base64
@tagged('post_install_l10n', 'post_install', '-at_install')
-class TestUBLBE(TestUBLCommon):
+class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon):
@classmethod
def setUpClass(cls, chart_template_ref="be"):
@@ -67,6 +69,8 @@ class TestUBLBE(TestUBLCommon):
'country_id': cls.env.ref('base.be').id,
})
+ cls.env.company.invoice_is_ubl_cii = True
+
@classmethod
def setup_company_data(cls, company_name, chart_template):
# OVERRIDE
@@ -495,3 +499,84 @@ class TestUBLBE(TestUBLCommon):
list_line_subtotals=[99], currency_id=self.currency_data['currency'].id, list_line_price_unit=[99],
list_line_discount=[0], list_line_taxes=[tax_21+self.recupel], move_type='out_invoice',
)
+
+ ####################################################
+ # Test Send & print
+ ####################################################
+
+ def test_send_and_print(self):
+ invoice = self._generate_move(
+ self.partner_1,
+ self.partner_2,
+ send=False,
+ move_type='out_invoice',
+ invoice_line_ids=[
+ {
+ 'product_id': self.product_a.id,
+ 'tax_ids': [Command.set(self.tax_21.ids)],
+ },
+ ],
+ )
+ wizard = self.create_send_and_print(invoice)
+ wizard._compute_send_mail_extra_fields()
+ self.assertRecordValues(wizard, [{
+ 'mode': 'invoice_single',
+ 'checkbox_ubl_cii_label': "Peppol BIS Billing 3.0",
+ 'enable_ubl_cii_xml': True,
+ 'checkbox_ubl_cii_xml': True,
+ }])
+ self._assert_mail_attachments_widget(wizard, [
+ {
+ 'mimetype': 'application/pdf',
+ 'name': 'INV_2017_00001.pdf',
+ 'placeholder': True,
+ },
+ {
+ 'mimetype': 'application/xml',
+ 'name': 'INV_2017_00001_ubl_bis3.xml',
+ 'placeholder': True,
+ },
+ ])
+ self.assertFalse(invoice.invoice_pdf_report_id)
+ self.assertFalse(invoice.ubl_cii_xml_id)
+
+ # Send.
+ wizard.action_send_and_print()
+ self.assertTrue(invoice.invoice_pdf_report_id)
+ self.assertTrue(invoice.ubl_cii_xml_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice._name),
+ ('res_id', '=', invoice.id),
+ ('res_field', 'in', ('invoice_pdf_report_file', 'ubl_cii_xml_file')),
+ ])
+ self.assertEqual(len(invoice_attachments), 2)
+
+ # Send again.
+ wizard = self.create_send_and_print(invoice)
+ self.assertRecordValues(wizard, [{
+ 'mode': 'invoice_single',
+ 'checkbox_ubl_cii_label': 'Peppol BIS Billing 3.0',
+ 'enable_ubl_cii_xml': False,
+ 'checkbox_ubl_cii_xml': False,
+ }])
+ self._assert_mail_attachments_widget(wizard, [
+ {
+ 'id': invoice.invoice_pdf_report_id.id,
+ 'mimetype': 'application/pdf',
+ 'name': 'INV_2019_00001.pdf',
+ },
+ {
+ 'id': invoice.ubl_cii_xml_id.id,
+ 'mimetype': 'application/xml',
+ 'name': 'INV_2019_00001_ubl_bis3.xml',
+ },
+ ])
+ wizard.action_send_and_print()
+ self.assertTrue(invoice.invoice_pdf_report_id)
+ self.assertTrue(invoice.ubl_cii_xml_id)
+ invoice_attachments = self.env['ir.attachment'].search([
+ ('res_model', '=', invoice._name),
+ ('res_id', '=', invoice.id),
+ ('res_field', 'in', ('invoice_pdf_report_file', 'ubl_cii_xml_file')),
+ ])
+ self.assertEqual(len(invoice_attachments), 2)
diff --git a/addons/l10n_es_edi_facturae/models/account_move.py b/addons/l10n_es_edi_facturae/models/account_move.py
index 7c96949d4b5..4d99f00bcd2 100644
--- a/addons/l10n_es_edi_facturae/models/account_move.py
+++ b/addons/l10n_es_edi_facturae/models/account_move.py
@@ -80,7 +80,11 @@ class AccountMove(models.Model):
def _l10n_es_edi_facturae_get_default_enable(self):
self.ensure_one()
- return self.company_id.country_code == 'ES' and self.company_id.currency_id.name == 'EUR'
+ return not self.invoice_pdf_report_id \
+ and self.l10n_es_edi_facturae_xml_id \
+ and self.is_invoice(include_receipts=True) \
+ and self.company_id.country_code == 'ES' \
+ and self.company_id.currency_id.name == 'EUR'
def _l10n_es_edi_facturae_get_filename(self):
self.ensure_one()
diff --git a/addons/l10n_es_edi_facturae/models/account_move_send.py b/addons/l10n_es_edi_facturae/models/account_move_send.py
index a9466a658ec..d535d797d92 100644
--- a/addons/l10n_es_edi_facturae/models/account_move_send.py
+++ b/addons/l10n_es_edi_facturae/models/account_move_send.py
@@ -37,7 +37,7 @@ class AccountMoveSend(models.Model):
# EXTENDS 'account'
results = super()._get_placeholder_mail_attachments_data(move)
- if self.l10n_es_edi_facturae_enable_xml and self.l10n_es_edi_facturae_checkbox_xml:
+ if self.mode == 'invoice_single' and self.l10n_es_edi_facturae_enable_xml and self.l10n_es_edi_facturae_checkbox_xml:
filename = f'{move.name.replace("/", "_")}_facturae_signed.xml'
results.append({
'id': f'placeholder_{filename}',
@@ -52,18 +52,19 @@ class AccountMoveSend(models.Model):
# BUSINESS ACTIONS
# -------------------------------------------------------------------------
- def _prepare_document(self, invoice):
+ def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
# EXTENDS 'account'
- results = super()._prepare_document(invoice)
+ results = super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
if self.l10n_es_edi_facturae_checkbox_xml and invoice._l10n_es_edi_facturae_get_default_enable():
try:
xml_content = invoice._l10n_es_edi_facturae_render_facturae()
except Exception as error:
- return {
- 'error': "".join((_("Errors occured while creating the EDI document (format: %s):", "Facturae"),
- "\n", str(error))),
- }
+ invoice_data['error'] = "".join((
+ _("Errors occured while creating the EDI document (format: %s):", "Facturae"),
+ "\n",
+ str(error),
+ ))
results['l10n_es_edi_facturae_attachment_values'] = {
'name': invoice._l10n_es_edi_facturae_get_filename(),
@@ -76,11 +77,11 @@ class AccountMoveSend(models.Model):
return results
- def _link_document(self, invoice, prepared_data):
+ def _link_invoice_documents(self, invoice, invoice_data):
# EXTENDS 'account'
- super()._link_document(invoice, prepared_data)
+ super()._link_invoice_documents(invoice, invoice_data)
- attachment_vals = prepared_data.get('l10n_es_edi_facturae_attachment_values')
+ attachment_vals = invoice_data.get('l10n_es_edi_facturae_attachment_values')
if attachment_vals:
self.env['ir.attachment'].create(attachment_vals)
- invoice.invalidate_model(fnames=['l10n_es_edi_facturae_xml_id', 'l10n_es_edi_facturae_xml_file'])
+ invoice.invalidate_recordset(fnames=['l10n_es_edi_facturae_xml_id', 'l10n_es_edi_facturae_xml_file'])
diff --git a/addons/snailmail_account/wizard/account_move_send.py b/addons/snailmail_account/wizard/account_move_send.py
index f9dfdc83d40..eabec5accf5 100644
--- a/addons/snailmail_account/wizard/account_move_send.py
+++ b/addons/snailmail_account/wizard/account_move_send.py
@@ -15,6 +15,16 @@ class AccountMoveSend(models.Model):
)
send_by_post_warning_message = fields.Text(compute='_compute_send_by_post_warning_message')
+ def _get_available_field_values_in_multi(self, move):
+ # EXTENDS 'account'
+ values = super()._get_available_field_values_in_multi(move)
+ values['checkbox_send_by_post'] = self.checkbox_send_by_post
+ return values
+
+ # -------------------------------------------------------------------------
+ # COMPUTE METHODS
+ # -------------------------------------------------------------------------
+
@api.depends('mode')
def _compute_enable_send_by_post(self):
for wizard in self:
@@ -54,18 +64,24 @@ class AccountMoveSend(models.Model):
'attachment_id': move.invoice_pdf_report_id.id,
}
- def _generate_documents_success_hook(self, moves, from_cron):
+ def _hook_if_success(self, moves_data, from_cron=False, allow_fallback_pdf=False):
# EXTENDS 'account'
- moves = super()._generate_documents_success_hook(moves, from_cron)
+ super()._hook_if_success(moves_data, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
send_by_post = self.enable_send_by_post and self.checkbox_send_by_post
+ if not send_by_post:
+ return
- if send_by_post:
- self.env['snailmail.letter']\
- .create([
- self._prepare_snailmail_letter_values(move)
- for move in moves
- ])\
- ._snailmail_print(immediate=False)
+ moves = self.env['account.move']
+ for move in moves_data:
+ moves |= move
+ moves = moves.filtered('invoice_pdf_report_id')
+ if not moves:
+ return
- return moves
+ self.env['snailmail.letter']\
+ .create([
+ self._prepare_snailmail_letter_values(move)
+ for move in moves
+ ])\
+ ._snailmail_print(immediate=False)