From 60687c611afdfec9ecceba1dcccca2134f44746a Mon Sep 17 00:00:00 2001 From: git_admin Date: Tue, 28 Apr 2026 07:34:08 +0000 Subject: [PATCH] Tower: upload at_accounting 18.0.1.7 (via marketplace) --- .../wizard/account_report_send.py | 310 ++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 addons/at_accounting/wizard/account_report_send.py diff --git a/addons/at_accounting/wizard/account_report_send.py b/addons/at_accounting/wizard/account_report_send.py new file mode 100644 index 0000000..130e4d6 --- /dev/null +++ b/addons/at_accounting/wizard/account_report_send.py @@ -0,0 +1,310 @@ +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tools.misc import get_lang + + +class AccountReportSend(models.TransientModel): + _name = 'account.report.send' + _description = "Account Report Send" + + partner_ids = fields.Many2many( + comodel_name='res.partner', + compute='_compute_partner_ids', + ) + mode = fields.Selection( + selection=[ + ('single', "Single Recipient"), + ('multi', "Multiple Recipients"), + ], + compute='_compute_mode', + readonly=False, + store=True, + ) + + # == PRINT == + enable_download = fields.Boolean() + checkbox_download = fields.Boolean(string="Download") + + # == MAIL == + enable_send_mail = fields.Boolean(default=True) + checkbox_send_mail = fields.Boolean(string="Email", default=True) + + display_mail_composer = fields.Boolean(compute='_compute_send_mail_extra_fields') + warnings = fields.Json(compute='_compute_warnings') + send_mail_readonly = fields.Boolean(compute='_compute_send_mail_extra_fields') + mail_template_id = fields.Many2one( + comodel_name='mail.template', + string="Email template", + domain="[('model', '=', 'res.partner')]", + ) + + account_report_id = fields.Many2one( + comodel_name='account.report', + string="Report", + ) + report_options = fields.Json() + + mail_lang = fields.Char( + string="Lang", + compute='_compute_mail_lang', + ) + mail_partner_ids = fields.Many2many( + comodel_name='res.partner', + string="Recipients", + compute='_compute_mail_partner_ids', + store=True, + readonly=False, + ) + mail_subject = fields.Char( + string="Subject", + compute='_compute_mail_subject_body', + store=True, + readonly=False, + ) + mail_body = fields.Html( + string="Contents", + sanitize_style=True, + compute='_compute_mail_subject_body', + store=True, + readonly=False, + ) + mail_attachments_widget = fields.Json( + compute='_compute_mail_attachments_widget', + store=True, + readonly=False, + ) + + @api.model + def default_get(self, fields_list): + # EXTENDS 'base' + results = super().default_get(fields_list) + + context_options = self._context.get('default_report_options', {}) + if 'account_report_id' in fields_list and 'account_report_id' not in results: + report_id = context_options.get('report_id', False) + results['account_report_id'] = report_id + results['report_options'] = context_options + + return results + + @api.model + def _get_mail_field_value(self, partner, mail_template, mail_lang, field, **kwargs): + if not mail_template: + return + return mail_template\ + .with_context(lang=mail_lang)\ + ._render_field(field, partner.ids, **kwargs)[partner._origin.id] + + def _get_default_mail_attachments_widget(self, partner, mail_template): + return self._get_placeholder_mail_attachments_data(partner) \ + + self._get_mail_template_attachments_data(mail_template) + + def _get_wizard_values(self): + self.ensure_one() + options = self.report_options + if not options.get('partner_ids', []): + options['partner_ids'] = self.partner_ids.ids + return { + 'mail_template_id': self.mail_template_id.id, + 'checkbox_download': self.checkbox_download, + 'checkbox_send_mail': self.checkbox_send_mail, + 'report_options': options, + } + + def _get_placeholder_mail_attachments_data(self, partner): + """ Returns all the placeholder data. + Should be extended to add placeholder based on the checkboxes. + :param: partner: The partner for which this report is generated. + :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 = f"{partner.name} - {self.account_report_id.get_default_report_filename(self.report_options, 'pdf')}" + return [{ + 'id': f'placeholder_{filename}', + 'name': filename, + 'mimetype': 'application/pdf', + 'placeholder': True, + }] + + @api.model + 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 + # ------------------------------------------------------------------------- + + @api.depends('partner_ids') + def _compute_mode(self): + for wizard in self: + wizard.mode = 'single' if len(wizard.partner_ids) == 1 else 'multi' + + @api.depends('checkbox_send_mail') + def _compute_send_mail_extra_fields(self): + for wizard in self: + wizard.display_mail_composer = wizard.mode == 'single' + partners_without_mail_data = wizard.mail_partner_ids.filtered(lambda x: not x.email) + wizard.send_mail_readonly = partners_without_mail_data == wizard.mail_partner_ids + + @api.depends('mail_partner_ids', 'checkbox_send_mail', 'send_mail_readonly') + def _compute_warnings(self): + for wizard in self: + warnings = {} + + partners_without_mail = wizard.mail_partner_ids.filtered(lambda x: not x.email) + if wizard.send_mail_readonly or (wizard.checkbox_send_mail and partners_without_mail): + warnings['account_missing_email'] = { + 'message': _("Partner(s) should have an email address."), + 'action_text': _("View Partner(s)"), + 'action': partners_without_mail._get_records_action(name=_("Check Partner(s) Email(s)")) + } + + wizard.warnings = warnings + + @api.depends('partner_ids') + def _compute_mail_lang(self): + for wizard in self: + if wizard.mode == 'single': + wizard.mail_lang = wizard.partner_ids.lang + else: + wizard.mail_lang = get_lang(self.env).code + + @api.depends('account_report_id', 'report_options') + def _compute_partner_ids(self): + for wizard in self: + wizard.partner_ids = wizard.account_report_id._get_report_send_recipients(wizard.report_options) + + @api.depends('account_report_id', 'report_options') + def _compute_mail_partner_ids(self): + for wizard in self: + wizard.mail_partner_ids = wizard.partner_ids + + @api.depends('mail_template_id', 'mail_lang', 'mode') + def _compute_mail_subject_body(self): + for wizard in self: + if wizard.mode == 'single' and wizard.mail_template_id: + wizard.mail_subject = self._get_mail_field_value(wizard.mail_partner_ids, wizard.mail_template_id, wizard.mail_lang, 'subject') + wizard.mail_body = self._get_mail_field_value(wizard.mail_partner_ids, wizard.mail_template_id, wizard.mail_lang, 'body_html', options={'post_process': True}) + else: + wizard.mail_subject = wizard.mail_body = None + + @api.depends('mail_template_id', 'mode') + def _compute_mail_attachments_widget(self): + for wizard in self: + if wizard.mode == 'single': + wizard.mail_attachments_widget = wizard._get_default_mail_attachments_widget(wizard.mail_partner_ids, wizard.mail_template_id) + else: + wizard.mail_attachments_widget = [] + + @api.model + def _action_download(self, attachments): + """ Download the PDF attachment, or a zip of attachments if there are more than one. """ + return { + 'type': 'ir.actions.act_url', + 'url': f'/account_reports/download_attachments/{",".join(map(str, attachments.ids))}', + 'close': True, + } + + def _process_send_and_print(self, report, options, recipient_partner_ids=None, wizard=None): + """ Generate a report for one partner based on the options (send_and_print_values stored on the report). + :param options: dict of report options (should contain one partner id in options['partner_ids']) + :param recipient_partner_ids: list of partner ids that will receive the mail message. + :param wizard: account.report.send wizard if exists. Indicates if sending by cron. + """ + wizard_vals = report.send_and_print_values if not wizard else wizard._get_wizard_values() + to_email = wizard_vals['checkbox_send_mail'] + to_download = wizard_vals['checkbox_download'] + + mail_template_id = self.env['mail.template'].browse(wizard_vals['mail_template_id']) + if wizard: + attachments_ids = [att['id'] for att in wizard.mail_attachments_widget or [] if not att['placeholder']] + else: + attachments_ids = mail_template_id.attachment_ids.ids + + options['unfold_all'] = True + + partner_ids = options.get('partner_ids', []) + partners = self.env['res.partner'].browse(partner_ids) + if not recipient_partner_ids: + recipient_partner_ids = partners.filtered('email').ids + + downloadable_attachments = self.env['ir.attachment'] + + for partner in partners: + options['partner_ids'] = partner.ids + report_attachment = partner._get_partner_account_report_attachment(report, options) + + if to_email and recipient_partner_ids: + if wizard and wizard.mode == 'single': + subject = self.mail_subject + body = self.mail_body + else: + subject = self._get_mail_field_value(partner, mail_template_id, partner.lang, 'subject') + body = self._get_mail_field_value(partner, mail_template_id, partner.lang, 'body_html', options={'post_process': True}) + + partner.message_post( + body=body, + subject=subject, + partner_ids=recipient_partner_ids, + attachment_ids=attachments_ids + report_attachment.ids, + ) + + if to_download: + downloadable_attachments += report_attachment + + if downloadable_attachments: + return self._action_download(downloadable_attachments) + + def action_send_and_print(self, force_synchronous=False): + """ Create the documents and send them to the end customers. + If we are sending multiple statements and not downloading them we will process the moves asynchronously. + :param force_synchronous: Flag indicating if the method should be done synchronously. + """ + self.ensure_one() + + if self.mode == 'multi' and self.checkbox_send_mail and not self.mail_template_id: + raise UserError(_('Please select a mail template to send multiple statements.')) + + force_synchronous = force_synchronous or self.checkbox_download + process_later = self.mode == 'multi' and not force_synchronous + if process_later: + # Set sending information on report + if self.account_report_id.send_and_print_values: + raise UserError(_('There are currently reports waiting to be sent, please try again later.')) + self.account_report_id.send_and_print_values = self._get_wizard_values() + self.env.ref('at_accounting.ir_cron_account_report_send')._trigger() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'type': 'info', + 'title': _('Sending statements'), + 'message': _('Statements are being sent in the background.'), + 'next': {'type': 'ir.actions.act_window_close'}, + }, + } + options = { + **self.report_options, + 'partner_ids': self.partner_ids.ids, + } + return self._process_send_and_print( + report=self.account_report_id, + options=options, + recipient_partner_ids=self.mail_partner_ids.ids, + wizard=self, + )