[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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>&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>&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>
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 & Pay Quotation</t>
|
||||
</t>
|
||||
<t t-else="" t-set="access_name">Review, Accept & 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 & 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>
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user