[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:
@@ -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&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&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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 & 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 & 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>
|
||||
|
||||
@@ -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&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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user