249 lines
8.5 KiB
Python
249 lines
8.5 KiB
Python
# Part of Fusion Accounting. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class FusionFollowupSendWizard(models.TransientModel):
|
|
"""Wizard for previewing and manually sending payment follow-ups.
|
|
|
|
Allows the user to review the email content, modify the message
|
|
body, choose communication channels, and send the follow-up for
|
|
one or more partners at once.
|
|
"""
|
|
|
|
_name = 'fusion.followup.send.wizard'
|
|
_description = "Fusion Follow-up Send Wizard"
|
|
|
|
# ---- Context Fields ----
|
|
followup_line_id = fields.Many2one(
|
|
comodel_name='fusion.followup.line',
|
|
string="Follow-up Record",
|
|
readonly=True,
|
|
)
|
|
partner_id = fields.Many2one(
|
|
comodel_name='res.partner',
|
|
string="Partner",
|
|
related='followup_line_id.partner_id',
|
|
readonly=True,
|
|
)
|
|
followup_level_id = fields.Many2one(
|
|
comodel_name='fusion.followup.level',
|
|
string="Follow-up Level",
|
|
related='followup_line_id.followup_level_id',
|
|
readonly=True,
|
|
)
|
|
|
|
# ---- Editable Content ----
|
|
email_subject = fields.Char(
|
|
string="Subject",
|
|
compute='_compute_email_preview',
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
email_body = fields.Html(
|
|
string="Email Body",
|
|
compute='_compute_email_preview',
|
|
store=True,
|
|
readonly=False,
|
|
help="Edit the email body before sending. Changes apply only to this send.",
|
|
)
|
|
|
|
# ---- Channel Overrides ----
|
|
do_send_email = fields.Boolean(
|
|
string="Send Email",
|
|
compute='_compute_channel_defaults',
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
do_send_sms = fields.Boolean(
|
|
string="Send SMS",
|
|
compute='_compute_channel_defaults',
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
do_print_letter = fields.Boolean(
|
|
string="Print Letter",
|
|
compute='_compute_channel_defaults',
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
do_join_invoices = fields.Boolean(
|
|
string="Attach Invoices",
|
|
compute='_compute_channel_defaults',
|
|
store=True,
|
|
readonly=False,
|
|
)
|
|
|
|
# ---- Informational ----
|
|
overdue_amount = fields.Monetary(
|
|
string="Overdue Amount",
|
|
related='followup_line_id.overdue_amount',
|
|
readonly=True,
|
|
currency_field='currency_id',
|
|
)
|
|
overdue_count = fields.Integer(
|
|
string="Overdue Invoices",
|
|
related='followup_line_id.overdue_count',
|
|
readonly=True,
|
|
)
|
|
currency_id = fields.Many2one(
|
|
comodel_name='res.currency',
|
|
related='followup_line_id.currency_id',
|
|
readonly=True,
|
|
)
|
|
|
|
# --------------------------------------------------
|
|
# Default Values
|
|
# --------------------------------------------------
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
"""Populate the wizard from the active follow-up line."""
|
|
defaults = super().default_get(fields_list)
|
|
active_id = self.env.context.get('active_id')
|
|
if active_id and self.env.context.get('active_model') == 'fusion.followup.line':
|
|
defaults['followup_line_id'] = active_id
|
|
return defaults
|
|
|
|
# --------------------------------------------------
|
|
# Computed Fields
|
|
# --------------------------------------------------
|
|
|
|
@api.depends('followup_level_id', 'partner_id')
|
|
def _compute_email_preview(self):
|
|
"""Build a preview of the email subject and body.
|
|
|
|
Uses the level's email template if set, otherwise falls back to
|
|
the level description or a sensible default.
|
|
"""
|
|
for wizard in self:
|
|
level = wizard.followup_level_id
|
|
partner = wizard.partner_id
|
|
company = wizard.followup_line_id.company_id or self.env.company
|
|
|
|
if level and level.email_template_id:
|
|
template = level.email_template_id
|
|
# Render the template for the partner
|
|
rendered = template._render_field(
|
|
'body_html', [partner.id],
|
|
engine='inline_template',
|
|
compute_lang=True,
|
|
)
|
|
wizard.email_body = rendered.get(partner.id, '')
|
|
rendered_subject = template._render_field(
|
|
'subject', [partner.id],
|
|
engine='inline_template',
|
|
compute_lang=True,
|
|
)
|
|
wizard.email_subject = rendered_subject.get(partner.id, '')
|
|
else:
|
|
wizard.email_subject = _(
|
|
"%(company)s - Payment Reminder",
|
|
company=company.name,
|
|
)
|
|
if level and level.description:
|
|
wizard.email_body = level.description
|
|
else:
|
|
wizard.email_body = _(
|
|
"<p>Dear %(partner)s,</p>"
|
|
"<p>This is a reminder that your account has an outstanding "
|
|
"balance. Please arrange payment at your earliest convenience.</p>"
|
|
"<p>Best regards,<br/>%(company)s</p>",
|
|
partner=partner.name or _('Customer'),
|
|
company=company.name,
|
|
)
|
|
|
|
@api.depends('followup_level_id')
|
|
def _compute_channel_defaults(self):
|
|
"""Pre-fill channel toggles from the follow-up level configuration."""
|
|
for wizard in self:
|
|
level = wizard.followup_level_id
|
|
if level:
|
|
wizard.do_send_email = level.send_email
|
|
wizard.do_send_sms = level.send_sms
|
|
wizard.do_print_letter = level.send_letter
|
|
wizard.do_join_invoices = level.join_invoices
|
|
else:
|
|
wizard.do_send_email = True
|
|
wizard.do_send_sms = False
|
|
wizard.do_print_letter = False
|
|
wizard.do_join_invoices = False
|
|
|
|
# --------------------------------------------------
|
|
# Actions
|
|
# --------------------------------------------------
|
|
|
|
def action_send_followup(self):
|
|
"""Send the follow-up using the wizard-configured channels and content.
|
|
|
|
Sends the (possibly edited) email, optionally triggers SMS,
|
|
then advances the partner to the next follow-up level.
|
|
|
|
:returns: ``True`` to close the wizard.
|
|
:raises UserError: If no follow-up line is linked.
|
|
"""
|
|
self.ensure_one()
|
|
line = self.followup_line_id
|
|
if not line:
|
|
raise UserError(_("No follow-up record is linked to this wizard."))
|
|
|
|
partner = line.partner_id
|
|
|
|
# ---- Email ----
|
|
if self.do_send_email:
|
|
attachment_ids = []
|
|
if self.do_join_invoices:
|
|
attachment_ids = line._get_invoice_attachments(partner)
|
|
|
|
mail_values = {
|
|
'subject': self.email_subject,
|
|
'body_html': self.email_body,
|
|
'email_from': (
|
|
line.company_id.email
|
|
or self.env.user.email_formatted
|
|
),
|
|
'email_to': partner.email,
|
|
'author_id': self.env.user.partner_id.id,
|
|
'res_id': partner.id,
|
|
'model': 'res.partner',
|
|
'attachment_ids': [(6, 0, attachment_ids)],
|
|
}
|
|
mail = self.env['mail.mail'].sudo().create(mail_values)
|
|
mail.send()
|
|
|
|
# ---- SMS ----
|
|
if self.do_send_sms and line.followup_level_id.sms_template_id:
|
|
try:
|
|
line.followup_level_id.sms_template_id._send_sms(partner.id)
|
|
except Exception:
|
|
pass
|
|
|
|
# ---- Advance Level ----
|
|
level = line.followup_level_id
|
|
if level:
|
|
next_level = level._get_next_level()
|
|
line.write({
|
|
'date': fields.Date.context_today(self),
|
|
'followup_level_id': next_level.id if next_level else level.id,
|
|
})
|
|
else:
|
|
line.write({'date': fields.Date.context_today(self)})
|
|
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
def action_preview_email(self):
|
|
"""Recompute the email preview after manual edits to the level.
|
|
|
|
:returns: Action to reload the wizard form.
|
|
"""
|
|
self.ensure_one()
|
|
self._compute_email_preview()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': self._name,
|
|
'res_id': self.id,
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
}
|