[REF] mail, *: improve 'pay now' notification template

* = sale, purchase

PURPOSE

Purpose of this commit is to improve the 'Pay Now' notification template
used notably when using the "Send by email" button on

  * invoices
  * sale orders
  * RFQ and purchase orders

SPECIFICATIONS

Global specifications

  * remove gray background that is around the white content (aka have an
    email with an uniform white background);
  * move button on top of email like other notification templates (top-left
    and company logo is top-right);
  * fix various small wording issues;
  * fix signature usage;

Technical specifications

Remove custom definition of access links and labels in 'Pay Now' notification
template (``mail_notification_paynow``). It is now done at model level through
the ``_notify_get_groups`` that is generic to notification emails. This allows
to remove QWeb override in purchase and sale notably. Displaying access links
is now controller by the ``has_button_access`` value. Model computes links,
access and labels while view only displays what is requested. That way any
template can use those values instead of being defined in a template subject
to user changes.

Sale / Purchase

Overrides of those modules is not necessary anymore since button labelling
and URLs are managed at model level.

Purchase "specific" buttons for Accept / Update dates are now email layout
actions, like used in other modules like HR or Project.

Continuation of odoo/odoo#76418 .

Task-2712450 (Mail/Sale: Improve 'Pay Now' notification template)

Part-of: odoo/odoo#82167
This commit is contained in:
Thibault Delavallée
2022-01-31 17:47:33 +00:00
parent 8ebef14256
commit 75979fccdf
9 changed files with 84 additions and 99 deletions
@@ -131,33 +131,37 @@
</table>
</template>
<!-- Information on model to use this notification template
* if the record has an online access defined in _get_access_action, having
a _get_share_url methods is required (like sale order and invoice);
* this template works best with portal-enable models although it is not
a complete requirement currently;
-->
<!-- Used mainly in conjunction of portal models (SO, PO, Invoices) although not mandatory -->
<template id="mail_notification_paynow" name="Mail: Pay Now mail notification template">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 24px; background-color: white; color: #454748; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #FFFFFF; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 24px; background-color: #FFFFFF; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
<td align="center" style="min-width: 590px;">
<t t-set="access_action" t-value="record._get_access_action(force_website=True)"/>
<t t-set="is_online" t-value="access_action and access_action['type'] == 'ir.actions.act_url'"/>
<t t-set="base_url" t-value="record.get_base_url()"/>
<t t-set="share_url" t-value="is_online and record._get_share_url(redirect=True, signup_partner=notification_is_customer, share_token=notification_is_customer)"/>
<t t-set="access_url" t-value="is_online and share_url and base_url + share_url or ''"/>
<t t-set="access_name">
View <t t-esc="model_description or 'document'"/>
</t>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: white; padding: 0; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #FFFFFF; padding: 0; border-collapse:separate;">
<tr><td valign="middle">
<span style="font-size: 10px;">Your <t t-esc="model_description or 'document'"/></span><br/>
<span style="font-size: 20px; font-weight: bold;">
<t t-esc="message.record_name"/>
</span>
<t t-if="has_button_access">
<a t-att-href="button_access['url']"
style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #875A7B; border: 0px solid #875A7B; border-radius:3px">
<t t-esc="button_access['title']"/>
</a>
</t>
<t t-else="">
<span style="font-size: 10px;">Your <t t-esc="model_description or 'document'"/></span>
<br/>
<span style="font-size: 20px; font-weight: bold;">
<t t-esc="message.record_name and message.record_name.replace('/','-') or ''"/>
</span>
</t>
<t t-if="actions">
<t t-foreach="actions" t-as="action">
|
<a t-att-href="action['url']" style="color: #875A7B; text-decoration:none !important;">
<t t-esc="action['title']"/>
</a>
</t>
</t>
</td><td valign="middle" align="right">
<img t-att-src="'/logo.png?company=%s' % (company.id or 0)" style="padding: 0px; margin: 0px; height: 48px;" t-att-alt="'%s' % company.name"/>
</td></tr>
@@ -171,12 +175,6 @@
<tr>
<td style="padding: 0">
<t t-out="message.body"/>
<div t-if="is_online and not record._context.get('proforma')" style="margin: 32px 0px 32px 0px; text-align: center;">
<a t-att-href="access_url"
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
<t t-esc="access_name"/>
</a>
</div>
<t t-if="email_add_signature and 'user_id' in record and record.user_id and not record.env.user._is_superuser() and not is_html_empty(record.user_id.sudo().signature)">
<div style="margin: 0px; padding: 0px; font-size:13px;">
Best regards,
+10 -4
View File
@@ -46,10 +46,14 @@ class PortalMixin(models.AbstractModel):
:return: the url of the record with access parameters, if any.
"""
self.ensure_one()
params = {
'model': self._name,
'res_id': self.id,
}
if redirect:
# model / res_id used by mail/view to check access on record
params = {
'model': self._name,
'res_id': self.id,
}
else:
params = {}
if share_token and hasattr(self, 'access_token'):
params['access_token'] = self._portal_ensure_token()
if pid:
@@ -68,6 +72,8 @@ class PortalMixin(models.AbstractModel):
if access_token and 'partner_id' in self._fields and self['partner_id']:
customer = self['partner_id']
local_msg_vals['access_token'] = self.access_token
local_msg_vals['pid'] = customer.id
local_msg_vals['hash'] = self._sign_token(customer.id)
local_msg_vals.update(customer.signup_get_auth_param()[customer.id])
access_link = self._notify_get_action_link('view', **local_msg_vals)
-35
View File
@@ -1,41 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<template id="mail_notification_confirm" inherit_id="mail.mail_notification_paynow" name="Purchase: Confirmation mail notification template">
<xpath expr="//t[@t-set='access_name']" position="after">
<t t-if="record._name == 'purchase.order'">
<t t-if="record.state == 'purchase' and not record.env.context.get('is_reminder')">
<t t-set="access_name">Confirm</t>
<t t-set="access_url" t-value="record.get_confirm_url(confirm_type='reception')"/>
</t>
</t>
</xpath>
<xpath expr="//a[@t-att-href='access_url']" position="replace">
<t t-if="record._name == 'purchase.order' and record.env.context.get('is_reminder')">
<a t-att-href="record.get_confirm_url(confirm_type='reminder')"
style="margin-right: 10px; background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
Yes
</a>
<a t-att-href="record.get_update_url()"
style="margin-left: 10px; background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
No, Update Dates
</a>
<div>&amp;nbsp;</div>
<div style="margin: 0px; padding: 0px; font-size:13px; text-align: left;">
If you have any questions, please do not hesitate to contact us.
<div>&amp;nbsp;</div>
Best regards,
</div>
</t>
<t t-else="">
<a t-att-href="access_url"
style="margin-left: 10px; background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
<t t-esc="access_name"/>
</a>
</t>
</xpath>
</template>
<template id="track_po_line_template">
<div>
<strong>The ordered quantity has been updated.</strong>
+21
View File
@@ -351,6 +351,27 @@ class PurchaseOrder(models.Model):
self.filtered(lambda o: o.state == 'draft').write({'state': 'sent'})
return super(PurchaseOrder, self.with_context(mail_post_autofollow=self.env.context.get('mail_post_autofollow', True))).message_post(**kwargs)
def _notify_get_recipients_groups(self, msg_vals=None):
""" Tweak 'view document' button for portal customers, calling directly
routes for confirm specific to PO model. """
groups = super(PurchaseOrder, self)._notify_get_recipients_groups(msg_vals=msg_vals)
customer_portal_group = next(group for group in groups if group[0] == 'portal_customer')
if customer_portal_group:
access_opt = customer_portal_group[2].setdefault('button_access', {})
if self.env.context.get('is_reminder'):
access_opt['title'] = _('View')
actions = customer_portal_group[2].setdefault('actions', list())
actions.extend([
{'url': self.get_confirm_url(confirm_type='reminder'), 'title': _('Accept')},
{'url': self.get_update_url(), 'title': _('Update Dates')},
])
else:
access_opt['title'] = _('Confirm')
access_opt['url'] = self.get_confirm_url(confirm_type='reception')
return groups
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'purchase':
-1
View File
@@ -24,7 +24,6 @@ This module contains all the common features of Sales Management and eCommerce.
'data/ir_sequence_data.xml',
'data/mail_data_various.xml',
'data/mail_template_data.xml',
'data/mail_templates.xml',
'data/sale_data.xml',
'wizard/account_accrued_orders_wizard_views.xml',
-27
View File
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<template id="mail_notification_paynow_online"
inherit_id="mail.mail_notification_paynow"
name="Quotation: Sign and Pay mail notification template">
<xpath expr="//t[@t-set='access_name']" position="after">
<t t-if="record._name == 'sale.order'">
<t t-set="is_transaction_pending" t-value="record.get_portal_last_transaction().state == 'pending'"/>
<t t-if="record.has_to_be_signed(include_draft=True)">
<t t-if="record.has_to_be_paid()" t-set="access_name">
<t t-if="is_transaction_pending">View Quotation</t>
<t t-else="">Review, Sign &amp; Pay Quotation</t>
</t>
<t t-else="" t-set="access_name">Review, Accept &amp; Sign Quotation</t>
</t>
<t t-elif="record.has_to_be_paid(include_draft=True) and not is_transaction_pending">
<t t-set="access_name">Review, Accept &amp; Pay Quotation</t>
</t>
<t t-elif="record.state in ('draft', 'sent')">
<t t-set="access_name">View Quotation</t>
</t>
</t>
</xpath>
</template>
</odoo>
+26
View File
@@ -1069,6 +1069,32 @@ class SaleOrder(models.Model):
transaction = self.get_portal_last_transaction()
return (self.state == 'sent' or (self.state == 'draft' and include_draft)) and not self.is_expired and self.require_payment and transaction.state != 'done' and self.amount_total
def _notify_get_recipients_groups(self, msg_vals=None):
""" Give access button to users and portal customer as portal is integrated
in sale. Customer and portal group have probably no right to see
the document so they don't have the access button. """
groups = super(SaleOrder, self)._notify_get_recipients_groups(msg_vals=msg_vals)
if self._context.get('proforma'):
for group in [g for g in groups if g[0] in ('portal_customer', 'customer')]:
group[2]['has_button_access'] = False
return groups
customer_portal_group = next(group for group in groups if group[0] == 'portal_customer')
if customer_portal_group:
access_opt = customer_portal_group[2].setdefault('button_access', {})
is_tx_pending = self.get_portal_last_transaction().state == 'pending'
if self.has_to_be_signed(include_draft=True):
if self.has_to_be_paid():
access_opt['title'] = _('View Quotation') if is_tx_pending else _('Review, Sign & Pay Quotation')
else:
access_opt['title'] = _('Review, Accept & Sign Quotation')
elif self.has_to_be_paid(include_draft=True) and not is_tx_pending:
access_opt['title'] = _('Review, Accept & Pay Quotation')
elif self.state in ('draft', 'sent'):
access_opt['title'] = _('View Quotation')
return groups
def preview_sale_order(self):
self.ensure_one()
return {
@@ -41,11 +41,6 @@ class MailTestStandard(models.Model):
container_id = fields.Many2one('mail.test.container', tracking=True)
company_id = fields.Many2one('res.company')
def _get_share_url(self, redirect, signup_partner, share_token):
"""This function is required for a test on 'mail.mail_notification_paynow' template (test_message_post/test_mail_add_signature),
another model should be created in master"""
return '/mail/view'
class MailTestActivity(models.Model):
""" This model can be used to test activities in addition to simple chatter
@@ -26,6 +26,8 @@ class TestWebsiteSaleMail(HttpCase):
# we override unlink because we don't want the email to be auto deleted
MailMail = odoo.addons.mail.models.mail_mail.MailMail
# as we check some link content, avoid mobile doing its link management
self.env['ir.config_parameter'].sudo().set_param('mail_mobile.disable_redirect_firebase_dynamic_link', True)
with patch.object(MailMail, 'unlink', lambda self: None):
self.start_tour("/", 'shop_mail', login="admin")