[IMP] account,crm,project,purchase,sale,[test_]mail: improves activity notif

Ease the understanding of the notifications sent when users are assigned to an
activity or a model (task, lead, ...).

Technical note:
- the customization of data sent to the email layout template (here subtitles)
should be done by overriding
mail_thread._notify_by_email_prepare_rendering_context on the model.
But in this case, we send an activity (ex.: todo) for a model (ex.: crm.lead)
which involves 2 models. Data from both models must be sent to the template
layout. To solve this problem an optional parameter to
mail_thread.message_notify has been added: subtitles. This allows the
caller which knows about the 2 models to set the values for subtitles for
the template.
- mail_activity._render_notify_header has been added to render the subject and
subtitles using the language of the recipients through the context lang
variable.
- a generic mail notification template has been added and the specific
mail_notification_paynow has been derived from it as the only change is the
handling of the signature.

Task-2801600

Part-of: odoo/odoo#88466
This commit is contained in:
Pierre-Yves Dufays
2022-09-14 19:13:44 +02:00
parent 6f777688a1
commit a577afc75e
11 changed files with 118 additions and 75 deletions
+5 -3
View File
@@ -3584,13 +3584,15 @@ class AccountMove(models.Model):
message, msg_vals, model_description=model_description,
force_email_company=force_email_company, force_email_lang=force_email_lang
)
subtitles = [render_context['record'].name]
if self.invoice_date_due:
render_context['subtitle'] = _('%(amount)s due\N{NO-BREAK SPACE}%(date)s',
subtitles.append(_('%(amount)s due\N{NO-BREAK SPACE}%(date)s',
amount=format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')),
date=format_date(self.env, self.invoice_date_due, date_format='short', lang_code=render_context.get('lang'))
)
))
else:
render_context['subtitle'] = format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang'))
subtitles.append(format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')))
render_context['subtitles'] = subtitles
return render_context
# -------------------------------------------------------------------------
+12
View File
@@ -16,6 +16,7 @@ from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
from odoo.tools.translate import _
from odoo.tools import date_utils, email_re, email_split, is_html_empty, groupby
from odoo.tools.misc import get_lang
from . import crm_stage
@@ -1792,6 +1793,17 @@ class Lead(models.Model):
return self.env.ref('crm.mt_lead_lost')
return super(Lead, self)._track_subtype(init_values)
def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False, model_description=False,
force_email_company=False, force_email_lang=False):
render_context = super()._notify_by_email_prepare_rendering_context(
message, msg_vals, model_description=model_description,
force_email_company=force_email_company, force_email_lang=force_email_lang
)
if self.date_deadline:
render_context['subtitles'].append(
_('Deadline: %s', self.date_deadline.strftime(get_lang(self.env).date_format)))
return render_context
def _notify_get_recipients_groups(self, msg_vals=None):
""" Handle salesman recipients that can convert leads into opportunities
and set opportunities as won / lost. """
+16 -18
View File
@@ -3,15 +3,12 @@
<data>
<!-- Discuss utility templates for notifications -->
<template id="message_user_assigned">
<p style="margin: 0px;">
<span>Dear <t t-esc="object.user_id.sudo().name"/>,</span><br />
<div style="margin: 0px; padding: 0px; font-size: 13px;">
<span>Dear <t t-esc="object.user_id.sudo().name"/>,</span>
<br/><br/>
<span style="margin-top: 8px;">You have been assigned to the <t t-esc="model_description or 'document'"/> <t t-esc="object.display_name"/>.</span>
</p>
<p style="padding-top: 24px; padding-bottom: 16px;">
<a t-att-href="access_link" t-att-data-oe-model="object._name" t-att-data-oe-id="object.id" style="background-color:#875A7B; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px;">
View <t t-esc="model_description or 'document'"/>
</a>
</p>
<br/>
</div>
</template>
<template id="message_activity_done">
@@ -36,17 +33,18 @@
<template id="message_activity_assigned">
<div style="margin: 0px; padding: 0px; font-size: 13px;">
<span t-field="activity.create_uid.name"/> assigned you an activity <span t-field="activity.activity_type_id.name"/>
<t t-if="activity.summary">(<span t-field="activity.summary"/>)</t>
on <span t-field="activity.res_name"/>
to close for <span t-field="activity.date_deadline"/>.<br />
<p style="padding: 16px 0px 16px 0px;">
<a t-att-href="access_link" t-att-data-oe-model="activity.res_model" t-att-data-oe-id="activity.res_id"
style="background-color:#875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">
View <t t-esc="model_description or 'document'"/>
</a>
Dear <span t-field="activity.user_id.name"/>,
<br/><br/>
<p>
<span t-field="activity.create_uid.name"/> has just assigned you the following activity:
<ul>
<li>Document: "<t t-esc="activity.res_name"/>"
<t t-if="model_description"> (<t t-esc="model_description"/>)</t>
</li>
<li t-if="activity.summary">Summary: <span t-field="activity.summary"/></li>
<li>Deadline: <span t-field="activity.date_deadline"/></li>
</ul>
</p>
<div t-if="activity.note" style="margin-top: 8px;" t-field="activity.note"/>
</div>
</template>
@@ -168,8 +168,7 @@
</html>
</template>
<!-- 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">
<template id="mail_notification_layout" name="Mail: mail notification layout template">
<html t-att-lang="lang">
<header>
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
@@ -201,17 +200,27 @@
</td></tr>
</table></td><td width="7"></td><!--only inner cellspacing-->
</t>
<t t-if="not has_button_access and is_html_empty(subtitle)">
<t t-if="not has_button_access and not subtitles">
<td>
<span style="font-size: 10px;">Your <t t-out="model_description or 'document'"/></span>
<br />
<span style="font-size: 20px; font-weight: bold;">
<span style="font-size: 12px; font-weight: bold;">
<t t-out="message.record_name and message.record_name.replace('/','-') or ''"/>
</span>
</td><td width="7"></td>
</t>
<t t-if="subtitles">
<td style="font-size: 12px;">
<t t-foreach="subtitles" t-as="subtitle">
<span t-attf-style="{{ 'font-weight:bold;' if subtitle_first else '' }}"
t-out="subtitle"/>
<br t-if="not subtitle_last"/>
</t>
</td>
</t>
<t t-if="actions">
<td>
<td>&amp;nbsp;</td>
<td style="font-size: 12px;">
<t t-foreach="actions" t-as="action">
|
<a t-att-href="action['url']" style="color: #875A7B; text-decoration:none !important; text-decoration: none;">
@@ -220,12 +229,6 @@
</t>
</td><td width="7"></td>
</t>
<t t-if="not is_html_empty(subtitle)">
<td style="font-size: 12px;">
<span style="font-weight:bold;" t-out="record.name"/><br/>
<span t-out="subtitle"/>
</td>
</t>
</tr></table></td></tr>
<tr><td>
<hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 5px 0px;"/>
@@ -237,13 +240,10 @@
<tr>
<td style="padding-left: 5px;">
<t t-out="message.body"/>
<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,
</div>
<div>&amp;nbsp;</div>
<t t-if="email_add_signature and not is_html_empty(signature)" class="o_signature">
<br/>
<div style="font-size: 13px;">
<div t-out="record.user_id.sudo().signature"/>
<div t-out="signature"/>
</div>
</t>
</td>
@@ -286,5 +286,17 @@
<t t-out="'&#847; &#8203; ' * 140"/>
</div>
</template>
<template id="mail_notification_paynow" name="Mail: Pay Now mail notification template"
inherit_id="mail.mail_notification_layout" primary="True">
<xpath expr="//t[hasclass('o_signature')]" position="replace">
<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)">
<br/>
<div style="font-size: 13px;">
<div t-out="record.user_id.sudo().signature"/>
</div>
</t>
</xpath>
</template>
</data>
</odoo>
+13 -10
View File
@@ -9,7 +9,8 @@ from dateutil.relativedelta import relativedelta
from odoo import api, exceptions, fields, models, _, Command
from odoo.osv import expression
from odoo.tools.misc import clean_context
from odoo.tools import is_html_empty
from odoo.tools.misc import clean_context, get_lang
class MailActivity(models.Model):
@@ -480,11 +481,11 @@ class MailActivity(models.Model):
model_description = activity.env['ir.model']._get(activity.res_model).display_name
body = activity.env['ir.qweb']._render(
'mail.message_activity_assigned',
dict(
activity=activity,
model_description=model_description,
access_link=activity.env['mail.thread']._notify_get_action_link('view', model=activity.res_model, res_id=activity.res_id),
),
{
'activity': activity,
'model_description': model_description,
'is_html_empty': is_html_empty,
},
minimal_qcontext=True
)
record = activity.env[activity.res_model].browse(activity.res_id)
@@ -492,12 +493,14 @@ class MailActivity(models.Model):
record.message_notify(
partner_ids=activity.user_id.partner_id.ids,
body=body,
subject=_('%(activity_name)s: %(summary)s assigned to you',
activity_name=activity.res_name,
summary=activity.summary or activity.activity_type_id.name),
record_name=activity.res_name,
model_description=model_description,
email_layout_xmlid='mail.mail_notification_light',
email_layout_xmlid='mail.mail_notification_layout',
subject=_('"%(activity_name)s: %(summary)s" assigned to you',
activity_name=activity.res_name,
summary=activity.summary or activity.activity_type_id.name),
subtitles=[_('Activity: %s', activity.activity_type_id.name),
_('Deadline: %s', activity.date_deadline.strftime(get_lang(activity.env).date_format))]
)
def action_done(self):
+6 -3
View File
@@ -2411,7 +2411,7 @@ class MailThread(models.AbstractModel):
mail_auto_delete=True, # mail.mail
model_description=False, force_email_company=False, force_email_lang=False, # rendering
resend_existing=False, force_send=True, send_after_commit=True, # email send
**kwargs):
subtitles=None, **kwargs):
""" Method to send email linked to notified messages.
:param message: ``mail.message`` record to notify;
@@ -2442,6 +2442,7 @@ class MailThread(models.AbstractModel):
:param force_send: send emails directly instead of using queue;
:param send_after_commit: if force_send, tells whether to send emails after
the transaction has been committed using a post-commit hook;
:param subtitles: optional list that will be set as template value "subtitles"
"""
partners_data = [r for r in recipients_data if r['notif'] == 'email']
if not partners_data:
@@ -2460,6 +2461,8 @@ class MailThread(models.AbstractModel):
force_email_company=force_email_company,
force_email_lang=force_email_lang,
) # 10 queries
if subtitles:
template_values['subtitles'] = subtitles
email_layout_xmlid = msg_vals.get('email_layout_xmlid') if msg_vals else message.email_layout_xmlid
template_xmlid = email_layout_xmlid if email_layout_xmlid else 'mail.message_notification_email'
@@ -2649,7 +2652,7 @@ class MailThread(models.AbstractModel):
'model_description': model_description,
'record': self,
'record_name': record_name,
'subtitle': False,
'subtitles': [record_name],
# user / environment
'company': company,
'email_add_signature': email_add_signature,
@@ -3070,7 +3073,7 @@ class MailThread(models.AbstractModel):
body=assignation_msg,
partner_ids=partner_ids,
record_name=record.display_name,
email_layout_xmlid='mail.mail_notification_light',
email_layout_xmlid='mail.mail_notification_layout',
model_description=model_description,
)
+5 -8
View File
@@ -100,15 +100,12 @@
<!-- You have been assigned email -->
<template id="project_message_user_assigned">
<p style="margin: 0px;">
<span>Dear <t t-esc="assignee_name"/>,</span><br />
<div style="margin: 0px; padding: 0px; font-size: 13px;">
<span>Dear <t t-esc="assignee_name"/>,</span>
<br/><br/>
<span style="margin-top: 8px;">You have been assigned to the <t t-esc="model_description or 'document'"/> <t t-esc="object.display_name"/>.</span>
</p>
<p style="padding-top: 24px; padding-bottom: 16px;">
<a t-att-href="access_link" t-att-data-oe-model="object._name" t-att-data-oe-id="object.id" style="background-color:#875A7B; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px;">
View <t t-esc="model_description or 'document'"/>
</a>
</p>
<br/>
</div>
</template>
</data>
</odoo>
+17 -3
View File
@@ -2045,8 +2045,6 @@ class Task(models.Model):
result = super(Task, tasks).write(vals)
self._task_message_auto_subscribe_notify({task: task.user_ids - old_user_ids[task] - self.env.user for task in self})
if 'user_ids' in vals:
tasks._populate_missing_personal_stages()
@@ -2065,6 +2063,8 @@ class Task(models.Model):
if task.display_project_id != task.project_id and not task.parent_id:
# We must make the display_project_id follow the project_id if no parent_id set
task.display_project_id = task.project_id
self._task_message_auto_subscribe_notify({task: task.user_ids - old_user_ids[task] - self.env.user for task in self})
return result
def update_date_end(self, stage_id):
@@ -2141,6 +2141,20 @@ class Task(models.Model):
# Mail gateway
# ---------------------------------------------------
def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False, model_description=False,
force_email_company=False, force_email_lang=False):
render_context = super()._notify_by_email_prepare_rendering_context(
message, msg_vals, model_description=model_description,
force_email_company=force_email_company, force_email_lang=force_email_lang
)
if self.date_deadline:
render_context['subtitles'].append(
_('Deadline: %s', self.date_deadline.strftime(get_lang(self.env).date_format)))
elif self.date_assign:
render_context['subtitles'].append(
_('Assigned On: %s', self.date_assign.strftime(get_lang(self.env).date_format)))
return render_context
@api.model
def _task_message_auto_subscribe_notify(self, users_per_task):
# Utility method to send assignation notification upon writing/creation.
@@ -2165,7 +2179,7 @@ class Task(models.Model):
body=assignation_msg,
partner_ids=user.partner_id.ids,
record_name=task.display_name,
email_layout_xmlid='mail.mail_notification_light',
email_layout_xmlid='mail.mail_notification_layout',
model_description=task_model_description,
)
+7 -5
View File
@@ -366,15 +366,17 @@ class PurchaseOrder(models.Model):
message, msg_vals, model_description=model_description,
force_email_company=force_email_company, force_email_lang=force_email_lang
)
subtitles = [render_context['record'].name]
# don't show price on RFQ mail
if self.state not in ['draft', 'sent']:
if self.date_order:
render_context['subtitle'] = _('%(amount)s due\N{NO-BREAK SPACE}%(date)s',
amount=format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')),
date=format_date(self.env, self.date_order, date_format='short', lang_code=render_context.get('lang'))
)
subtitles.append(_('%(amount)s due\N{NO-BREAK SPACE}%(date)s',
amount=format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')),
date=format_date(self.env, self.date_order, date_format='short', lang_code=render_context.get('lang'))
))
else:
render_context['subtitle'] = format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang'))
subtitles.append(format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')))
render_context['subtitles'] = subtitles
return render_context
def _track_subtype(self, init_values):
+7 -7
View File
@@ -1231,15 +1231,15 @@ class SaleOrder(models.Model):
message, msg_vals, model_description=model_description,
force_email_company=force_email_company, force_email_lang=force_email_lang
)
subtitles = [render_context['record'].name]
if self.validity_date:
render_context['subtitle'] = _(
u'%(amount)s due\N{NO-BREAK SPACE}%(date)s',
amount=format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')),
date=format_date(self.env, self.validity_date, date_format='short', lang_code=render_context.get('lang'))
)
subtitles.append(_(u'%(amount)s due\N{NO-BREAK SPACE}%(date)s',
amount=format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')),
date=format_date(self.env, self.validity_date, date_format='short', lang_code=render_context.get('lang'))
))
else:
render_context['subtitle'] = format_amount(
self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang'))
subtitles.append(format_amount(self.env, self.amount_total, self.currency_id, lang_code=render_context.get('lang')))
render_context['subtitles'] = subtitles
return render_context
def _sms_get_number_fields(self):
+1 -1
View File
@@ -176,7 +176,7 @@ class TestActivityFlow(TestActivityCommon):
rec = self.test_record.with_user(self.user_employee)
with self.assertSinglePostNotifications(
[{'partner': self.partner_admin, 'type': 'email'}],
message_info={'content': 'assigned you an activity', 'subtype': 'mail.mt_note', 'message_type': 'user_notification'}):
message_info={'content': 'assigned you the following activity', 'subtype': 'mail.mt_note', 'message_type': 'user_notification'}):
activity = rec.activity_schedule(
'test_mail.mail_act_test_todo',
user_id=self.user_admin.id)