[REF] account,*: refactoring of the Send & Print wizard (part 1)
Currently the Send & Print wizard is stored as regular model and not a TransientModel. This cause multiple problems including concurrency ones. This PR switches the wizard back to a regular transient one. We instead store the values of the wizard that needs to be processed asynchronously in the related move. closes odoo/odoo#139311 Task-id: 3415101 Related: odoo/enterprise#49310 Signed-off-by: Laurent Smet (las) <las@odoo.com>
This commit is contained in:
@@ -126,7 +126,7 @@ class PortalAccount(CustomerPortal):
|
||||
if report_type == 'pdf' and download:
|
||||
# Send & Print wizard with only the 'download' checkbox to get the official attachment(s)
|
||||
template = request.env.ref(invoice_sudo._get_mail_template())
|
||||
attachment_ids = invoice_sudo._generate_pdf_and_send_invoice(template, checkbox_send_mail=False, checkbox_download=True)
|
||||
attachment_ids = invoice_sudo._generate_pdf_and_send_invoice(template, bypass_download=True, checkbox_send_mail=False, checkbox_download=True)
|
||||
attachments = request.env['ir.attachment'].browse(attachment_ids)
|
||||
if len(attachments) > 1:
|
||||
filename = invoice_sudo._get_invoice_report_filename(extension='zip')
|
||||
|
||||
@@ -539,6 +539,7 @@ class AccountMove(models.Model):
|
||||
string='Cash Rounding Method',
|
||||
help='Defines the smallest coinage of the currency that can be used to pay by cash.',
|
||||
)
|
||||
send_and_print_values = fields.Json()
|
||||
invoice_pdf_report_id = fields.Many2one(
|
||||
comodel_name='ir.attachment',
|
||||
string="PDF Attachment",
|
||||
@@ -628,23 +629,8 @@ class AccountMove(models.Model):
|
||||
move.invoice_user_id = False
|
||||
|
||||
def _compute_is_being_sent(self):
|
||||
self.is_being_sent = False
|
||||
if self.ids:
|
||||
self.env['account.move.send'].flush_model(['move_ids', 'mode'])
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
SELECT move_send_rel.account_move_id
|
||||
FROM account_move_send send
|
||||
JOIN account_move_account_move_send_rel move_send_rel
|
||||
ON move_send_rel.account_move_id in %(move_ids)s
|
||||
AND send.id = move_send_rel.account_move_send_id
|
||||
WHERE send.mode = 'invoice_multi'
|
||||
""",
|
||||
params={'move_ids': tuple(self.ids)}
|
||||
)
|
||||
move_ids = [res[0] for res in self.env.cr.fetchall()]
|
||||
if move_ids:
|
||||
self.env['account.move'].browse(move_ids).is_being_sent = True
|
||||
for move in self:
|
||||
move.is_being_sent = bool(move.send_and_print_values)
|
||||
|
||||
def _compute_payment_reference(self):
|
||||
for move in self.filtered(lambda m: (
|
||||
@@ -4088,45 +4074,34 @@ class AccountMove(models.Model):
|
||||
self.env.ref('account.ir_cron_auto_post_draft_entry')._trigger()
|
||||
|
||||
@api.model
|
||||
def _cron_account_move_send(self, job_count=10, with_commit=True):
|
||||
'''
|
||||
:param job_count: The maximum number of jobs to process if specified.
|
||||
:param with_commit: Flag indicating a commit should be made between each job.
|
||||
'''
|
||||
# Clean already processed wizards.
|
||||
self.env['account.move.send'].search([
|
||||
'|',
|
||||
('mode', '=', 'done'),
|
||||
'&',
|
||||
('mode', '=', 'invoice_single'),
|
||||
('create_date', '<=', fields.Datetime.now() - relativedelta(hours=24)),
|
||||
]).unlink()
|
||||
|
||||
# Process.
|
||||
def _cron_account_move_send(self, job_count=10):
|
||||
""" Handle Send & Print async processing.
|
||||
:param job_count: maximum number of jobs to process if specified.
|
||||
"""
|
||||
limit = job_count + 1
|
||||
to_process = self.env['account.move.send'].search([('mode', '=', 'invoice_multi')], limit=limit)
|
||||
|
||||
to_process = self.env['account.move']._read_group(
|
||||
[('send_and_print_values', '!=', False)],
|
||||
groupby=['company_id'],
|
||||
aggregates=['id:recordset'],
|
||||
limit=limit,
|
||||
)
|
||||
need_retrigger = len(to_process) > job_count
|
||||
if not to_process:
|
||||
return
|
||||
|
||||
for wizard in to_process[:job_count]:
|
||||
move_to_lock = wizard.move_ids
|
||||
if move_to_lock:
|
||||
try:
|
||||
with self.env.cr.savepoint(flush=False):
|
||||
self._cr.execute('SELECT * FROM account_move WHERE id IN %s FOR UPDATE NOWAIT', [tuple(move_to_lock.ids)])
|
||||
for _company, moves in to_process[:job_count]:
|
||||
try:
|
||||
# Lock moves
|
||||
with self.env.cr.savepoint(flush=False):
|
||||
self._cr.execute('SELECT * FROM account_move WHERE id IN %s FOR UPDATE NOWAIT', [tuple(moves.ids)])
|
||||
|
||||
except OperationalError as e:
|
||||
if e.pgcode == '55P03':
|
||||
_logger.debug('Another transaction already locked documents rows. Cannot process documents.')
|
||||
if not with_commit:
|
||||
raise UserError(_('This document is being sent by another process already.'))
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
wizard.action_send_and_print(from_cron=True)
|
||||
except OperationalError as e:
|
||||
if e.pgcode == '55P03':
|
||||
_logger.debug('Another transaction already locked documents rows. Cannot process documents.')
|
||||
else:
|
||||
raise
|
||||
|
||||
if with_commit:
|
||||
self.env.cr.commit()
|
||||
self.env['account.move.send']._process_send_and_print(moves)
|
||||
|
||||
if need_retrigger:
|
||||
self.env.ref('account.ir_cron_account_move_send')._trigger()
|
||||
@@ -4303,19 +4278,16 @@ class AccountMove(models.Model):
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
def _generate_pdf_and_send_invoice(self, template, from_cron=True, allow_fallback_pdf=True, **kwargs):
|
||||
def _generate_pdf_and_send_invoice(self, template, force_synchronous=True, allow_fallback_pdf=True, bypass_download=False, **kwargs):
|
||||
""" 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 force_synchronous: Flag indicating if the method should be done synchronously.
|
||||
:param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
|
||||
proforma PDF report instead.
|
||||
:param bypass_download: Don't trigger the action from action_send_and_print and get generated attachments_ids instead.
|
||||
"""
|
||||
composer_vals = self._get_pdf_and_send_invoice_vals(template, **kwargs)
|
||||
composer = self.env['account.move.send'].create(composer_vals)
|
||||
|
||||
# from_cron=True to log errors in chatter instead of raise
|
||||
return composer.action_send_and_print(from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
|
||||
return composer.action_send_and_print(force_synchronous=force_synchronous, allow_fallback_pdf=allow_fallback_pdf, bypass_download=bypass_download)
|
||||
|
||||
def get_invoice_pdf_report_attachment(self):
|
||||
if len(self) < 2 and self.invoice_pdf_report_id:
|
||||
|
||||
@@ -161,7 +161,7 @@ class TestAccountComposerPerformance(AccountTestInvoicingCommon, MailCommon):
|
||||
.create({'mail_template_id': move_template.id})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
composer.action_send_and_print(from_cron=True)
|
||||
composer.action_send_and_print(force_synchronous=True)
|
||||
|
||||
# check results: emails (mailing mode when being in multi)
|
||||
self.assertEqual(len(self._mails), 20, 'Should send an email to each invoice followers (accountman + partner)')
|
||||
@@ -492,7 +492,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
# Process.
|
||||
results = wizard.action_send_and_print()
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertRecordValues(wizard, [{'mode': 'done'}])
|
||||
self.assertFalse(invoice.send_and_print_values)
|
||||
|
||||
# The PDF has been successfully generated.
|
||||
pdf_report = invoice.invoice_pdf_report_id
|
||||
@@ -508,7 +508,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
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.assertFalse(invoice.send_and_print_values)
|
||||
self.assertRecordValues(invoice, [{'invoice_pdf_report_id': pdf_report.id}])
|
||||
invoice_attachments = self.env['ir.attachment'].search([
|
||||
('res_model', '=', invoice._name),
|
||||
@@ -578,7 +578,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
self.assertTrue(invoice2.is_being_sent)
|
||||
|
||||
# Run the CRON.
|
||||
wizard.action_send_and_print(from_cron=True)
|
||||
wizard.action_send_and_print(force_synchronous=True)
|
||||
self.assertTrue(invoice1.invoice_pdf_report_id)
|
||||
invoice_attachments = self.env['ir.attachment'].search([
|
||||
('res_model', '=', invoice1._name),
|
||||
@@ -600,7 +600,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
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)
|
||||
wizard.action_send_and_print(force_synchronous=True)
|
||||
invoice_attachments = self.env['ir.attachment'].search([
|
||||
('res_model', '=', invoice1._name),
|
||||
('res_id', '=', invoice1.id),
|
||||
@@ -645,7 +645,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
# Process.
|
||||
results = wizard.action_send_and_print()
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertRecordValues(wizard, [{'mode': 'done'}])
|
||||
self.assertEqual((invoice1 + invoice2).mapped('send_and_print_values'), [False, False])
|
||||
self.assertTrue(invoice1.invoice_pdf_report_id)
|
||||
invoice_attachments = self.env['ir.attachment'].search([
|
||||
('res_model', '=', invoice1._name),
|
||||
@@ -667,7 +667,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
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)
|
||||
wizard.action_send_and_print(force_synchronous=True)
|
||||
invoice_attachments = self.env['ir.attachment'].search([
|
||||
('res_model', '=', invoice1._name),
|
||||
('res_id', '=', invoice1.id),
|
||||
@@ -864,7 +864,7 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
results = wizard.action_send_and_print(allow_fallback_pdf=True)
|
||||
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertRecordValues(wizard, [{'mode': 'done'}])
|
||||
self.assertFalse(invoice.send_and_print_values)
|
||||
|
||||
# The PDF is not generated but a proforma.
|
||||
self.assertFalse(invoice.invoice_pdf_report_id)
|
||||
@@ -882,52 +882,12 @@ class TestAccountMoveSend(TestAccountMoveSendCommon):
|
||||
results = wizard.action_send_and_print(allow_fallback_pdf=True)
|
||||
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertRecordValues(wizard, [{'mode': 'done'}])
|
||||
self.assertFalse(invoice.send_and_print_values)
|
||||
|
||||
# The PDF is generated even in case of error, but invoice_pdf_report_id is not set
|
||||
self.assertFalse(invoice.invoice_pdf_report_id)
|
||||
self.assertTrue(invoice.message_main_attachment_id)
|
||||
|
||||
def test_with_unlink_invoices(self):
|
||||
invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
|
||||
wizard = self.create_send_and_print(invoice)
|
||||
|
||||
invoice.button_draft()
|
||||
invoice.unlink()
|
||||
|
||||
results = wizard.action_send_and_print(allow_fallback_pdf=True)
|
||||
self.assertEqual(results['type'], 'ir.actions.act_window_close')
|
||||
|
||||
self.env.ref('account.ir_cron_account_move_send').method_direct_trigger()
|
||||
self.assertFalse(wizard.exists())
|
||||
|
||||
def test_unlink_wizard_invoice_single(self):
|
||||
invoice = self.init_invoice("out_invoice", amounts=[1000], post=True)
|
||||
wizard = self.create_send_and_print(invoice)
|
||||
|
||||
def _hook_if_errors(self, moves_data, from_cron=False, allow_fallback_pdf=False):
|
||||
raise UserError('Test Error')
|
||||
|
||||
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
|
||||
invoice_data['error'] = 'test_error'
|
||||
|
||||
# Process with an error so that the wizard doesn't reach 'done' state
|
||||
with (
|
||||
patch.object(type(wizard), '_hook_if_errors', _hook_if_errors),
|
||||
patch.object(type(wizard), '_hook_invoice_document_before_pdf_report_render', _hook_invoice_document_before_pdf_report_render)
|
||||
):
|
||||
with self.assertRaises(UserError), self.cr.savepoint():
|
||||
wizard.action_send_and_print(allow_fallback_pdf=False)
|
||||
|
||||
# If cron is triggered within the same day wizard is still exists. We don't want to delete an opened wizard.
|
||||
self.env.ref('account.ir_cron_account_move_send').method_direct_trigger()
|
||||
self.assertTrue(wizard.exists())
|
||||
|
||||
# If cron is triggered one day or later than the creation it should be garbage collected.
|
||||
with freeze_time(fields.Datetime.now() + relativedelta(hours=24)):
|
||||
self.env.ref('account.ir_cron_account_move_send').method_direct_trigger()
|
||||
self.assertFalse(wizard.exists())
|
||||
|
||||
def test_with_empty_mail_template(self):
|
||||
""" Test you can use the send & print wizard without any mail template. """
|
||||
self.partner_a.email = "turlututu@tsointsoin"
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo.exceptions import UserError
|
||||
from odoo.tools.misc import get_lang
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_name = 'account.move.send'
|
||||
_description = "Account Move Send"
|
||||
|
||||
@@ -16,7 +16,6 @@ class AccountMoveSend(models.Model):
|
||||
selection=[
|
||||
('invoice_single', "Invoice Single"),
|
||||
('invoice_multi', "Invoice Multi"),
|
||||
('done', "Done"),
|
||||
],
|
||||
compute='_compute_mode',
|
||||
readonly=False,
|
||||
@@ -100,19 +99,71 @@ class AccountMoveSend(models.Model):
|
||||
.with_context(lang=lang)\
|
||||
._render_field(field, move.ids, **kwargs)[move._origin.id]
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
def _get_default_mail_body(self, moves, mail_template, mail_lang):
|
||||
if mail_template and len(moves) == 1:
|
||||
return self._get_mail_default_field_value_from_template(
|
||||
mail_template,
|
||||
mail_lang,
|
||||
moves,
|
||||
'body_html',
|
||||
options={'post_process': True},
|
||||
)
|
||||
return None
|
||||
|
||||
def _get_default_mail_subject(self, moves, mail_template, mail_lang):
|
||||
if mail_template and len(moves) == 1:
|
||||
return self._get_mail_default_field_value_from_template(
|
||||
mail_template,
|
||||
mail_lang,
|
||||
moves,
|
||||
'subject',
|
||||
)
|
||||
return None
|
||||
|
||||
def _get_default_mail_partner_ids(self, moves, mail_template, mail_lang):
|
||||
partners = self.env['res.partner'].with_company(moves.company_id)
|
||||
if mail_template and len(moves) == 1:
|
||||
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, mail_lang, moves, 'partner_to')
|
||||
partner_ids = mail_template._parse_partner_to(partner_to)
|
||||
partners |= self.env['res.partner'].sudo().browse(partner_ids).exists()
|
||||
return partners
|
||||
|
||||
def _get_default_mail_attachments_widget(self, moves, mail_template):
|
||||
return self._get_placeholder_mail_attachments_data(moves) \
|
||||
+ self._get_invoice_extra_attachments_data(moves) \
|
||||
+ self._get_mail_template_attachments_data(mail_template)
|
||||
|
||||
def _get_wizard_values(self, move=None):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'move_ids': [Command.set(move.ids)],
|
||||
'mail_template_id': self.mail_template_id.id,
|
||||
'checkbox_download': self.checkbox_download,
|
||||
'checkbox_send_mail': self.checkbox_send_mail,
|
||||
'download': self.checkbox_download,
|
||||
'send_mail': self.checkbox_send_mail,
|
||||
}
|
||||
|
||||
def _get_mail_move_data(self, move, wizard=None):
|
||||
mail_template_id = move.send_and_print_values and move.send_and_print_values.get('mail_template_id')
|
||||
mail_template = wizard and wizard.mail_template_id or self.env['mail.template'].browse(mail_template_id)
|
||||
mail_lang = mail_template._render_lang([move.id]).get(move.id) if mail_template else get_lang(self.env).code
|
||||
return {
|
||||
'mail_template_id': mail_template,
|
||||
'mail_lang': mail_lang,
|
||||
'mail_body': wizard and wizard.mail_body or self._get_default_mail_body(move, mail_template, mail_lang),
|
||||
'mail_subject': wizard and wizard.mail_subject or self._get_default_mail_subject(move, mail_template, mail_lang),
|
||||
'mail_partner_ids': wizard and wizard.mail_partner_ids or self._get_default_mail_partner_ids(move, mail_template, mail_lang),
|
||||
'mail_attachments_widget': wizard and wizard.mail_attachments_widget or self._get_default_mail_attachments_widget(move, mail_template),
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -177,10 +228,7 @@ class AccountMoveSend(models.Model):
|
||||
@api.depends('move_ids')
|
||||
def _compute_mode(self):
|
||||
for wizard in self:
|
||||
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
|
||||
wizard.mode = 'invoice_single' if len(wizard.move_ids) == 1 else 'invoice_multi'
|
||||
|
||||
@api.depends('move_ids')
|
||||
def _compute_enable_download(self):
|
||||
@@ -192,7 +240,7 @@ class AccountMoveSend(models.Model):
|
||||
for wizard in self:
|
||||
wizard.checkbox_download = wizard.enable_download and wizard.company_id.invoice_is_download
|
||||
|
||||
@api.depends('mode', 'checkbox_send_mail')
|
||||
@api.depends('checkbox_send_mail')
|
||||
def _compute_send_mail_extra_fields(self):
|
||||
for wizard in self:
|
||||
wizard.enable_send_mail = wizard.mode in ('invoice_single', 'invoice_multi')
|
||||
@@ -225,51 +273,17 @@ class AccountMoveSend(models.Model):
|
||||
@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':
|
||||
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 = mail_template._parse_partner_to(partner_to)
|
||||
partners |= self.env['res.partner'].sudo().browse(partner_ids).exists()
|
||||
wizard.mail_partner_ids = partners
|
||||
else:
|
||||
wizard.mail_partner_ids = []
|
||||
wizard.mail_partner_ids = self._get_default_mail_partner_ids(wizard.move_ids, wizard.mail_template_id, wizard.mail_lang)
|
||||
|
||||
@api.depends('mail_template_id', 'mail_lang')
|
||||
def _compute_mail_subject(self):
|
||||
for wizard in self:
|
||||
if wizard.mail_template_id and wizard.mode == 'invoice_single':
|
||||
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
|
||||
wizard.mail_subject = self._get_default_mail_subject(wizard.move_ids, wizard.mail_template_id, wizard.mail_lang)
|
||||
|
||||
@api.depends('mail_template_id', 'mail_lang')
|
||||
def _compute_mail_body(self):
|
||||
for wizard in self:
|
||||
if wizard.mail_template_id and wizard.mode == 'invoice_single':
|
||||
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
|
||||
wizard.mail_body = self._get_default_mail_body(wizard.move_ids, wizard.mail_template_id, wizard.mail_lang)
|
||||
|
||||
@api.depends('mail_template_id', 'mail_lang')
|
||||
def _compute_mail_attachments_widget(self):
|
||||
@@ -277,9 +291,7 @@ class AccountMoveSend(models.Model):
|
||||
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) \
|
||||
self._get_default_mail_attachments_widget(wizard.move_ids, wizard.mail_template_id) \
|
||||
+ manual_attachments_data
|
||||
else:
|
||||
wizard.mail_attachments_widget = []
|
||||
@@ -288,32 +300,29 @@ class AccountMoveSend(models.Model):
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _need_invoice_document(self, invoice):
|
||||
""" Determine if we need to generate the documents for the invoice passed as parameter.
|
||||
|
||||
:param invoice: An account.move record.
|
||||
:return: True if the PDF / electronic documents must be generated, False otherwise.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return not invoice.invoice_pdf_report_id and invoice.state == 'posted'
|
||||
|
||||
@api.model
|
||||
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()
|
||||
return
|
||||
|
||||
@api.model
|
||||
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
|
||||
|
||||
@@ -328,14 +337,12 @@ class AccountMoveSend(models.Model):
|
||||
'res_field': 'invoice_pdf_report_file', # Binary field
|
||||
}
|
||||
|
||||
@api.model
|
||||
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'] = {
|
||||
@@ -346,18 +353,18 @@ class AccountMoveSend(models.Model):
|
||||
'res_id': invoice.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
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()
|
||||
return
|
||||
|
||||
@api.model
|
||||
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.
|
||||
"""
|
||||
@@ -367,9 +374,9 @@ class AccountMoveSend(models.Model):
|
||||
invoice.invalidate_recordset(fnames=['invoice_pdf_report_id', 'invoice_pdf_report_file'])
|
||||
invoice.is_move_sent = True
|
||||
|
||||
@api.model
|
||||
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
|
||||
@@ -383,25 +390,21 @@ class AccountMoveSend(models.Model):
|
||||
|
||||
move.with_context(no_new_invoice=True).message_post(body=error)
|
||||
|
||||
@api.model
|
||||
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
|
||||
to_send_mail = {move: move_data for move, move_data in moves_data.items() if move_data.get('send_mail')}
|
||||
self._send_mails(to_send_mail)
|
||||
|
||||
# Send mail.
|
||||
if send_mail:
|
||||
self._send_mails(moves_data)
|
||||
|
||||
def _send_mail(self, move, **kwargs):
|
||||
@api.model
|
||||
def _send_mail(self, move, mail_template, **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
|
||||
|
||||
new_message = move\
|
||||
.with_context(
|
||||
@@ -425,18 +428,14 @@ class AccountMoveSend(models.Model):
|
||||
'res_id': new_message.id,
|
||||
})
|
||||
|
||||
def _get_mail_params(self, move):
|
||||
self.ensure_one()
|
||||
|
||||
if self.mode != 'invoice_single':
|
||||
return
|
||||
|
||||
@api.model
|
||||
def _get_mail_params(self, move, move_data):
|
||||
# We must ensure the newly created PDF are added. At this point, the PDF has been generated but not added
|
||||
# to 'mail_attachments_widget'.
|
||||
mail_attachments_widget = move_data.get('mail_attachments_widget')
|
||||
seen_attachment_ids = set()
|
||||
mail_attachments_widget = self.mail_attachments_widget or []
|
||||
to_exclude = set(x['name'] for x in mail_attachments_widget if x.get('skip'))
|
||||
for attachment_data in self._get_invoice_extra_attachments_data(self.move_ids) + mail_attachments_widget:
|
||||
to_exclude = {x['name'] for x in mail_attachments_widget if x.get('skip')}
|
||||
for attachment_data in self._get_invoice_extra_attachments_data(move) + mail_attachments_widget:
|
||||
if attachment_data['name'] in to_exclude:
|
||||
continue
|
||||
|
||||
@@ -453,20 +452,20 @@ class AccountMoveSend(models.Model):
|
||||
]
|
||||
|
||||
return {
|
||||
'body': self.mail_body,
|
||||
'subject': self.mail_subject,
|
||||
'partner_ids': self.mail_partner_ids.ids,
|
||||
'body': move_data['mail_body'],
|
||||
'subject': move_data['mail_subject'],
|
||||
'partner_ids': move_data['mail_partner_ids'].ids,
|
||||
'attachments': mail_attachments,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _send_mails(self, moves_data):
|
||||
self.ensure_one()
|
||||
subtype = self.env.ref('mail.mt_comment')
|
||||
mail_template = self.mail_template_id
|
||||
|
||||
for move, move_data in moves_data.items():
|
||||
form = move_data['_form']
|
||||
mail_params = form._get_mail_params(move)
|
||||
mail_template = move_data['mail_template_id']
|
||||
mail_lang = move_data['mail_lang']
|
||||
mail_params = self._get_mail_params(move, move_data)
|
||||
if not mail_params:
|
||||
continue
|
||||
|
||||
@@ -474,47 +473,47 @@ class AccountMoveSend(models.Model):
|
||||
attachment = move_data['proforma_pdf_attachment']
|
||||
mail_params['attachments'].append((attachment.name, attachment.raw))
|
||||
|
||||
mail_lang = form.mail_lang
|
||||
email_from = form._get_mail_default_field_value_from_template(mail_template, mail_lang, move, 'email_from')
|
||||
email_from = self._get_mail_default_field_value_from_template(mail_template, mail_lang, move, 'email_from')
|
||||
model_description = move.with_context(lang=mail_lang).type_name
|
||||
|
||||
form._send_mail(
|
||||
self._send_mail(
|
||||
move,
|
||||
mail_template,
|
||||
subtype_id=subtype.id,
|
||||
model_description=model_description,
|
||||
email_from=email_from,
|
||||
**mail_params,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _can_commit(self):
|
||||
""" Helper to know if we can commit the current transaction or not.
|
||||
|
||||
:return: True if commit is accepted, False otherwise.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return not tools.config['test_enable'] and not modules.module.current_test
|
||||
|
||||
@api.model
|
||||
def _call_web_service_before_invoice_pdf_render(self, invoices_data):
|
||||
# TO OVERRIDE
|
||||
# call a web service before the pdfs are rendered
|
||||
self.ensure_one()
|
||||
return
|
||||
|
||||
@api.model
|
||||
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
|
||||
# TO OVERRIDE
|
||||
# call a web service after the pdfs are rendered
|
||||
self.ensure_one()
|
||||
return
|
||||
|
||||
@api.model
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
if self._need_invoice_document(invoice):
|
||||
self._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']:
|
||||
@@ -534,10 +533,9 @@ class AccountMoveSend(models.Model):
|
||||
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) and not invoice_data.get('error'):
|
||||
form._prepare_invoice_pdf_report(invoice, invoice_data)
|
||||
form._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
|
||||
if self._need_invoice_document(invoice) and not invoice_data.get('error'):
|
||||
self._prepare_invoice_pdf_report(invoice, invoice_data)
|
||||
self._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
|
||||
|
||||
# Cleanup the error if we don't want to block the regular pdf generation.
|
||||
if allow_fallback_pdf:
|
||||
@@ -560,13 +558,12 @@ class AccountMoveSend(models.Model):
|
||||
|
||||
# Create and link the generated documents to the invoice if the web-service didn't failed.
|
||||
for invoice, invoice_data in invoices_data_pdf.items():
|
||||
form = invoice_data['_form']
|
||||
if not invoice_data.get('error') and form._need_invoice_document(invoice):
|
||||
form._link_invoice_documents(invoice, invoice_data)
|
||||
if not invoice_data.get('error') and self._need_invoice_document(invoice):
|
||||
self._link_invoice_documents(invoice, invoice_data)
|
||||
|
||||
@api.model
|
||||
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():
|
||||
@@ -577,46 +574,38 @@ class AccountMoveSend(models.Model):
|
||||
invoice_data['proforma_pdf_attachment'] = self.env['ir.attachment']\
|
||||
.create(invoice_data.pop('proforma_pdf_attachment_values'))
|
||||
|
||||
@api.model
|
||||
def _download(self, attachment_ids):
|
||||
def _download(self, attachment_ids, moves_data=None):
|
||||
""" Download the PDF or the zip of PDF if we are in 'multi' mode. """
|
||||
if len(attachment_ids) == 1:
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': f'/web/content/{attachment_ids[0]}?download=true',
|
||||
'url': f"/web/content/{attachment_ids[0]}?download=true",
|
||||
'close': True, # close the wizard
|
||||
}
|
||||
else:
|
||||
filename = self.move_ids._get_invoice_report_filename(extension='zip') if len(self.move_ids) == 1 else 'invoices.zip'
|
||||
filename = next(iter(moves_data))._get_invoice_report_filename(extension='zip') if len(moves_data) == 1 else 'invoices.zip'
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': f"/account/export_zip_documents?{url_encode({'ids': attachment_ids, 'filename': filename})}",
|
||||
'close': True,
|
||||
}
|
||||
|
||||
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.
|
||||
@api.model
|
||||
def _process_send_and_print(self, moves, wizard=None, allow_fallback_pdf=False, **kwargs):
|
||||
""" Process the moves given their individual configuration set on move.send_and_print_values.
|
||||
:param moves: account.move to process
|
||||
:param wizard: account.move.send wizard if exists. If not 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()
|
||||
from_cron = not wizard
|
||||
|
||||
download = self.enable_download and self.checkbox_download
|
||||
|
||||
moves_data = {move: {} for move in self.move_ids}
|
||||
if len(moves_data) == 1:
|
||||
moves_data[self.move_ids]['_form'] = self
|
||||
else:
|
||||
for move in self.move_ids:
|
||||
vals = self._get_available_field_values_in_multi(move)
|
||||
moves_data[move]['_form'] = self.create(vals)
|
||||
|
||||
if self.mode == 'invoice_multi' and not from_cron and not download:
|
||||
self.env.ref('account.ir_cron_account_move_send')._trigger()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
moves_data = {
|
||||
move: {
|
||||
**(move.send_and_print_values if not wizard else wizard._get_wizard_values(move)),
|
||||
**self._get_mail_move_data(move, wizard),
|
||||
}
|
||||
for move in moves
|
||||
}
|
||||
|
||||
# Generate all invoice documents.
|
||||
self._generate_invoice_documents(moves_data, allow_fallback_pdf=allow_fallback_pdf)
|
||||
@@ -636,22 +625,43 @@ class AccountMoveSend(models.Model):
|
||||
if success:
|
||||
self._hook_if_success(success, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
|
||||
|
||||
self.mode = 'done'
|
||||
# Update send and print values of moves
|
||||
for move in moves:
|
||||
if move.send_and_print_values:
|
||||
move.send_and_print_values = False
|
||||
|
||||
if download:
|
||||
to_download = {move: move_data for move, move_data in moves_data.items() if move_data.get('download')}
|
||||
if to_download:
|
||||
attachment_ids = []
|
||||
for move in self.move_ids:
|
||||
# _get_invoice_extra_attachments retrieves invoice PDF and other possible xml, etc.
|
||||
attachment_ids += self._get_invoice_extra_attachments(move).ids or moves_data.get(move).get('proforma_pdf_attachment').ids
|
||||
for move, move_data in to_download.items():
|
||||
attachment_ids += self._get_invoice_extra_attachments(move).ids or move_data.get('proforma_pdf_attachment').ids
|
||||
if attachment_ids:
|
||||
if from_cron:
|
||||
if kwargs.get('bypass_download'):
|
||||
return attachment_ids
|
||||
else:
|
||||
return self._download(attachment_ids)
|
||||
return self._download(attachment_ids, to_download)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def action_cancel(self):
|
||||
def action_send_and_print(self, force_synchronous=False, allow_fallback_pdf=False, **kwargs):
|
||||
""" Create the documents and send them to the end customers.
|
||||
If we are sending multiple invoices and not downloading them we will process the moves asynchronously.
|
||||
:param force_synchronous: Flag indicating if the method should be done synchronously.
|
||||
:param allow_fallback_pdf: In case of error when generating the documents for invoices, generate a
|
||||
proforma PDF report instead.
|
||||
"""
|
||||
self.ensure_one()
|
||||
self.unlink()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
force_synchronous = force_synchronous or self.checkbox_download
|
||||
process_later = self.mode == 'invoice_multi' and not force_synchronous
|
||||
if process_later:
|
||||
# Set sending information on moves
|
||||
for move in self.move_ids:
|
||||
move.send_and_print_values = self._get_wizard_values(move)
|
||||
self.env.ref('account.ir_cron_account_move_send')._trigger()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
return self._process_send_and_print(
|
||||
self.move_ids,
|
||||
wizard=self,
|
||||
allow_fallback_pdf=allow_fallback_pdf,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -95,8 +95,7 @@
|
||||
</button>
|
||||
<button string="Cancel"
|
||||
data-hotkey="x"
|
||||
name="action_cancel"
|
||||
type="object"
|
||||
special="cancel"
|
||||
class="btn-secondary"/>
|
||||
</footer>
|
||||
</form>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
@api.model
|
||||
def _get_mail_attachment_from_doc(self, doc):
|
||||
attachment_sudo = doc.sudo().attachment_id
|
||||
if attachment_sudo.res_model and attachment_sudo.res_id:
|
||||
return attachment_sudo
|
||||
return self.env['ir.attachment']
|
||||
|
||||
@api.model
|
||||
def _get_invoice_extra_attachments(self, move):
|
||||
""" Returns extra edi attachments
|
||||
"""
|
||||
""" Returns extra edi attachments. """
|
||||
result = super()._get_invoice_extra_attachments(move)
|
||||
for doc in move.edi_document_ids:
|
||||
result += self._get_mail_attachment_from_doc(doc)
|
||||
|
||||
@@ -13,17 +13,17 @@ from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
enable_ubl_cii_xml = fields.Boolean(compute='_compute_enable_ubl_cii_xml')
|
||||
checkbox_ubl_cii_label = fields.Char(compute='_compute_checkbox_ubl_cii_label')
|
||||
checkbox_ubl_cii_xml = fields.Boolean(compute='_compute_checkbox_ubl_cii_xml', store=True, readonly=False)
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
def _get_wizard_values(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_available_field_values_in_multi(move)
|
||||
values['checkbox_ubl_cii_xml'] = self.checkbox_ubl_cii_xml
|
||||
values = super()._get_wizard_values(move)
|
||||
values['ubl_cii_xml'] = self.checkbox_ubl_cii_xml
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -87,11 +87,12 @@ class AccountMoveSend(models.Model):
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
|
||||
|
||||
if self.mode == 'invoice_single' and self.checkbox_ubl_cii_xml and invoice._need_ubl_cii_xml():
|
||||
if invoice_data.get('ubl_cii_xml') and invoice._need_ubl_cii_xml():
|
||||
builder = invoice.partner_id._get_edi_builder()
|
||||
xml_content, errors = builder._export_invoice(invoice)
|
||||
filename = builder._export_invoice_filename(invoice)
|
||||
@@ -101,8 +102,7 @@ class AccountMoveSend(models.Model):
|
||||
invoice_data['error'] = "".join([
|
||||
_("Errors occured while creating the EDI document (format: %s):", builder._description),
|
||||
"\n",
|
||||
"<p><li>" + "</li><li>".join(errors) + "</li></p>" if self.mode == 'invoice_multi' \
|
||||
else "\n".join(errors)
|
||||
"<p><li>" + "</li><li>".join(errors) + "</li></p>",
|
||||
])
|
||||
invoice_data['error_but_continue'] = True
|
||||
else:
|
||||
@@ -119,6 +119,7 @@ class AccountMoveSend(models.Model):
|
||||
'builder': builder,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
|
||||
|
||||
@@ -7,7 +7,7 @@ from odoo import api, fields, models, _
|
||||
from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
checkbox_send_peppol = fields.Boolean(
|
||||
@@ -23,6 +23,12 @@ class AccountMoveSend(models.Model):
|
||||
)
|
||||
account_peppol_edi_mode_info = fields.Char(compute='_compute_account_peppol_edi_mode_info')
|
||||
|
||||
def _get_wizard_values(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_wizard_values(move)
|
||||
values['send_peppol'] = self.checkbox_send_peppol
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -96,67 +102,55 @@ class AccountMoveSend(models.Model):
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def action_send_and_print(self, from_cron=False, allow_fallback_pdf=False):
|
||||
def action_send_and_print(self, force_synchronous=False, allow_fallback_pdf=False, **kwargs):
|
||||
# Extends 'account' to force ubl xml checkbox if sending via peppol
|
||||
self.ensure_one()
|
||||
|
||||
if all([self.checkbox_send_peppol, self.enable_peppol, self.enable_ubl_cii_xml, not self.checkbox_ubl_cii_xml]):
|
||||
self.checkbox_ubl_cii_xml = True
|
||||
|
||||
return super().action_send_and_print(from_cron, allow_fallback_pdf)
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
# Extends 'account' to force ubl xml checkbox if sending via peppol
|
||||
# if there are many invoices to be sent
|
||||
values = super()._get_available_field_values_in_multi(move)
|
||||
if self.enable_peppol and self.checkbox_send_peppol:
|
||||
values['checkbox_ubl_cii_xml'] = True
|
||||
|
||||
return values
|
||||
return super().action_send_and_print(force_synchronous=force_synchronous, allow_fallback_pdf=allow_fallback_pdf, **kwargs)
|
||||
|
||||
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
|
||||
# Overrides 'account'
|
||||
super()._call_web_service_after_invoice_pdf_render(invoices_data)
|
||||
|
||||
if not self.checkbox_send_peppol:
|
||||
return
|
||||
|
||||
params = {'documents': []}
|
||||
|
||||
invoices_data_peppol = {}
|
||||
for invoice, invoice_data in invoices_data.items():
|
||||
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 and invoice.peppol_move_state not in ('processing', 'canceled', 'done'):
|
||||
xml_file = invoice.ubl_cii_xml_id.raw
|
||||
filename = invoice.ubl_cii_xml_id.name
|
||||
else:
|
||||
continue
|
||||
if invoice_data.get('send_peppol'):
|
||||
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 and invoice.peppol_move_state not in ('processing', 'canceled', 'done'):
|
||||
xml_file = invoice.ubl_cii_xml_id.raw
|
||||
filename = invoice.ubl_cii_xml_id.name
|
||||
else:
|
||||
continue
|
||||
|
||||
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
|
||||
invoice.peppol_move_state = 'error'
|
||||
invoice_data['error'] = _('The partner is missing Peppol EAS and/or Endpoint identifier.')
|
||||
continue
|
||||
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
|
||||
invoice.peppol_move_state = 'error'
|
||||
invoice_data['error'] = _('The partner is missing Peppol EAS and/or Endpoint identifier.')
|
||||
continue
|
||||
|
||||
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
|
||||
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"{invoice.partner_id.peppol_eas}:{invoice.partner_id.peppol_endpoint}"
|
||||
params['documents'].append({
|
||||
'filename': filename,
|
||||
'receiver': receiver_identification,
|
||||
'ubl': b64encode(xml_file).decode(),
|
||||
})
|
||||
invoices_data_peppol[invoice] = invoice_data
|
||||
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(),
|
||||
})
|
||||
invoices_data_peppol[invoice] = invoice_data
|
||||
|
||||
if not params['documents']:
|
||||
return
|
||||
|
||||
edi_user = self.company_id.account_edi_proxy_client_ids.filtered(
|
||||
edi_user = next(iter(invoices_data)).company_id.account_edi_proxy_client_ids.filtered(
|
||||
lambda u: u.proxy_type == 'peppol')
|
||||
|
||||
try:
|
||||
|
||||
@@ -103,7 +103,7 @@ class TestUBLDK(TestUBLCommon, TestAccountMoveSendCommon):
|
||||
],
|
||||
})
|
||||
invoice.action_post()
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, from_cron=False, allow_fallback_pdf=False)
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
|
||||
return invoice
|
||||
|
||||
#########
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from odoo import _, api, fields, models, SUPERUSER_ID
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
l10n_es_edi_facturae_enable_xml = fields.Boolean(compute='_compute_send_mail_extra_fields')
|
||||
@@ -9,6 +10,12 @@ class AccountMoveSend(models.Model):
|
||||
default=True,
|
||||
)
|
||||
|
||||
def _get_wizard_values(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_wizard_values(move)
|
||||
values['l10n_es_edi_facturae_xml'] = self.l10n_es_edi_facturae_checkbox_xml
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -52,18 +59,18 @@ class AccountMoveSend(models.Model):
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
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():
|
||||
if invoice_data.get('l10n_es_edi_facturae_xml') and invoice._l10n_es_edi_facturae_get_default_enable():
|
||||
xml_content, errors = invoice._l10n_es_edi_facturae_render_facturae()
|
||||
if errors:
|
||||
invoice_data['error'] = "".join([
|
||||
_("Errors occured while creating the EDI document (format: %s):", "Facturae"),
|
||||
"\n",
|
||||
"<p><li>" + "</li><li>".join(errors) + "</li></p>" if self.mode == 'invoice_multi' \
|
||||
else "\n".join(errors)
|
||||
"<p><li>" + "</li><li>".join(errors) + "</li></p>",
|
||||
])
|
||||
else:
|
||||
invoice_data['l10n_es_edi_facturae_attachment_values'] = {
|
||||
|
||||
@@ -113,6 +113,5 @@ class ResCompany(models.Model):
|
||||
|
||||
@api.depends("account_edi_proxy_client_ids")
|
||||
def _compute_l10n_it_edi_proxy_user_id(self):
|
||||
self.ensure_one()
|
||||
for company in self:
|
||||
company.l10n_it_edi_proxy_user_id = company.account_edi_proxy_client_ids.filtered(lambda x: x.proxy_type == 'l10n_it_edi')
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestItAccountMoveSend(TestItEdi, TestAccountMoveSendCommon):
|
||||
results = wizard.action_send_and_print()
|
||||
|
||||
# Asserts
|
||||
self.assertEqual(wizard.mode, 'done')
|
||||
self.assertEqual((invoice1 + invoice2).mapped('send_and_print_values'), [False, False])
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertEqual(1, len(self.get_attachments(invoice1.id)))
|
||||
self.assertTrue(invoice1.invoice_pdf_report_id)
|
||||
@@ -75,7 +75,7 @@ class TestItAccountMoveSend(TestItEdi, TestAccountMoveSendCommon):
|
||||
results = wizard.action_send_and_print()
|
||||
|
||||
# Asserts
|
||||
self.assertEqual(wizard.mode, 'done')
|
||||
self.assertEqual((invoice1 + invoice2).mapped('send_and_print_values'), [False, False])
|
||||
self.assertEqual(results['type'], 'ir.actions.act_url')
|
||||
self.assertEqual(2, len(self.get_attachments(invoice1.id)))
|
||||
self.assertTrue(invoice1.invoice_pdf_report_id)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
l10n_it_edi_warning_message = fields.Html(compute='_compute_l10n_it_edi_warning_message')
|
||||
@@ -26,6 +26,13 @@ class AccountMoveSend(models.Model):
|
||||
readonly=False,
|
||||
help="Send the e-invoice XML to the Italian Tax Agency.")
|
||||
|
||||
def _get_wizard_values(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_wizard_values(move)
|
||||
values['l10n_it_edi_checkbox_xml_export'] = self.l10n_it_edi_checkbox_xml_export
|
||||
values['l10n_it_edi_checkbox_send'] = self.l10n_it_edi_checkbox_send
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE/CONSTRAINS METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -60,57 +67,52 @@ class AccountMoveSend(models.Model):
|
||||
for wizard in self:
|
||||
wizard.l10n_it_edi_checkbox_send = not wizard.l10n_it_edi_readonly_send and wizard.l10n_it_edi_enable_send
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_available_field_values_in_multi(move)
|
||||
export = self.l10n_it_edi_checkbox_xml_export and move._l10n_it_edi_ready_for_xml_export()
|
||||
values['l10n_it_edi_checkbox_xml_export'] = export
|
||||
values['l10n_it_edi_checkbox_send'] = export or (bool(move.l10n_it_edi_attachment_id) and not move.l10n_it_edi_state)
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _need_invoice_document(self, invoice):
|
||||
# EXTENDS 'account'
|
||||
return super()._need_invoice_document(invoice) and not invoice.l10n_it_edi_attachment_id
|
||||
|
||||
@api.model
|
||||
def _get_invoice_extra_attachments(self, move):
|
||||
def _get_invoice_extra_attachments(self, invoice):
|
||||
# EXTENDS 'account'
|
||||
return super()._get_invoice_extra_attachments(move) + move.l10n_it_edi_attachment_id
|
||||
return super()._get_invoice_extra_attachments(invoice) + invoice.l10n_it_edi_attachment_id
|
||||
|
||||
@api.model
|
||||
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
|
||||
if self.l10n_it_edi_checkbox_xml_export and invoice._l10n_it_edi_ready_for_xml_export():
|
||||
if invoice_data.get('l10n_it_edi_checkbox_xml_export') and invoice._l10n_it_edi_ready_for_xml_export():
|
||||
if errors := invoice._l10n_it_edi_export_data_check():
|
||||
message = _("Errors occured while creating the e-invoice file.")
|
||||
message += "\n- " + "\n- ".join(errors)
|
||||
invoice_data['error'] = message
|
||||
|
||||
@api.model
|
||||
def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
super()._hook_invoice_document_after_pdf_report_render(invoice, invoice_data)
|
||||
if self.l10n_it_edi_checkbox_xml_export and invoice._l10n_it_edi_ready_for_xml_export():
|
||||
if invoice_data.get('l10n_it_edi_checkbox_xml_export') and invoice._l10n_it_edi_ready_for_xml_export():
|
||||
invoice_data['l10n_it_edi_values'] = invoice._l10n_it_edi_get_attachment_values(
|
||||
pdf_values=invoice_data['pdf_attachment_values'])
|
||||
|
||||
@api.model
|
||||
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
|
||||
# EXTENDS 'account'
|
||||
super()._call_web_service_after_invoice_pdf_render(invoices_data)
|
||||
if self.l10n_it_edi_checkbox_send:
|
||||
attachments_vals = {}
|
||||
moves = self.env['account.move']
|
||||
for move in invoices_data:
|
||||
if move._l10n_it_edi_ready_for_xml_export():
|
||||
moves |= move
|
||||
if attachment := move.l10n_it_edi_attachment_id:
|
||||
attachments_vals[move] = {'name': attachment.name, 'raw': attachment.raw}
|
||||
else:
|
||||
attachments_vals[move] = invoices_data[move]['l10n_it_edi_values']
|
||||
moves._l10n_it_edi_send(attachments_vals)
|
||||
attachments_vals = {}
|
||||
moves = self.env['account.move']
|
||||
for move, move_data in invoices_data.items():
|
||||
if move_data.get('l10n_it_edi_checkbox_send') and move._l10n_it_edi_ready_for_xml_export():
|
||||
moves |= move
|
||||
if attachment := move.l10n_it_edi_attachment_id:
|
||||
attachments_vals[move] = {'name': attachment.name, 'raw': attachment.raw}
|
||||
else:
|
||||
attachments_vals[move] = invoices_data[move]['l10n_it_edi_values']
|
||||
moves._l10n_it_edi_send(attachments_vals)
|
||||
|
||||
def _link_invoice_documents(self, invoice, invoice_data):
|
||||
# EXTENDS 'account'
|
||||
|
||||
@@ -104,7 +104,7 @@ class TestUBLRO(TestUBLCommon):
|
||||
})
|
||||
invoice = self.create_move("out_invoice", send=False)
|
||||
with self.assertRaisesRegex(UserError, "doesn't have a country code prefix in their Company ID"):
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, from_cron=False, allow_fallback_pdf=False)
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
|
||||
|
||||
def test_export_no_vat_but_have_company_id_with_prefix(self):
|
||||
self.company_data['company'].write({
|
||||
@@ -119,7 +119,7 @@ class TestUBLRO(TestUBLCommon):
|
||||
self.company_data['company'].vat = '1234567897'
|
||||
invoice = self.create_move("out_invoice", send=False)
|
||||
with self.assertRaisesRegex(UserError, "doesn't have a country code prefix in their VAT"):
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, from_cron=False, allow_fallback_pdf=False)
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
|
||||
|
||||
def test_export_constraints(self):
|
||||
self.company_data['company'].company_registry = None
|
||||
@@ -128,10 +128,10 @@ class TestUBLRO(TestUBLCommon):
|
||||
self.company_data["company"][required_field] = None
|
||||
invoice = self.create_move("out_invoice", send=False)
|
||||
with self.assertRaisesRegex(UserError, "required"):
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, from_cron=False, allow_fallback_pdf=False)
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
|
||||
self.company_data["company"][required_field] = prev_val
|
||||
|
||||
self.company_data["company"].city = "Bucharest"
|
||||
invoice = self.create_move("out_invoice", send=False)
|
||||
with self.assertRaisesRegex(UserError, "city name must be 'SECTORX'"):
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, from_cron=False, allow_fallback_pdf=False)
|
||||
invoice._generate_pdf_and_send_invoice(self.move_template, allow_fallback_pdf=False)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class AccountMoveSend(models.Model):
|
||||
class AccountMoveSend(models.TransientModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
enable_send_by_post = fields.Boolean(compute='_compute_enable_send_by_post')
|
||||
@@ -17,10 +17,10 @@ class AccountMoveSend(models.Model):
|
||||
send_by_post_warning_message = fields.Text(compute='_compute_send_by_post_extra_fields')
|
||||
send_by_post_readonly = fields.Boolean(compute='_compute_send_by_post_extra_fields')
|
||||
|
||||
def _get_available_field_values_in_multi(self, move):
|
||||
def _get_wizard_values(self, move=None):
|
||||
# EXTENDS 'account'
|
||||
values = super()._get_available_field_values_in_multi(move)
|
||||
values['checkbox_send_by_post'] = self.checkbox_send_by_post
|
||||
values = super()._get_wizard_values(move)
|
||||
values['send_by_post'] = self.checkbox_send_by_post
|
||||
return values
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -72,18 +72,15 @@ class AccountMoveSend(models.Model):
|
||||
'attachment_id': move.invoice_pdf_report_id.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _hook_if_success(self, moves_data, from_cron=False, allow_fallback_pdf=False):
|
||||
# EXTENDS 'account'
|
||||
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
|
||||
|
||||
moves = self.env['account.move']
|
||||
for move in moves_data:
|
||||
moves |= move
|
||||
moves = moves.filtered('invoice_pdf_report_id')
|
||||
for move, move_data in moves_data.items():
|
||||
if move_data.get('send_by_post') and move.invoice_pdf_report_id:
|
||||
moves |= move
|
||||
if not moves:
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user