summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@openerp.com>2016-07-07 16:03:05 +0200
committerThibault Delavallée <tde@openerp.com>2016-09-01 12:57:56 +0200
commit72dfcae2a4b03623e780b857abf37c7aec5b8456 (patch)
treef5492bd2b33cb21fc8e8d3523a56d2b314007a2e
parent21949937636a2b0fc5b71a2667eede1750e05175 (diff)
[IMP] mail: improve notification management for customers
Currently it is impossible in Discuss to know whether an email has been sent to a customer and whether it failed or bounced. A notified partner has an entry in the needaction m2m table. In this commit we decorate this table to add fields about the email notification: is an email sent, did it failed, did it bounce. This information is kept only for customers. Internal users does not use this information. Moreover their notification is deleted once the message is read in the Chatter. This avoids having a notification table that grows quickly. Chatter now holds a new icon for email details. It allows to know on a thread status of emails sent to customers.
-rw-r--r--addons/mail/models/__init__.py1
-rw-r--r--addons/mail/models/mail_mail.py13
-rw-r--r--addons/mail/models/mail_message.py53
-rw-r--r--addons/mail/models/mail_notification.py30
-rw-r--r--addons/mail/models/mail_thread.py2
-rw-r--r--addons/mail/models/res_partner.py20
-rw-r--r--addons/mail/security/ir.model.access.csv3
-rw-r--r--addons/mail/security/mail_security.xml10
-rw-r--r--addons/mail/static/src/js/chat_manager.js2
-rw-r--r--addons/mail/static/src/js/chat_window.js1
-rw-r--r--addons/mail/static/src/js/client_action.js1
-rw-r--r--addons/mail/static/src/js/thread.js1
-rw-r--r--addons/mail/static/src/less/thread.less46
-rw-r--r--addons/mail/static/src/xml/thread.xml15
-rw-r--r--addons/mail/views/mail_message_views.xml22
15 files changed, 202 insertions, 18 deletions
diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py
index 1ba9746eb4e..2426a6dc9e6 100644
--- a/addons/mail/models/__init__.py
+++ b/addons/mail/models/__init__.py
@@ -4,6 +4,7 @@ import mail_message_subtype
import mail_tracking_value
import mail_alias
import mail_followers
+import mail_notification
import mail_message
import mail_mail
import mail_thread
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 5d90c54c0af..7297c763953 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -129,6 +129,19 @@ class MailMail(models.Model):
:param browse_record mail: the mail that was just sent
:return: True
"""
+ notif_emails = self.filtered(lambda email: email.notification)
+ if notif_emails:
+ notifications = self.env['mail.notification'].search([
+ ('mail_message_id', 'in', notif_emails.mapped('mail_message_id').ids),
+ ('is_email', '=', True)])
+ if mail_sent:
+ notifications.write({
+ 'email_status': 'sent',
+ })
+ else:
+ notifications.write({
+ 'email_status': 'exception',
+ })
if mail_sent:
self.sudo().filtered(lambda self: self.auto_delete).unlink()
return True
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 8d6a933221b..0f8054d958a 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -82,6 +82,10 @@ class Message(models.Model):
help='Need Action')
channel_ids = fields.Many2many(
'mail.channel', 'mail_message_mail_channel_rel', string='Channels')
+ # notifications
+ notification_ids = fields.One2many(
+ 'mail.notification', 'mail_message_id', 'Notifications',
+ auto_join=True, copy=False)
# user interface
starred_partner_ids = fields.Many2many(
'res.partner', 'mail_message_res_partner_starred_rel', string='Favorited By')
@@ -105,7 +109,10 @@ class Message(models.Model):
@api.multi
def _get_needaction(self):
""" Need action on a mail.message = notified on my channel """
- my_messages = self.sudo().filtered(lambda msg: self.env.user.partner_id in msg.needaction_partner_ids)
+ my_messages = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', self.ids),
+ ('res_partner_id', '=', self.env.user.partner_id.id),
+ ('is_read', '=', False)]).mapped('mail_message_id')
for message in self:
message.needaction = message in my_messages
@@ -117,8 +124,8 @@ class Message(models.Model):
@api.model
def _search_needaction(self, operator, operand):
if operator == '=' and operand:
- return [('needaction_partner_ids', 'in', self.env.user.partner_id.id)]
- return [('needaction_partner_ids', 'not in', self.env.user.partner_id.id)]
+ return ['&', ('notification_ids.res_partner_id', '=', self.env.user.partner_id.id), ('notification_ids.is_read', '=', False)]
+ return ['&', ('notification_ids.res_partner_id', '=', self.env.user.partner_id.id), ('notification_ids.is_read', '=', True)]
@api.depends('starred_partner_ids')
def _get_starred(self):
@@ -147,7 +154,8 @@ class Message(models.Model):
""" Remove all needactions of the current partner. If channel_ids is
given, restrict to messages written in one of those channels. """
partner_id = self.env.user.partner_id.id
- if domain is None:
+ delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
+ if domain is None and delete_mode:
query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s"
args = [(partner_id,)]
if channel_ids:
@@ -170,7 +178,14 @@ class Message(models.Model):
if channel_ids:
msg_domain += [('channel_ids', 'in', channel_ids)]
unread_messages = self.search(expression.AND([msg_domain, domain]))
- unread_messages.sudo().write({'needaction_partner_ids': [(3, partner_id)]})
+ notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', unread_messages.ids),
+ ('res_partner_id', '=', self.env.user.partner_id.id),
+ ('is_read', '=', False)])
+ if delete_mode:
+ notifications.unlink()
+ else:
+ notifications.write({'is_read': True})
ids = unread_messages.mapped('id')
notification = {'type': 'mark_as_read', 'message_ids': ids, 'channel_ids': channel_ids}
@@ -193,14 +208,20 @@ class Message(models.Model):
def set_message_done(self):
""" Remove the needaction from messages for the current partner. """
partner_id = self.env.user.partner_id
- messages = self.filtered(lambda msg: partner_id in msg.needaction_partner_ids)
- if not len(messages):
+ delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
+
+ notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', self.ids),
+ ('res_partner_id', '=', partner_id.id),
+ ('is_read', '=', False)])
+
+ if not notifications:
return
- messages.sudo().write({'needaction_partner_ids': [(3, partner_id.id)]})
# notifies changes in messages through the bus. To minimize the number of
# notifications, we need to group the messages depending on their channel_ids
groups = []
+ messages = notifications.mapped('mail_message_id')
current_channel_ids = messages[0].channel_ids
current_group = []
for record in messages:
@@ -215,6 +236,11 @@ class Message(models.Model):
current_group = [record.id]
current_channel_ids = record.channel_ids
+ if delete_mode:
+ notifications.unlink()
+ else:
+ notifications.write({'is_read': True})
+
for (msg_ids, channel_ids) in groups:
notification = {'type': 'mark_as_read', 'message_ids': msg_ids, 'channel_ids': [c.id for c in channel_ids]}
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', partner_id.id), notification)
@@ -270,6 +296,8 @@ class Message(models.Model):
partners |= message.partner_ids
elif not message.subtype_id and message.partner_ids: # take specified people of message without a subtype (log)
partners |= message.partner_ids
+ if message.needaction_partner_ids: # notified
+ partners |= message.needaction_partner_ids
if message.attachment_ids:
attachments |= message.attachment_ids
if message.tracking_value_ids:
@@ -311,6 +339,11 @@ class Message(models.Model):
else:
partner_ids = [partner_tree[partner.id] for partner in message.partner_ids
if partner.id in partner_tree]
+
+ customer_email_data = []
+ for notification in message.notification_ids.filtered(lambda notif: notif.res_partner_id.partner_share):
+ customer_email_data.append((partner_tree[notification.res_partner_id.id][0], partner_tree[notification.res_partner_id.id][1], notification.email_status))
+
attachment_ids = []
for attachment in message.attachment_ids:
if attachment.id in attachments_tree:
@@ -323,6 +356,10 @@ class Message(models.Model):
message_dict.update({
'author_id': author,
'partner_ids': partner_ids,
+ 'customer_email_status': (all(d[2] == 'sent' for d in customer_email_data) and 'sent') or
+ (any(d[2] == 'exception' for d in customer_email_data) and 'exception') or
+ (any(d[2] == 'bounce' for d in customer_email_data) and 'bounce') or 'ready',
+ 'customer_email_data': customer_email_data,
'attachment_ids': attachment_ids,
'tracking_value_ids': tracking_value_ids,
})
diff --git a/addons/mail/models/mail_notification.py b/addons/mail/models/mail_notification.py
new file mode 100644
index 00000000000..e14754b7e8f
--- /dev/null
+++ b/addons/mail/models/mail_notification.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models
+
+
+class Notification(models.Model):
+ _name = 'mail.notification'
+ _table = 'mail_message_res_partner_needaction_rel'
+ _rec_name = 'res_partner_id'
+ _log_access = False
+ _description = 'Message Notifications'
+
+ mail_message_id = fields.Many2one(
+ 'mail.message', 'Message', index=True, ondelete='cascade', required=True)
+ res_partner_id = fields.Many2one(
+ 'res.partner', 'Needaction Recipient', index=True, ondelete='cascade', required=True)
+ is_read = fields.Boolean('Is Read', index=True)
+ is_email = fields.Boolean('Sent by Email', index=True)
+ email_status = fields.Selection([
+ ('ready', 'Ready to Send'),
+ ('sent', 'Sent'),
+ ('bounce', 'Bounced'),
+ ('exception', 'Exception')], 'Email Status',
+ default='ready', index=True)
+
+ @api.model_cr
+ def init(self):
+ self._cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_res_partner_id_is_read_email_status_mail_message_id',))
+ if not self._cr.fetchone():
+ self._cr.execute('CREATE INDEX mail_notification_res_partner_id_is_read_email_status_mail_message_id ON mail_message_res_partner_needaction_rel (res_partner_id, is_read, email_status, mail_message_id)')
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 9a84e8546a4..cea21f5c668 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -190,7 +190,7 @@ class MailThread(models.AbstractModel):
# search for unread messages, directly in SQL to improve performances
self._cr.execute(""" SELECT msg.res_id FROM mail_message msg
RIGHT JOIN mail_message_res_partner_needaction_rel rel
- ON rel.mail_message_id = msg.id AND rel.res_partner_id = %s
+ ON rel.mail_message_id = msg.id AND rel.res_partner_id = %s AND (rel.is_read = false OR rel.is_read IS NULL)
WHERE msg.model = %s AND msg.res_id in %s""",
(self.env.user.partner_id.id, self._name, tuple(self.ids),))
for result in self._cr.fetchall():
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py
index 2e47dacc1dc..7c3a96ba36b 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -133,6 +133,17 @@ class Partner(models.Model):
emails |= self.env['mail.mail'].create(create_values)
return emails, recipients_nbr
+ @api.model
+ def _notify_udpate_notifications(self, emails):
+ for email in emails:
+ notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', '=', email.mail_message_id.id),
+ ('res_partner_id', 'in', email.recipient_ids.ids)])
+ notifications.write({
+ 'is_email': True,
+ 'email_status': 'ready',
+ })
+
@api.multi
def _notify(self, message, force_send=False, user_signature=True):
# TDE TODO: model-dependant ? (like customer -> always email ?)
@@ -166,7 +177,6 @@ class Partner(models.Model):
base_template_ctx['signature'] = False
base_mail_values = self._notify_prepare_email_values(message)
-
# classify recipients: actions / no action
if message.model and message.res_id and hasattr(self.env[message.model], '_message_notification_recipients'):
recipients = self.env[message.model].browse(message.res_id)._message_notification_recipients(message, self)
@@ -185,6 +195,9 @@ class Partner(models.Model):
fol_values = template_fol.generate_email(message.id, fields=['body_html', 'subject'])
# send email
new_emails, new_recipients_nbr = self._notify_send(fol_values['body'], fol_values['subject'], recipient_template_values['followers'], **base_mail_values)
+ # update notifications
+ self._notify_udpate_notifications(new_emails)
+
emails |= new_emails
recipients_nbr += new_recipients_nbr
if recipient_template_values['not_followers']:
@@ -196,6 +209,9 @@ class Partner(models.Model):
not_values = template_not.generate_email(message.id, fields=['body_html', 'subject'])
# send email
new_emails, new_recipients_nbr = self._notify_send(not_values['body'], not_values['subject'], recipient_template_values['not_followers'], **base_mail_values)
+ # update notifications
+ self._notify_udpate_notifications(new_emails)
+
emails |= new_emails
recipients_nbr += new_recipients_nbr
@@ -226,7 +242,7 @@ class Partner(models.Model):
self.env.cr.execute("""
SELECT count(*) as needaction_count
FROM mail_message_res_partner_needaction_rel R
- WHERE R.res_partner_id = %s """, (self.env.user.partner_id.id,))
+ WHERE R.res_partner_id = %s AND (R.is_read = false OR R.is_read IS NULL)""", (self.env.user.partner_id.id,))
return self.env.cr.dictfetchall()[0].get('needaction_count')
_logger.error('Call to needaction_count without partner_id')
return 0
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 384e99d91fa..59a37569bfe 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -10,6 +10,9 @@ access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_portal,mail.followers.portal,model_mail_followers,base.group_portal,1,1,1,0
access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,1,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
+access_mail_notification_portal,mail.notification.portal,model_mail_notification,base.group_portal,1,0,0,0
+access_mail_notification_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
+access_mail_notification_system, mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
access_mail_channel_all,mail.group.all,model_mail_channel,,1,0,0,0
access_mail_channel_user,mail.group.user,model_mail_channel,base.group_user,1,1,1,1
access_mail_channel_partner_public,mail.channel.partner.public,model_mail_channel_partner,base.group_public,1,0,0,0
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index deab4c51438..65495db6925 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -23,6 +23,16 @@
<field name="perm_read" eval="False"/>
</record>
+ <record id="ir_rule_mail_notifications_group_user" model="ir.rule">
+ <field name="name">mail.notifications: group_user: write its own entries</field>
+ <field name="model_id" ref="model_mail_notification"/>
+ <field name="groups" eval="[(4, ref('base.group_user'))]"/>
+ <field name="domain_force">[('res_partner_id', '=', user.partner_id.id)]</field>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_unlink" eval="False"/>
+ <field name="perm_read" eval="False"/>
+ </record>
+
<record id="mail_message_subtype_rule_public" model="ir.rule">
<field name="name">mail.message.subtype: portal/public: read public subtypes</field>
<field name="model_id" ref="model_mail_message_subtype"/>
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
index d37e9dcf282..2cf6090871f 100644
--- a/addons/mail/static/src/js/chat_manager.js
+++ b/addons/mail/static/src/js/chat_manager.js
@@ -132,6 +132,8 @@ function make_message (data) {
attachment_ids: data.attachment_ids || [],
subject: data.subject,
email_from: data.email_from,
+ customer_email_status: data.customer_email_status,
+ customer_email_data: data.customer_email_data,
record_name: data.record_name,
tracking_value_ids: data.tracking_value_ids,
channel_ids: data.channel_ids,
diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js
index da8900b8230..7bdece26fb3 100644
--- a/addons/mail/static/src/js/chat_window.js
+++ b/addons/mail/static/src/js/chat_window.js
@@ -32,6 +32,7 @@ return Widget.extend({
autofocus: true,
display_stars: true,
display_reply_icon: false,
+ display_email_icon: false,
placeholder: _t("Say something"),
input_less: false,
});
diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js
index f94997c946c..7de0b1ac83b 100644
--- a/addons/mail/static/src/js/client_action.js
+++ b/addons/mail/static/src/js/client_action.js
@@ -491,6 +491,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
display_empty_channel: !messages.length && !this.domain.length,
display_no_match: !messages.length && this.domain.length,
display_subject: this.channel.mass_mailing || this.channel.id === "channel_inbox",
+ display_email_icon: false,
display_reply_icon: true,
};
},
diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js
index ea593d21dfa..2176eff55fa 100644
--- a/addons/mail/static/src/js/thread.js
+++ b/addons/mail/static/src/js/thread.js
@@ -72,6 +72,7 @@ var Thread = Widget.extend({
display_document_link: true,
display_avatar: true,
squash_close_messages: true,
+ display_email_icon: true,
display_reply_icon: false,
});
this.expanded_msg_ids = [];
diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less
index 5da7d2b4963..7a9efa45731 100644
--- a/addons/mail/static/src/less/thread.less
+++ b/addons/mail/static/src/less/thread.less
@@ -70,7 +70,8 @@
}
}
- i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply {
+
+ i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply, i.o_thread_message_email {
cursor: pointer;
opacity: 0;
&.fa-star {
@@ -84,7 +85,7 @@
opacity: @mail-thread-side-date-opacity;
}
- i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply {
+ i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply, i.o_thread_message_email {
opacity: @mail-thread-icon-opacity;
&:hover {
opacity: 1;
@@ -146,9 +147,48 @@
}
}
- .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply {
+ .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_email {
padding: 4px;
}
+
+ i.o_thread_message_email {
+ &.o_thread_message_email_ready {
+ color: grey;
+ }
+ &.o_thread_message_email_exception {
+ color: red;
+ opacity: 1;
+ }
+ &.o_thread_message_email_bounce {
+ color: red;
+ opacity: 1;
+ }
+ }
+
+ .o_thread_tooltip_container {
+ display: inline;
+ position: relative;
+
+ .o_thread_tooltip_content {
+ top: 10px;
+ background: @gray-lighter;
+ padding: 4px;
+ border: solid @gray-lighter 1px;
+ border-radius: 5px;
+ color: gray;
+ width: 300px;
+ opacity: 0;
+ position: absolute;
+ transition: opacity 0.5s;
+ i.fa-exclamation {
+ color: red;
+ }
+ }
+
+ .o_thread_tooltip:hover + .o_thread_tooltip_content {
+ opacity: 1;
+ }
+ }
}
}
.o_thread_title {
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index 9896cb0ce6e..47e84e38f60 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -94,6 +94,21 @@
<t t-if="message.origin_id &amp;&amp; (message.origin_id !== options.channel_id)">
(from <a t-att-data-oe-id="message.origin_id" href="#">#<t t-esc="message.origin_name"/></a>)
</t>
+ <span t-if="options.display_email_icon &amp;&amp; message.customer_email_data.length" class="o_thread_tooltip_container">
+ <i t-att-class="'o_thread_tooltip o_thread_message_email o_thread_message_email_' + message.customer_email_status + ' fa fa-envelope-o'"/>
+ <span class="o_thread_tooltip_content">
+ <t t-foreach="message.customer_email_data" t-as="customer">
+ <span>
+ <t t-if="customer[2] == 'sent'"><i class='fa fa-check'/></t>
+ <t t-if="customer[2] == 'bounce'"><i class='fa fa-exclamation'/></t>
+ <t t-if="customer[2] == 'exception'"><i class='fa fa-exclamation'/></t>
+ <t t-if="customer[2] == 'ready'"><i class='fa fa-send-o'/></t>
+ <t t-esc="customer[1]"/>
+ </span>
+ <br />
+ </t>
+ </span>
+ </span>
<span>
<i t-if="options.display_stars &amp;&amp; !message.is_system_notification"
t-att-class="'fa fa-lg o_thread_message_star ' + (message.is_starred ? 'fa-star' : 'fa-star-o')"
diff --git a/addons/mail/views/mail_message_views.xml b/addons/mail/views/mail_message_views.xml
index 338e3f473f4..636aefa0863 100644
--- a/addons/mail/views/mail_message_views.xml
+++ b/addons/mail/views/mail_message_views.xml
@@ -59,10 +59,24 @@
</page>
<page string="Recipients">
<group>
- <field name="partner_ids" widget="many2many_tags"/>
- <field name="channel_ids" widget="many2many_tags"/>
- <field name="needaction_partner_ids" widget="many2many_tags"/>
- <field name="starred_partner_ids" widget="many2many_tags"/>
+ <group>
+ <field name="partner_ids" widget="many2many_tags"/>
+ <field name="channel_ids" widget="many2many_tags"/>
+ </group>
+ <group>
+ <field name="needaction_partner_ids" widget="many2many_tags"/>
+ <field name="starred_partner_ids" widget="many2many_tags"/>
+ </group>
+ </group>
+ <group>
+ <field name="notification_ids">
+ <tree>
+ <field name="res_partner_id"/>
+ <field name="is_read"/>
+ <field name="is_email"/>
+ <field name="email_status"/>
+ </tree>
+ </field>
</group>
</page>
<page string="Tracking">