[MERGE][IMP] mail: notify when assigning another user to an activity

Purpose of this merge is to ease the use and discovery of activities by
notifying people assigned on an activity. This is limited to activities
assigned to another user than the creator to avoid being notified of
activities you just created for yourself.

This merge is related to task ID 1854820. Closes PR #25265.
This commit is contained in:
Thibault Delavallée
2018-07-27 17:23:01 +02:00
6 changed files with 118 additions and 54 deletions
+57
View File
@@ -320,6 +320,63 @@
</table>
</template>
<!-- Discuss utility templates for notifications -->
<template id="message_user_assigned">
<p style="margin: 0px;">
<span>Dear <t t-esc="object.user_id.name"/>,</span><br />
<span style="margin-top: 8px;">You have been assigned to the <t t-esc="object._description.lower()"/> <t t-esc="object.name_get()[0][1]"/>.</span>
</p>
<p style="margin-top: 24px; margin-bottom: 16px;">
<a t-att-href="'/mail/view?model=%s&amp;res_id=%s' % (object._name, object.id)" style="background-color:#875A7B; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px;">
View <t t-esc="object._description.lower()"/>
</a>
</p>
</template>
<template id="message_activity_done">
<div>
<p>
<strong><span t-attf-class="fa #{activity.activity_type_id.icon}"/></strong> <span t-field="activity.activity_type_id.name"/> done
by <span t-field="activity.user_id.name"/><span t-if="activity.summary">:</span>
<span t-if="activity.summary" t-field="activity.summary"/>
</p>
<div t-field="activity.note" style="margin-left: 8px;"/>
<div t-if="activity.feedback" style="margin-top: 8px;">
<strong> Feedback</strong>
<div t-field="activity.feedback" style="margin-left: 8px;"></div>
</div>
</div>
</template>
<template id="message_activity_assigned">
<div style="margin: 0px; padding: 0px; font-size: 13px;">
<span t-field="activity.create_user_id.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="margin: 16px 0px 16px 0px;">
<a t-att-href="'/mail/view?model=%s&amp;res_id=%s' % (activity.res_model, 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>
</p>
<div t-if="activity.note" style="margin-top: 8px;" t-field="activity.note"/>
</div>
</template>
<!-- Mail gateway templates -->
<template id="mail_bounce_catchall">
<div>
<p>Hello <t t-esc="message['email_from']"/>,</p>
<p>The email sent to <t t-esc="message['to']"/> cannot be processed. This address
is used to collect replies and should not be used to directly contact <t t-esc="res_company.name"/>.</p>
<p>Please contact us instead using <a t-att-href="'mailto:%s' % res_company.email"><t t-esc="res_company.email"/></a></p>
<p>Regards,</p>
<p>The <t t-esc="res_company.name"/> team.</p>
</div>
<blockquote><t t-esc="message['body']"/></blockquote>
</template>
<!-- Channel and moderation related data -->
<template id="mail_channel_notify_moderation">
<div>
+23 -2
View File
@@ -5,8 +5,6 @@ from datetime import date, datetime, timedelta
import pytz
from odoo import api, exceptions, fields, models, _
from odoo.osv import expression
from odoo.tools import pycompat
class MailActivityType(models.Model):
@@ -231,6 +229,8 @@ class MailActivity(models.Model):
# check target user has rights on document otherwise we have to prevent activity creation
if activity_user.user_id != self.env.user:
activity_user._check_access_assignation()
if not self.env.context.get('mail_activity_quick_update', False):
activity_user.action_notify()
self.env[activity_user.res_model].browse(activity_user.res_id).message_subscribe(partner_ids=[activity_user.user_id.partner_id.id])
if activity.date_deadline <= fields.Date.today():
@@ -249,6 +249,8 @@ class MailActivity(models.Model):
if values.get('user_id'):
if values['user_id'] != self.env.uid:
self._check_access_assignation()
if not self.env.context.get('mail_activity_quick_update', False):
self.action_notify()
for activity in self:
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
if activity.date_deadline <= fields.Date.today():
@@ -273,6 +275,25 @@ class MailActivity(models.Model):
{'type': 'activity_updated', 'activity_deleted': True})
return super(MailActivity, self.sudo()).unlink()
@api.multi
def action_notify(self):
body_template = self.env.ref('mail.message_activity_assigned')
for activity in self:
model_description = self.env[activity.res_model]._description.lower()
body = body_template.render(
dict(activity=activity, model_description=model_description),
engine='ir.qweb',
minimal_qcontext=True
)
self.env['mail.thread'].message_notify(
partner_ids=activity.user_id.partner_id.ids,
body=body,
subject=_('%s: %s assigned to you') % (activity.res_name, activity.summary or activity.activity_type_id.name),
record_name=activity.res_name,
model_description=model_description,
notif_layout='mail.mail_notification_light'
)
@api.multi
def action_done(self):
""" Wrapper without feedback because web button add context as
+5 -2
View File
@@ -115,8 +115,11 @@
<button string="Schedule" name="action_close_dialog"
type="object" class="btn-primary"/>
<button string="Mark as Done" name="action_done"
type="object" class="btn-secondary"/>
<button string="Done &amp; Schedule Next" name="action_done_schedule_next" type="object" class="btn-secondary"/>
type="object" class="btn-secondary"
context="{'mail_activity_quick_update': True}"/>
<button string="Done &amp; Schedule Next" name="action_done_schedule_next"
type="object" class="btn-secondary"
context="{'mail_activity_quick_update': True}"/>
<button string="Discard" class="btn-secondary" special="cancel" />
</footer>
</sheet>
-39
View File
@@ -109,44 +109,5 @@
</p>
</template>
<template id="message_user_assigned">
<p style="margin: 0px;">
<span>Dear <t t-esc="object.user_id.name"/>,</span><br />
<span style="margin-top: 8px;">You have been assigned to the <t t-esc="object._description.lower()"/> <t t-esc="object.name_get()[0][1]"/>.</span>
</p>
<p style="margin-top: 24px; margin-bottom: 16px;">
<a t-att-href="'/mail/view?model=%s&amp;res_id=%s' % (object._name, object.id)" style="background-color:#875A7B; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px;">
View <t t-esc="object._description.lower()"/>
</a>
</p>
</template>
<template id="message_activity_done">
<div>
<p>
<strong><span t-attf-class="fa #{activity.activity_type_id.icon}"/></strong> <span t-field="activity.activity_type_id.name"/> done
by <span t-field="activity.user_id.name"/><span t-if="activity.summary">:</span>
<span t-if="activity.summary" t-field="activity.summary"/>
</p>
<div t-field="activity.note" style="margin-left: 8px;"/>
<div t-if="activity.feedback" style="margin-top: 8px;">
<strong> Feedback</strong>
<div t-field="activity.feedback" style="margin-left: 8px;"></div>
</div>
</div>
</template>
<template id="mail_bounce_catchall">
<div>
<p>Hello <t t-esc="message['email_from']"/>,</p>
<p>The email sent to <t t-esc="message['to']"/> cannot be processed. This address
is used to collect replies and should not be used to directly contact <t t-esc="res_company.name"/>.</p>
<p>Please contact us instead using <a t-att-href="'mailto:%s' % res_company.email"><t t-esc="res_company.email"/></a></p>
<p>Regards,</p>
<p>The <t t-esc="res_company.name"/> team.</p>
</div>
<blockquote><t t-esc="message['body']"/></blockquote>
</template>
</data>
</odoo>
+13 -11
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from json import dumps, loads
import json
@@ -43,23 +42,23 @@ class BaseFunctionalTest(common.SavepointCase):
def assertNotifications(self, **counters):
""" Counters: 'partner_attribute': 'inbox' or 'email' """
try:
init_messages = self.test_record.message_ids
init = {}
partners = self.env['res.partner']
for partner_attribute in counters.keys():
partner = getattr(self, partner_attribute)
partners |= partner
partners |= getattr(self, partner_attribute)
init_notifs = self.env['mail.notification'].sudo().search([('res_partner_id', 'in', partners.ids)])
for partner in partners:
if partner.user_ids:
init[partner] = {
'na_counter': self.test_record.sudo(partner.user_ids[0]).message_needaction_counter,
'na_counter': len([n for n in init_notifs if n.res_partner_id == partner]),
}
yield
finally:
new_messages = self.test_record.sudo().message_ids - init_messages
new_notifications = self.env['mail.notification'].search([
new_notifications = self.env['mail.notification'].sudo().search([
('res_partner_id', 'in', partners.ids),
('mail_message_id', 'in', new_messages.ids)
('id', 'not in', init_notifs.ids)
])
new_messages = new_notifications.mapped('mail_message_id')
for partner_attribute in counters.keys():
counter, notif_type, notif_read = counters[partner_attribute]
@@ -70,9 +69,12 @@ class BaseFunctionalTest(common.SavepointCase):
if partner.user_ids:
expected = init[partner]['na_counter'] + counter if notif_read == 'unread' else init[partner]['na_counter']
self.assertEqual(expected, self.test_record.sudo(partner.user_ids[0]).message_needaction_counter,
'Invalid number of notification for %s: %s instead of %s' %
(partner.name, expected, self.test_record.sudo(partner.user_ids[0]).message_needaction_counter))
real = self.env['mail.notification'].sudo().search_count([
('res_partner_id', '=', partner.id),
('is_read', '=', False)
])
self.assertEqual(expected, real, 'Invalid number of notification for %s: %s instead of %s' %
(partner.name, real, expected))
if partner_notif:
self.assertTrue(all(n.is_email == (notif_type == 'email') for n in partner_notif))
self.assertTrue(all(n.is_read == (notif_read == 'read') for n in partner_notif),
@@ -144,6 +144,26 @@ class TestMailActivity(BaseFunctionalTest):
self.assertEqual(self.test_record.activity_ids, self.env['mail.activity'])
self.assertEqual(len(self.test_record.message_ids), 2)
def test_activity_notify_other_user(self):
self.user_admin.notification_type = 'email'
rec = self.test_record.sudo(self.user_employee)
with self.assertNotifications(partner_admin=(1, 'email', 'read')):
activity = rec.activity_schedule(
'test_mail.mail_act_test_todo',
user_id=self.user_admin.id)
self.assertEqual(activity.create_user_id, self.user_employee)
self.assertEqual(activity.user_id, self.user_admin)
def test_activity_notify_same_user(self):
self.user_employee.notification_type = 'email'
rec = self.test_record.sudo(self.user_employee)
with self.assertNotifications(partner_employee=(0, 'email', 'read')):
activity = rec.activity_schedule(
'test_mail.mail_act_test_todo',
user_id=self.user_employee.id)
self.assertEqual(activity.create_user_id, self.user_employee)
self.assertEqual(activity.user_id, self.user_employee)
def test_activity_security_user_access(self):
activity = self.test_record.activity_schedule(
'test_mail.mail_act_test_todo',