375 lines
14 KiB
Python
375 lines
14 KiB
Python
# Fusion Accounting - Report Send Wizard
|
|
# Handles email dispatch and download of accounting reports to partners
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.misc import get_lang
|
|
|
|
|
|
class AccountReportSend(models.TransientModel):
|
|
"""Wizard providing a unified interface for sending accounting reports
|
|
via email and/or downloading them as PDF attachments."""
|
|
|
|
_name = 'account.report.send'
|
|
_description = "Fusion Accounting Report Dispatch Wizard"
|
|
|
|
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,
|
|
)
|
|
|
|
# -- Download options --
|
|
enable_download = fields.Boolean()
|
|
checkbox_download = fields.Boolean(string="Download")
|
|
|
|
# -- Email options --
|
|
enable_send_mail = fields.Boolean(default=True)
|
|
checkbox_send_mail = fields.Boolean(string="Email", default=True)
|
|
|
|
display_mail_composer = fields.Boolean(compute='_compute_mail_ui_state')
|
|
warnings = fields.Json(compute='_compute_warnings')
|
|
send_mail_readonly = fields.Boolean(compute='_compute_mail_ui_state')
|
|
|
|
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,
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Default values
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
# EXTENDS 'base'
|
|
defaults = super().default_get(fields_list)
|
|
|
|
ctx_options = self.env.context.get('default_report_options', {})
|
|
if 'account_report_id' in fields_list and 'account_report_id' not in defaults:
|
|
defaults['account_report_id'] = ctx_options.get('report_id', False)
|
|
defaults['report_options'] = ctx_options
|
|
|
|
return defaults
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Helper methods
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.model
|
|
def _get_mail_field_value(self, partner, mail_template, mail_lang, field, **kwargs):
|
|
"""Render a single field from the mail template for a given partner."""
|
|
if not mail_template:
|
|
return
|
|
rendered = mail_template.with_context(lang=mail_lang)._render_field(
|
|
field, partner.ids, **kwargs
|
|
)
|
|
return rendered[partner._origin.id]
|
|
|
|
def _get_default_mail_attachments_widget(self, partner, mail_template):
|
|
"""Combine report placeholder attachments with template-defined attachments."""
|
|
placeholder_data = self._get_placeholder_mail_attachments_data(partner)
|
|
template_data = self._get_mail_template_attachments_data(mail_template)
|
|
return placeholder_data + template_data
|
|
|
|
def _get_wizard_values(self):
|
|
"""Serialize the current wizard state into a storable dictionary."""
|
|
self.ensure_one()
|
|
opts = self.report_options
|
|
if not opts.get('partner_ids', []):
|
|
opts['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': opts,
|
|
}
|
|
|
|
def _get_placeholder_mail_attachments_data(self, partner):
|
|
"""Generate placeholder attachment metadata for the report PDF.
|
|
|
|
Returns a list with one dict per placeholder containing:
|
|
- id: unique placeholder identifier string
|
|
- name: display filename
|
|
- mimetype: MIME type of the file
|
|
- placeholder: True (prevents download/deletion in the widget)
|
|
"""
|
|
self.ensure_one()
|
|
pdf_name = (
|
|
f"{partner.name} - "
|
|
f"{self.account_report_id.get_default_report_filename(self.report_options, 'pdf')}"
|
|
)
|
|
return [{
|
|
'id': f'placeholder_{pdf_name}',
|
|
'name': pdf_name,
|
|
'mimetype': 'application/pdf',
|
|
'placeholder': True,
|
|
}]
|
|
|
|
@api.model
|
|
def _get_mail_template_attachments_data(self, mail_template):
|
|
"""Build attachment metadata from files linked to the mail template."""
|
|
return [
|
|
{
|
|
'id': att.id,
|
|
'name': att.name,
|
|
'mimetype': att.mimetype,
|
|
'placeholder': False,
|
|
'mail_template_id': mail_template.id,
|
|
}
|
|
for att in mail_template.attachment_ids
|
|
]
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Computed fields
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.depends('partner_ids')
|
|
def _compute_mode(self):
|
|
for wiz in self:
|
|
wiz.mode = 'single' if len(wiz.partner_ids) == 1 else 'multi'
|
|
|
|
@api.depends('checkbox_send_mail')
|
|
def _compute_mail_ui_state(self):
|
|
"""Determine whether the full mail composer should be shown
|
|
and whether the send-mail checkbox should be read-only."""
|
|
for wiz in self:
|
|
wiz.display_mail_composer = wiz.mode == 'single'
|
|
recipients_missing_email = wiz.mail_partner_ids.filtered(lambda p: not p.email)
|
|
wiz.send_mail_readonly = recipients_missing_email == wiz.mail_partner_ids
|
|
|
|
@api.depends('mail_partner_ids', 'checkbox_send_mail', 'send_mail_readonly')
|
|
def _compute_warnings(self):
|
|
for wiz in self:
|
|
alert_map = {}
|
|
|
|
no_email_partners = wiz.mail_partner_ids.filtered(lambda p: not p.email)
|
|
if wiz.send_mail_readonly or (wiz.checkbox_send_mail and no_email_partners):
|
|
alert_map['account_missing_email'] = {
|
|
'message': _("Partner(s) should have an email address."),
|
|
'action_text': _("View Partner(s)"),
|
|
'action': no_email_partners._get_records_action(
|
|
name=_("Check Partner(s) Email(s)")
|
|
),
|
|
}
|
|
|
|
wiz.warnings = alert_map
|
|
|
|
@api.depends('partner_ids')
|
|
def _compute_mail_lang(self):
|
|
for wiz in self:
|
|
if wiz.mode == 'single':
|
|
wiz.mail_lang = wiz.partner_ids.lang
|
|
else:
|
|
wiz.mail_lang = get_lang(self.env).code
|
|
|
|
@api.depends('account_report_id', 'report_options')
|
|
def _compute_partner_ids(self):
|
|
for wiz in self:
|
|
wiz.partner_ids = wiz.account_report_id._get_report_send_recipients(
|
|
wiz.report_options
|
|
)
|
|
|
|
@api.depends('account_report_id', 'report_options')
|
|
def _compute_mail_partner_ids(self):
|
|
for wiz in self:
|
|
wiz.mail_partner_ids = wiz.partner_ids
|
|
|
|
@api.depends('mail_template_id', 'mail_lang', 'mode')
|
|
def _compute_mail_subject_body(self):
|
|
for wiz in self:
|
|
if wiz.mode == 'single' and wiz.mail_template_id:
|
|
wiz.mail_subject = self._get_mail_field_value(
|
|
wiz.mail_partner_ids, wiz.mail_template_id, wiz.mail_lang, 'subject'
|
|
)
|
|
wiz.mail_body = self._get_mail_field_value(
|
|
wiz.mail_partner_ids, wiz.mail_template_id, wiz.mail_lang,
|
|
'body_html', options={'post_process': True},
|
|
)
|
|
else:
|
|
wiz.mail_subject = wiz.mail_body = None
|
|
|
|
@api.depends('mail_template_id', 'mode')
|
|
def _compute_mail_attachments_widget(self):
|
|
for wiz in self:
|
|
if wiz.mode == 'single':
|
|
wiz.mail_attachments_widget = wiz._get_default_mail_attachments_widget(
|
|
wiz.mail_partner_ids, wiz.mail_template_id,
|
|
)
|
|
else:
|
|
wiz.mail_attachments_widget = []
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Actions
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.model
|
|
def _action_download(self, attachments):
|
|
"""Return an action that triggers browser download of the given attachments,
|
|
or a zip archive when multiple files are present."""
|
|
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):
|
|
"""Core processing logic: generates the report for each partner,
|
|
optionally emails it, and collects attachments for download.
|
|
|
|
:param report: account.report record to generate
|
|
:param options: dict of report generation options
|
|
:param recipient_partner_ids: explicit list of partner IDs to receive emails
|
|
:param wizard: the send wizard record (None when running via cron)
|
|
"""
|
|
stored_vals = report.send_and_print_values if not wizard else wizard._get_wizard_values()
|
|
should_email = stored_vals['checkbox_send_mail']
|
|
should_download = stored_vals['checkbox_download']
|
|
|
|
template = self.env['mail.template'].browse(stored_vals['mail_template_id'])
|
|
|
|
if wizard:
|
|
attachment_ids_from_widget = [
|
|
item['id']
|
|
for item in (wizard.mail_attachments_widget or [])
|
|
if not item['placeholder']
|
|
]
|
|
else:
|
|
attachment_ids_from_widget = template.attachment_ids.ids
|
|
|
|
options['unfold_all'] = True
|
|
|
|
target_partner_ids = options.get('partner_ids', [])
|
|
target_partners = self.env['res.partner'].browse(target_partner_ids)
|
|
|
|
if not recipient_partner_ids:
|
|
recipient_partner_ids = target_partners.filtered('email').ids
|
|
|
|
files_for_download = self.env['ir.attachment']
|
|
|
|
for partner_rec in target_partners:
|
|
options['partner_ids'] = partner_rec.ids
|
|
generated_attachment = partner_rec._get_partner_account_report_attachment(
|
|
report, options,
|
|
)
|
|
|
|
if should_email and recipient_partner_ids:
|
|
# Determine subject/body based on single vs multi mode
|
|
if wizard and wizard.mode == 'single':
|
|
email_subject = self.mail_subject
|
|
email_body = self.mail_body
|
|
else:
|
|
email_subject = self._get_mail_field_value(
|
|
partner_rec, template, partner_rec.lang, 'subject',
|
|
)
|
|
email_body = self._get_mail_field_value(
|
|
partner_rec, template, partner_rec.lang,
|
|
'body_html', options={'post_process': True},
|
|
)
|
|
|
|
partner_rec.message_post(
|
|
body=email_body,
|
|
subject=email_subject,
|
|
partner_ids=recipient_partner_ids,
|
|
attachment_ids=attachment_ids_from_widget + generated_attachment.ids,
|
|
)
|
|
|
|
if should_download:
|
|
files_for_download += generated_attachment
|
|
|
|
if files_for_download:
|
|
return self._action_download(files_for_download)
|
|
|
|
def action_send_and_print(self, force_synchronous=False):
|
|
"""Entry point: dispatch report generation, emailing, and/or downloading.
|
|
|
|
When sending to multiple recipients without download, processing is
|
|
deferred to a background cron job for better performance.
|
|
|
|
:param force_synchronous: bypass async processing when True
|
|
"""
|
|
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.')
|
|
)
|
|
|
|
# Download always requires synchronous processing
|
|
force_synchronous = force_synchronous or self.checkbox_download
|
|
defer_to_cron = self.mode == 'multi' and not force_synchronous
|
|
|
|
if defer_to_cron:
|
|
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('fusion_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'},
|
|
},
|
|
}
|
|
|
|
merged_options = {
|
|
**self.report_options,
|
|
'partner_ids': self.partner_ids.ids,
|
|
}
|
|
return self._process_send_and_print(
|
|
report=self.account_report_id,
|
|
options=merged_options,
|
|
recipient_partner_ids=self.mail_partner_ids.ids,
|
|
wizard=self,
|
|
)
|