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 @@
+
+ mail.notifications: group_user: write its own entries
+
+
+ [('res_partner_id', '=', user.partner_id.id)]
+
+
+
+
+
mail.message.subtype: portal/public: read public subtypes
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 @@
(from #)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+