Initial commit
This commit is contained in:
374
Fusion Accounting/wizard/account_report_send.py
Normal file
374
Fusion Accounting/wizard/account_report_send.py
Normal file
@@ -0,0 +1,374 @@
|
||||
# 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,
|
||||
)
|
||||
Reference in New Issue
Block a user