summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/mail/models/mail_mail.py2
-rw-r--r--addons/mail/models/mail_message.py91
-rw-r--r--addons/mail/models/mail_notification.py21
-rw-r--r--addons/mail/static/src/js/discuss.js2
-rw-r--r--addons/mail/static/src/js/models/mail_failure.js41
-rw-r--r--addons/mail/static/src/js/models/messages/abstract_message.js56
-rw-r--r--addons/mail/static/src/js/models/messages/message.js74
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js2
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js35
-rw-r--r--addons/mail/static/src/js/thread_widget.js42
-rw-r--r--addons/mail/static/src/js/thread_windows/abstract_thread_window.js2
-rw-r--r--addons/mail/static/src/scss/thread.scss10
-rw-r--r--addons/mail/static/src/xml/thread.xml37
-rw-r--r--addons/mail/tests/common.py75
-rw-r--r--addons/mail/wizard/mail_resend_cancel.py2
-rw-r--r--addons/mail/wizard/mail_resend_message.py4
-rw-r--r--addons/sms/models/mail_message.py54
-rw-r--r--addons/sms/models/sms_sms.py1
-rw-r--r--addons/sms/static/src/js/mail_failure.js26
-rw-r--r--addons/sms/static/src/js/message.js73
-rw-r--r--addons/sms/static/src/js/sms_notification_manager.js64
-rw-r--r--addons/sms/static/src/js/systray_messaging_menu.js2
-rw-r--r--addons/sms/static/src/js/thread_widget.js86
-rw-r--r--addons/sms/static/src/scss/thread.scss5
-rw-r--r--addons/sms/static/src/xml/thread.xml31
-rw-r--r--addons/sms/views/assets.xml2
-rw-r--r--addons/sms/wizard/sms_cancel.py2
-rw-r--r--addons/sms/wizard/sms_resend.py4
-rw-r--r--addons/snailmail/__manifest__.py2
-rw-r--r--addons/snailmail/models/__init__.py1
-rw-r--r--addons/snailmail/models/mail_message.py17
-rw-r--r--addons/snailmail/models/mail_notification.py18
-rw-r--r--addons/snailmail/models/snailmail_letter.py109
-rw-r--r--addons/snailmail/static/src/js/mail_failure.js26
-rw-r--r--addons/snailmail/static/src/js/message.js61
-rw-r--r--addons/snailmail/static/src/js/snailmail_notification_manager.js67
-rw-r--r--addons/snailmail/static/src/js/thread_widget.js130
-rw-r--r--addons/snailmail/static/src/scss/thread.scss17
-rw-r--r--addons/snailmail/static/src/xml/thread.xml68
-rw-r--r--addons/snailmail/static/tests/chatter_snailmail_tests.js212
-rw-r--r--addons/snailmail/views/assets.xml2
-rw-r--r--addons/test_mail_full/tests/test_sms_management.py45
-rw-r--r--addons/test_mail_full/tests/test_sms_performance.py6
43 files changed, 615 insertions, 1012 deletions
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 49abe210f7a..36db2a281df 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -178,7 +178,7 @@ class MailMail(models.Model):
})
messages = notifications.mapped('mail_message_id').filtered(lambda m: m.is_thread_message())
# TDE TODO: could be great to notify message-based, not notifications-based, to lessen number of notifs
- messages._notify_mail_failure_update() # notify user that we have a failure
+ messages._notify_message_notification_update() # notify user that we have a failure
if not failure_type or failure_type == 'RECIPIENT': # if we have another error, we want to keep the mail.
mail_to_delete_ids = [mail.id for mail in self if mail.auto_delete]
self.browse(mail_to_delete_ids).sudo().unlink()
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index f17852b203d..25ed19c7629 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -12,6 +12,7 @@ from odoo import _, api, fields, models, modules, tools
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.osv import expression
+from odoo.tools import groupby
_logger = logging.getLogger(__name__)
_image_dataurl = re.compile(r'(data:image/[a-z]+?);base64,([a-z0-9+/\n]{3,}=*)\n*([\'"])(?: data-filename="([^"]*)")?', re.I)
@@ -890,7 +891,7 @@ class Message(models.Model):
for partner_id, message_ids in partner_to_pid.items():
notifications.append([
(self._cr.dbname, 'res.partner', partner_id),
- {'type': 'deletion', 'message_ids': list(message_ids)}
+ {'type': 'deletion', 'message_ids': sorted(list(message_ids))} # sorted to make deterministic for tests
])
self.env['bus.bus'].sendmany(notifications)
self.unlink()
@@ -955,21 +956,6 @@ class Message(models.Model):
else:
author = (0, message_sudo.email_from)
- # Notifications
- customer_email_status = (
- (all(n.notification_status == 'sent' for n in message_sudo.notification_ids if n.notification_type == 'email') and 'sent') or
- (any(n.notification_status == 'exception' for n in message_sudo.notification_ids if n.notification_type == 'email') and 'exception') or
- (any(n.notification_status == 'bounce' for n in message_sudo.notification_ids if n.notification_type == 'email') and 'bounce') or
- 'ready'
- )
- customer_email_data = []
- filtered_notifications = message_sudo.notification_ids.filtered(lambda n:
- n.notification_type == 'email' and n.res_partner_id.active and
- (n.notification_status in ('bounce', 'exception', 'canceled') or n.res_partner_id.partner_share)
- )
- for notification in filtered_notifications:
- customer_email_data.append((notification.res_partner_id.id, notification.res_partner_id.display_name, notification.notification_status))
-
# Attachments
main_attachment = self.env['ir.attachment']
if message_sudo.attachment_ids and message_sudo.res_id and issubclass(self.pool[message_sudo.model], self.pool['mail.thread']):
@@ -1000,8 +986,7 @@ class Message(models.Model):
vals.update({
'author_id': author,
- 'customer_email_status': customer_email_status,
- 'customer_email_data': customer_email_data,
+ 'notifications': message_sudo.notification_ids._filtered_for_web_client()._notification_format(),
'attachment_ids': attachment_ids,
'tracking_value_ids': tracking_value_ids,
})
@@ -1009,14 +994,16 @@ class Message(models.Model):
return vals_list
def message_fetch_failed(self):
+ """Returns all messages, sent by the current user, that have errors, in
+ the format expected by the web client."""
messages = self.search([
('has_error', '=', True),
- ('author_id.id', '=', self.env.user.partner_id.id),
+ ('author_id', '=', self.env.user.partner_id.id),
('res_id', '!=', 0),
('model', '!=', False),
('message_type', '!=', 'user_notification')
])
- return messages._format_mail_failures()
+ return messages._message_notification_format()
@api.model
def message_fetch(self, domain, limit=20, moderated_channel_ids=None):
@@ -1116,47 +1103,34 @@ class Message(models.Model):
'moderation_status',
]
- def _get_mail_failure_dict(self):
+ def _message_notification_format(self):
+ """Returns the current messages and their corresponding notifications in
+ the format expected by the web client.
+
+ Notifications hold the information about each recipient of a message: if
+ the message was successfully sent or if an exception or bounce occurred.
+ """
return {
- 'message_id': self.id,
- 'record_name': self.record_name,
- 'model_name': self.env['ir.model']._get(self.model).display_name,
- 'uuid': self.message_id,
- 'res_id': self.res_id,
- 'model': self.model,
- 'last_message_date': self.date,
- 'module_icon': '/mail/static/src/img/smiley/mailfailure.jpg',
+ message.id: {
+ 'message_id': message.id,
+ 'model_name': message.env['ir.model']._get(message.model).display_name,
+ 'res_id': message.res_id,
+ 'model': message.model,
+ 'last_message_date': message.date,
+ 'message_type': message.message_type,
+ 'notifications': message.notification_ids._filtered_for_web_client()._notification_format(),
+ } for message in self
}
- def _format_mail_failures(self):
- """ A shorter message to notify a failure update """
- failures_infos = []
-
- # prepare notifications computation in batch
- all_notifications = self.env['mail.notification'].sudo().search([
- ('mail_message_id', 'in', self.ids)
- ])
- msgid_to_notif = defaultdict(lambda: self.env['mail.notification'].sudo())
- for notif in all_notifications:
- msgid_to_notif[notif.mail_message_id.id] += notif
-
- # for each channel, build the information header and include the logged partner information
- for message in self:
- notifications = msgid_to_notif[message.id]
- if not any(notification.notification_type == 'email' for notification in notifications):
- continue
- info = dict(message._get_mail_failure_dict(),
- failure_type='mail',
- notifications=dict((notif.res_partner_id.id, (notif.notification_status, notif.res_partner_id.name)) for notif in notifications))
- failures_infos.append(info)
- return failures_infos
-
- def _notify_mail_failure_update(self):
+ def _notify_message_notification_update(self):
+ """Send bus notifications to update status of notifications in the web
+ client. Purpose is to send the updated status per author."""
messages = self.env['mail.message']
for message in self:
# Check if user has access to the record before displaying a notification about it.
# In case the user switches from one company to another, it might happen that he doesn't
# have access to the record related to the notification. In this case, we skip it.
+ # YTI FIXME: check allowed_company_ids if necessary
if message.model and message.res_id:
record = self.env[message.model].browse(message.res_id)
try:
@@ -1166,12 +1140,11 @@ class Message(models.Model):
continue
else:
messages |= message
-
- for author, author_messages in tools.groupby(messages, itemgetter('author_id')):
- self.env['bus.bus'].sendone(
- (self._cr.dbname, 'res.partner', author.id),
- {'type': 'mail_failure', 'elements': self.env['mail.message'].concat(*author_messages)._format_mail_failures()}
- )
+ updates = [[
+ (self._cr.dbname, 'res.partner', author.id),
+ {'type': 'message_notification_update', 'elements': self.env['mail.message'].concat(*author_messages)._message_notification_format()}
+ ] for author, author_messages in groupby(messages.sorted('author_id'), itemgetter('author_id'))]
+ self.env['bus.bus'].sendmany(updates)
# ------------------------------------------------------
# TOOLS
diff --git a/addons/mail/models/mail_notification.py b/addons/mail/models/mail_notification.py
index a45a7223b5f..b1dfe410b7c 100644
--- a/addons/mail/models/mail_notification.py
+++ b/addons/mail/models/mail_notification.py
@@ -82,3 +82,24 @@ class Notification(models.Model):
('notification_status', 'in', ('sent', 'canceled'))
]
return self.search(domain).unlink()
+
+ def _filtered_for_web_client(self):
+ """Returns only the notifications to show on the web client."""
+ return self.filtered(lambda n:
+ n.notification_type != 'inbox' and
+ (n.notification_status in ['bounce', 'exception', 'canceled'] or n.res_partner_id.partner_share)
+ )
+
+ def _notification_format(self):
+ """Returns the current notifications in the format expected by the web
+ client."""
+ return {
+ notif.id: {
+ 'notification_id': notif.id,
+ 'notification_type': notif.notification_type,
+ 'notification_status': notif.notification_status,
+ 'failure_type': notif.failure_type,
+ 'partner_id': notif.res_partner_id.id,
+ 'partner_name': notif.res_partner_id.display_name,
+ } for notif in self
+ }
diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js
index 7b56282565a..29a02738f66 100644
--- a/addons/mail/static/src/js/discuss.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -473,7 +473,7 @@ var Discuss = AbstractAction.extend({
displaySubjectOnMessages: this._thread.isMassMailing() ||
this._thread.getID() === 'mailbox_inbox' ||
this._thread.getID() === 'mailbox_moderation',
- displayEmailIcons: false,
+ displayNotificationIcons: false,
displayReplyIcons: true,
displayBottomThreadFreeSpace: true,
displayModerationCommands: true,
diff --git a/addons/mail/static/src/js/models/mail_failure.js b/addons/mail/static/src/js/models/mail_failure.js
index 232dd86152c..815c95a034f 100644
--- a/addons/mail/static/src/js/models/mail_failure.js
+++ b/addons/mail/static/src/js/models/mail_failure.js
@@ -30,14 +30,12 @@ var MailFailure = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
this._documentID = data.res_id;
this._documentModel = data.model;
- this._failureType = data.failure_type || 'mail';
this._lastMessageDate = moment(); // by default: current datetime
this._messageID = data.message_id;
+ this._messageType = data.message_type;
this._modelName = data.model_name;
this._moduleIcon = data.module_icon;
this._notifications = data.notifications;
- this._recordName = data.record_name;
- this._uuid = data.uuid;
if (data.last_message_date) {
this._lastMessageDate = moment(time.str_to_datetime(data.last_message_date));
@@ -65,14 +63,6 @@ var MailFailure = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
return this._documentID;
},
/**
- * Get the type of failure of this mail failure.
- *
- * @returns {string}
- */
- getFailureType: function () {
- return this._failureType;
- },
- /**
* Get the ID of the message that this mail failure is related to.
*
* @returns {integer}
@@ -81,18 +71,26 @@ var MailFailure = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
return this._messageID;
},
/**
+ * Get the type of the message that this mail failure is related to.
+ *
+ * @returns {string}
+ */
+ getMessageType() {
+ return this._messageType;
+ },
+ /**
* Get a valid object for the 'mail.preview' template
*
* @returns {Object}
*/
getPreview: function () {
var preview = {
- body: _t("An error occurred when sending an email"),
+ body: this._getPreviewBody(),
date: this._lastMessageDate,
documentID: this._documentID,
documentModel: this._documentModel,
id: 'mail_failure',
- imageSRC: this._moduleIcon,
+ imageSRC: this._getPreviewImage(),
title: this._modelName,
};
return preview;
@@ -105,6 +103,23 @@ var MailFailure = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
isLinkedToDocument: function () {
return !!(this._documentModel && this._documentID);
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {string}
+ */
+ _getPreviewBody() {
+ return _t("An error occurred when sending an email.");
+ },
+ /**
+ * @returns {string}
+ */
+ _getPreviewImage() {
+ return '/mail/static/src/img/smiley/mailfailure.jpg';
+ },
});
return MailFailure;
diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js
index 8076c78910f..b293f6f1db0 100644
--- a/addons/mail/static/src/js/models/messages/abstract_message.js
+++ b/addons/mail/static/src/js/models/messages/abstract_message.js
@@ -164,6 +164,34 @@ var AbstractMessage = Class.extend({
return _.difference(this.getAttachments(), this.getImageAttachments());
},
/**
+ * Gets the class to use as the notification icon.
+ *
+ * @returns {string}
+ */
+ getNotificationIcon() {
+ if (!this.hasNotificationsError()) {
+ return 'fa fa-envelope-o';
+ }
+ return 'fa fa-envelope';
+ },
+ /**
+ * Gets the list of notifications of this message, in no specific order.
+ * By default messages do not have notifications.
+ *
+ * @returns {Object[]}
+ */
+ getNotifications() {
+ return [];
+ },
+ /**
+ * Gets the text to display next to the notification icon.
+ *
+ * @returns {string}
+ */
+ getNotificationText() {
+ return '';
+ },
+ /**
* Get the time elapsed between sent message and now
*
* @return {string}
@@ -199,15 +227,6 @@ var AbstractMessage = Class.extend({
return !!(this._serverAuthorID && this._serverAuthorID[0]);
},
/**
- * State whether this message contains some customer email data
- * By default, messages do not have any customer email data
- *
- * @return {boolean}
- */
- hasCustomerEmailData: function () {
- return false;
- },
- /**
* State whether this message has an email of its sender.
* By default, messages do not have any email of its sender.
*
@@ -237,6 +256,25 @@ var AbstractMessage = Class.extend({
});
},
/**
+ * States whether this message has some notifications.
+ *
+ * @returns {boolean}
+ */
+ hasNotifications() {
+ return this.getNotifications().length > 0;
+ },
+ /**
+ * States whether this message has notifications that are in error.
+ *
+ * @returns {boolean}
+ */
+ hasNotificationsError() {
+ return this.getNotifications().some(notif =>
+ notif.notification_status === 'exception' ||
+ notif.notification_status === 'bounce'
+ );
+ },
+ /**
* State whether this message originates from a channel.
* By default, messages do not originate from a channel.
*
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index dce25573dbe..1e3ae8c5289 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -24,15 +24,14 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {Object} data
* @param {string} [data.body = ""]
* @param {(string|integer)[]} [data.channel_ids]
- * @param {Object[]} [data.customer_email_data]
- * @param {string} [data.customer_email_status]
* @param {string} [data.email_from]
+ * @param {Array} [data.history_partner_ids = []]
* @param {string} [data.info]
* @param {string} [data.model]
* @param {string} [data.moderation_status='accepted']
* @param {string} [data.module_icon]
* @param {Array} [data.needaction_partner_ids = []]
- * @param {Array} [data.history_partner_ids = []]
+ * @param {Object[]} [data.notifications]
* @param {string} [data.record_name]
* @param {integer} [data.res_id]
* @param {Array} [data.starred_partner_ids = []]
@@ -61,12 +60,6 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
//--------------------------------------------------------------------------
/**
- * @param {Object} data
- */
- addCustomerEmailData: function (data) {
- this._customerEmailData.push(data);
- },
- /**
* @override
* @return {string|undefined}
*/
@@ -102,30 +95,6 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
return '/mail/static/src/img/smiley/avatar.jpg';
},
/**
- * Get the customer email data of this email, if any.
- * If this message has no such data, returns 'undefined'
- *
- * @return {Object[]|undefined}
- */
- getCustomerEmailData: function () {
- if (!this.hasCustomerEmailData()) {
- return undefined;
- }
- return this._customerEmailData;
- },
- /**
- * Get the customer email status of this email, if any.
- * If this message has no such data, returns 'undefined'
- *
- * @return {string|undefined}
- */
- getCustomerEmailStatus: function () {
- if (!this.hasCustomerEmailData()) {
- return undefined;
- }
- return this._customerEmailStatus;
- },
- /**
* Get the text to display for the author of the message
*
* Rule of precedence for the displayed author::
@@ -203,6 +172,12 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
return this._emailFrom;
},
/**
+ * @override
+ */
+ getNotifications() {
+ return Object.values(this._notifications);
+ },
+ /**
* Get the ID of the channel that this message originates from.
* If this message does not originate from a channel, returns `-1`.
*
@@ -314,15 +289,6 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
return session.url('/mail/view?message_id=' + this._id);
},
/**
- * State whether this message contains some customer email data
- *
- * @override
- * @return {boolean}
- */
- hasCustomerEmailData: function () {
- return !!(this._customerEmailData && (this._customerEmailData.length > 0));
- },
- /**
* State whether this message has an email of its sender.
*
* @override
@@ -467,6 +433,14 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
this._warnMessageModerated();
},
/**
+ * Sets notifications based on data from the server.
+ *
+ * @param {Object} notifications
+ */
+ setNotifications(notifications) {
+ this._notifications = notifications;
+ },
+ /**
* Set whether the message is starred or not.
* If it is starred, the message is moved to the "Starred" mailbox.
* Note that this function only applies it locally, the server is not aware
@@ -522,14 +496,6 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
args: [[this._id]],
});
},
- /**
- * Update the customer email status
- *
- * @param {string} newCustomerEmailStatus
- */
- updateCustomerEmailStatus: function (newCustomerEmailStatus) {
- this._customerEmailStatus = newCustomerEmailStatus;
- },
//--------------------------------------------------------------------------
// Private
@@ -735,8 +701,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {Object} data
* @param {string} [data.body = ""]
* @param {(string|integer)[]} [data.channel_ids]
- * @param {Object[]} [data.customer_email_data]
- * @param {string} [data.customer_email_status]
+ * @param {Object[]} [data.notifications]
* @param {string} [data.email_from]
* @param {string} [data.info]
* @param {string} [data.model]
@@ -751,9 +716,8 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {string} [data.subtype_description]
* @param {Object[]} [data.tracking_value_ids]
*/
- _setInitialData: function (data){
- this._customerEmailData = data.customer_email_data || [];
- this._customerEmailStatus = data.customer_email_status;
+ _setInitialData(data) {
+ this._notifications = data.notifications || {};
this._documentModel = data.model;
this._documentName = data.record_name;
this._documentID = data.res_id;
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 6a2fe69581d..c56cca019e0 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -728,7 +728,7 @@ var MailManager = AbstractService.extend({
if (
item.failure.isLinkedToDocument() &&
(item.failure.getDocumentModel() === failure.getDocumentModel()) &&
- (item.failure.getFailureType() === failure.getFailureType())
+ (item.failure.getMessageType() === failure.getMessageType())
) {
isSameDocument = item.failure.getDocumentID() === failure.getDocumentID();
return true;
diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js
index ba9230eb32b..ef91155c9db 100644
--- a/addons/mail/static/src/js/services/mail_notification_manager.js
+++ b/addons/mail/static/src/js/services/mail_notification_manager.js
@@ -299,7 +299,7 @@ MailManager.include({
* @param {string} datas.elements[].notifications[0] sending state of a mail
* failure (e.g. 'exception').
*/
- _handlePartnerMailFailureNotification: function (datas) {
+ _handlePartnerMessageNotificationUpdateNotification(datas) {
var self = this;
_.each(datas.elements, function (data) {
var isNewFailure = _.some(data.notifications, function (notif) {
@@ -322,12 +322,7 @@ MailManager.include({
return msg.getID() === data.message_id;
});
if (message) {
- if (isNewFailure) {
- message.updateCustomerEmailStatus('exception');
- } else {
- message.updateCustomerEmailStatus('sent');
- }
- self._updateMessageNotificationStatus(data, message);
+ message.setNotifications(data.notifications);
self._mailBus.trigger('update_message', message);
}
});
@@ -438,8 +433,8 @@ MailManager.include({
this._handlePartnerTransientMessageNotification(data);
} else if (data.type === 'activity_updated') {
this._handlePartnerActivityUpdateNotification(data);
- } else if (data.type === 'mail_failure') {
- this._handlePartnerMailFailureNotification(data);
+ } else if (data.type === 'message_notification_update') {
+ this._handlePartnerMessageNotificationUpdateNotification(data);
} else if (data.type === 'user_connection') {
this._handlePartnerUserConnectionNotification(data);
} else if (data.info === 'channel_seen') {
@@ -562,28 +557,6 @@ MailManager.include({
this._super.apply(this, arguments);
this.call('bus_service', 'onNotification', this, this._onNotification);
},
- /**
- * Update the message notification status of message based on update_message
- *
- * @private
- * @param {Object} data
- * @param {Object[]} data.notifications
- * @param {mail.model.Message} message
- */
- _updateMessageNotificationStatus: function (data, message) {
- _.each(data.notifications, function (notif, id) {
- var partnerName = notif[1];
- var notifStatus = notif[0];
- var res = _.find(message.getCustomerEmailData(), function (entry) {
- return entry[0] === parseInt(id);
- });
- if (res) {
- res[2] = notifStatus;
- } else {
- message.addCustomerEmailData([parseInt(id), partnerName, notifStatus]);
- }
- });
- },
//--------------------------------------------------------------------------
// Handlers
diff --git a/addons/mail/static/src/js/thread_widget.js b/addons/mail/static/src/js/thread_widget.js
index 0a58b13d5ad..583d14e949a 100644
--- a/addons/mail/static/src/js/thread_widget.js
+++ b/addons/mail/static/src/js/thread_widget.js
@@ -42,8 +42,7 @@ var ThreadWidget = Widget.extend({
'click .oe_mail_expand': '_onClickMailExpand',
'click .o_thread_message': '_onClickMessage',
'click': '_onClick',
- 'click .o_thread_message_email_exception': '_onClickEmailException',
- 'click .o_thread_message_email_bounce': '_onClickEmailException',
+ 'click .o_thread_message_notification_error': '_onClickMessageNotificationError',
'click .o_thread_message_moderation': '_onClickMessageModeration',
'change .moderation_checkbox': '_onChangeModerationCheckbox',
},
@@ -66,7 +65,7 @@ var ThreadWidget = Widget.extend({
displayDocumentLinks: true,
displayAvatars: true,
squashCloseMessages: true,
- displayEmailIcons: true,
+ displayNotificationIcons: true,
displayReplyIcons: false,
loadMoreOnScroll: false,
hasMessageAttachmentDeletable: false,
@@ -80,7 +79,7 @@ var ThreadWidget = Widget.extend({
displayDocumentLinks: false,
displayAvatars: this._enabledOptions.displayAvatars,
squashCloseMessages: false,
- displayEmailIcons: false,
+ displayNotificationIcons: false,
displayReplyIcons: false,
loadMoreOnScroll: this._enabledOptions.loadMoreOnScroll,
hasMessageAttachmentDeletable: false,
@@ -215,7 +214,7 @@ var ThreadWidget = Widget.extend({
}, 1000*60);
}
- this._renderMessageMailPopover(messages);
+ this._renderMessageNotificationPopover(messages);
if (thread.hasSeenFeature()) {
this._renderMessageSeenPopover(thread, messages);
}
@@ -454,15 +453,16 @@ var ThreadWidget = Widget.extend({
}
}, 500, true),
/**
- * Render the popover when mouse-hovering on the mail icon of a message
- * in the thread. There is at most one such popover at any given time.
+ * Render the popover when mouse-hovering on the notification icon of a
+ * message in the thread.
+ * There is at most one such popover at any given time.
*
* @private
* @param {mail.model.AbstractMessage[]} messages list of messages in the
* rendered thread, for which popover on mouseover interaction is
* permitted.
*/
- _renderMessageMailPopover: function (messages) {
+ _renderMessageNotificationPopover(messages) {
if (this._messageMailPopover) {
this._messageMailPopover.popover('hide');
}
@@ -481,7 +481,7 @@ var ThreadWidget = Widget.extend({
return message.getID() === messageID;
});
return QWeb.render('mail.widget.Thread.Message.MailTooltip', {
- data: message.hasCustomerEmailData() ? message.getCustomerEmailData() : [],
+ notifications: message.getNotifications(),
});
},
});
@@ -583,18 +583,6 @@ var ThreadWidget = Widget.extend({
* @private
* @param {MouseEvent} ev
*/
- _onClickEmailException: function (ev) {
- var messageID = $(ev.currentTarget).data('message-id');
- this.do_action('mail.mail_resend_message_action', {
- additional_context: {
- mail_message_to_resend: messageID
- }
- });
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
_onClickMailExpand: function (ev) {
ev.preventDefault();
},
@@ -617,6 +605,18 @@ var ThreadWidget = Widget.extend({
* @private
* @param {MouseEvent} ev
*/
+ _onClickMessageNotificationError(ev) {
+ const messageID = $(ev.currentTarget).data('message-id');
+ this.do_action('mail.mail_resend_message_action', {
+ additional_context: {
+ mail_message_to_resend: messageID,
+ }
+ });
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
_onClickMessageReply: function (ev) {
this._selectedMessageID = $(ev.currentTarget).data('message-id');
this.$('.o_thread_message').removeClass('o_thread_selected_message');
diff --git a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
index 7ec1c214ef1..7e39f9c7e2e 100644
--- a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
+++ b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
@@ -59,7 +59,7 @@ var AbstractThreadWindow = Widget.extend({
autofocus: true,
displayStars: true,
displayReplyIcons: false,
- displayEmailIcons: false,
+ displayNotificationIcons: false,
placeholder: _t("Say something"),
});
diff --git a/addons/mail/static/src/scss/thread.scss b/addons/mail/static/src/scss/thread.scss
index c64b3f6aefd..2fc55c7d637 100644
--- a/addons/mail/static/src/scss/thread.scss
+++ b/addons/mail/static/src/scss/thread.scss
@@ -210,15 +210,13 @@
}
}
- .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_email {
+ .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_notification {
padding: 4px;
}
- i.o_thread_message_email {
- &.o_thread_message_email_ready {
- color: grey;
- }
- &.o_thread_message_email_exception, &.o_thread_message_email_bounce {
+ .o_thread_message_notification {
+ color: grey;
+ &.o_thread_message_notification_error {
color: red;
opacity: 1;
cursor: pointer;
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index 0774be1ccaa..bf1c5427911 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -256,7 +256,7 @@
@param {Object} options
@param {boolean} [options.displayAvatars]
@param {boolean} [options.displayDocumentLinks]
- @param {boolean} [options.displayEmailIcons]
+ @param {boolean} [options.displayNotificationIcons]
@param {boolean} [options.displayMarkAsRead]
@param {boolean} [options.displayModerationCommands] when set, display the moderation commands on
the message. This includes the moderation checkboxes (needs a control panel such as in Discuss app).
@@ -327,13 +327,11 @@
<t t-if="message.originatesFromChannel() and (message.getOriginChannelID() !== thread.getID())">
(<small>from</small> <a t-att-data-oe-id="message.getOriginChannelID()" href="#">#<t t-esc="message.getOriginChannelName()"/></a>)
</t>
- <span t-if="options.displayEmailIcons and (message.hasCustomerEmailData() or ['exception', 'bounce'].indexOf(message._customerEmailStatus) > -1)" class="o_thread_tooltip_container">
-
- <t t-set="fatype" t-value="''"/>
- <t t-if="message.getCustomerEmailStatus() === 'sent' or message.getCustomerEmailStatus() === 'ready'">
- <t t-set="fatype" t-value="'-o'"/>
- </t>
- <i t-att-class="'o_thread_tooltip o_thread_message_email o_thread_message_email_' + message.getCustomerEmailStatus() + ' fa fa-envelope' + fatype" t-att-data-message-id="message.getID()"/>
+ <span t-if="options.displayNotificationIcons and message.hasNotifications()" class="o_thread_tooltip_container">
+ <span name="notification_icon" t-attf-class="d-inline-flex align-items-center o_thread_tooltip o_thread_message_notification {{ message.hasNotificationsError() ? 'o_thread_message_notification_error' : '' }}" t-att-data-message-id="message.getID()" t-att-data-message-type="message.getType()">
+ <i t-att-class="message.getNotificationIcon()"/>
+ <small t-if="message.getNotificationText()" t-esc="message.getNotificationText()" class="font-weight-bold ml-1"/>
+ </span>
</span>
<span t-attf-class="o_thread_icons">
<t t-if="thread.hasSeenFeature()" t-call="mail.widget.Thread.Message.SeenIcon"/>
@@ -450,23 +448,22 @@
</t>
<!--
- @param {Object[]|undefined} list of customer email data of a given message.
- A customer email data is a list with the following format:
- [partnerID, partnerName, emailStatus]
+ @param {Object[]} notifications: list of notifications
+ A notification is an object with at least the following keys:
+ {notification_status, partner_name}
-->
<t t-name="mail.widget.Thread.Message.MailTooltip">
- <div t-foreach="data" t-as="customerEmailData">
- <span class="d-inline-block text-center o_thread_tooltip_icon">
- <i t-if="customerEmailData[2] === 'sent'" class='fa fa-check' title="Sent" role="img" aria-label="Sent"/>
- <i t-if="customerEmailData[2] === 'bounce'" class='fa fa-exclamation text-danger' title="Bounced" role="img" aria-label="Bounced"/>
- <i t-if="customerEmailData[2] === 'exception'" class='fa fa-exclamation text-danger' title="Error" role="img" aria-label="Error"/>
- <i t-if="customerEmailData[2] === 'ready'" class='fa fa-send-o' title="Ready" role="img" aria-label="Ready"/>
- <i t-if="customerEmailData[2] === 'canceled'" class='fa fa-trash-o' title="Canceled" role="img" aria-label="Canceled"/>
+ <div t-foreach="notifications" t-as="notification">
+ <span name="notification_status" class="d-inline-block text-center o_thread_tooltip_icon">
+ <i t-if="notification.notification_status === 'sent'" class='fa fa-check' title="Sent" role="img" aria-label="Sent"/>
+ <i t-if="notification.notification_status === 'bounce'" class='fa fa-exclamation text-danger' title="Bounced" role="img" aria-label="Bounced"/>
+ <i t-if="notification.notification_status === 'exception'" class='fa fa-exclamation text-danger' title="Error" role="img" aria-label="Error"/>
+ <i t-if="notification.notification_status === 'ready'" class='fa fa-send-o' title="Ready" role="img" aria-label="Ready"/>
+ <i t-if="notification.notification_status === 'canceled'" class='fa fa-trash-o' title="Canceled" role="img" aria-label="Canceled"/>
</span>
- <span t-esc="customerEmailData[1]"/>
+ <span name="partner_name" t-esc="notification.partner_name"/>
</div>
</t>
-
<!--
@param {mail.model.Message} message
@param {Object} options
diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py
index 5d445ffe374..cc5f9b0d67b 100644
--- a/addons/mail/tests/common.py
+++ b/addons/mail/tests/common.py
@@ -426,7 +426,6 @@ class MailCase(MockEmail):
# check notifications and prepare assert data
email_groups = defaultdict(list)
- bus_groups = {'failure': []}
mail_groups = {'failure': []}
for recipient in message_info['notif']:
partner, ntype, ngroup, nstatus = recipient['partner'], recipient['type'], recipient.get('group'), recipient.get('status', 'sent')
@@ -449,8 +448,6 @@ class MailCase(MockEmail):
self.assertTrue(partner_notif, 'Mail: not found notification for %s (type: %s, state: %s, message: %s)' % (partner, ntype, nstatus, message.id))
# prepare further asserts
- if partner and nstatus == 'exception':
- bus_groups['failure'].append(partner)
if ntype == 'email':
if nstatus == 'sent':
if ncheck_send:
@@ -468,16 +465,9 @@ class MailCase(MockEmail):
done_msgs |= message
# check bus notifications that should be sent (hint: message author, multiple notifications)
- if bus_groups['failure']:
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', message.author_id.id)],
- [{'type': 'mail_failure', 'elements': [{
- 'message_id': message.id,
- 'failure_type': 'mail',
- 'notifications': dict(('%s' % p.id, ['exception', p.name]) for p in bus_groups['failure'])}]
- }],
- check_unique=False
- )
+ bus_notifications = message.notification_ids._filtered_for_web_client().filtered(lambda n: n.notification_status == 'exception')
+ if bus_notifications:
+ self.assertMessageBusNotifications(message)
# check emails that should be sent (hint: mail.mail per group, email par recipient)
for recipients in email_groups.values():
@@ -494,6 +484,18 @@ class MailCase(MockEmail):
return done_msgs, done_notifs
+ def assertMessageBusNotifications(self, message):
+ """Asserts that the expected notification updates have been sent on the
+ bus for the given message."""
+ self.assertBusNotifications(
+ [(self.cr.dbname, 'res.partner', message.author_id.id)],
+ [{
+ 'type': 'message_notification_update',
+ 'elements': message._message_notification_format(),
+ }],
+ check_unique=False
+ )
+
def assertBusNotifications(self, channels, message_items=None, check_unique=True):
""" Check bus notifications content. Mandatory and basic check is about
channels being notified. Content check is optional.
@@ -505,54 +507,29 @@ class MailCase(MockEmail):
]
:param message_items: if given, list of expected message making a valid
pair (channel, message) to be found in bus.bus, like [
- {'type': 'sms_update',
- 'elements': [{
+ {'type': 'message_notification_update',
+ 'elements': {self.msg.id: {
'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['sent', self.partner_2.name]}
- }]
+ 'message_type': 'sms',
+ 'notifications': {...},
+ ...
+ }}
}, {...}]
"""
- def check_content(returned_value, expected_value):
- if isinstance(expected_value, list):
- done = []
- for expected_item in expected_value:
- for returned_item in returned_value:
- if check_content(returned_item, expected_item):
- done.append(expected_item)
- break
- else:
- return False
- return len(done) == len(expected_value)
- elif isinstance(expected_value, dict):
- return all(k in returned_value for k in expected_value.keys()) and all(
- check_content(returned_value[key], val)
- for key, val in expected_value.items()
- )
- else:
- return returned_value == expected_value
-
bus_notifs = self.env['bus.bus'].sudo().search([('channel', 'in', [json_dump(channel) for channel in channels])])
if check_unique:
self.assertEqual(len(bus_notifs), len(channels))
self.assertEqual(set(bus_notifs.mapped('channel')), set([json_dump(channel) for channel in channels]))
- notif_messages = [json.loads(n.message) for n in bus_notifs]
+ notif_messages = [n.message for n in bus_notifs]
+
for expected in message_items or []:
for notification in notif_messages:
- found_keys, not_found_keys = [], []
- if not all(k in notification for k in expected.keys()):
- continue
- for expected_key, expected_value in expected.items():
- done = check_content(notification[expected_key], expected_value)
- if done:
- found_keys.append(expected_key)
- else:
- not_found_keys.append(expected_key)
- if set(found_keys) == set(expected.keys()):
+ if json_dump(expected) == notification:
break
else:
- raise AssertionError('Keys %s not found (expected: %s - returned: %s)' % (not_found_keys, repr(expected), repr(notif_messages)))
+ raise AssertionError('No notification was found with the expected value.\nExpected:\n%s\nReturned:\n%s' %
+ (json_dump(expected), '\n'.join([n for n in notif_messages])))
return bus_notifs
diff --git a/addons/mail/wizard/mail_resend_cancel.py b/addons/mail/wizard/mail_resend_cancel.py
index 7f2c5d334fb..328579c25d0 100644
--- a/addons/mail/wizard/mail_resend_cancel.py
+++ b/addons/mail/wizard/mail_resend_cancel.py
@@ -33,5 +33,5 @@ class MailResendCancel(models.TransientModel):
messages_ids = list(set([row[1] for row in res]))
if notif_ids:
self.env["mail.notification"].browse(notif_ids).sudo().write({'notification_status': 'canceled'})
- self.env["mail.message"].browse(messages_ids)._notify_mail_failure_update()
+ self.env["mail.message"].browse(messages_ids)._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/mail/wizard/mail_resend_message.py b/addons/mail/wizard/mail_resend_message.py
index 15cbc0d4cf3..c2b8f508dec 100644
--- a/addons/mail/wizard/mail_resend_message.py
+++ b/addons/mail/wizard/mail_resend_message.py
@@ -75,14 +75,14 @@ class MailResendMessage(models.TransientModel):
record._notify_record_by_email(message, {'partners': email_partners_data}, check_existing=True, send_after_commit=False)
- self.mail_message_id._notify_mail_failure_update()
+ self.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
def cancel_mail_action(self):
for wizard in self:
for notif in wizard.notification_ids:
notif.filtered(lambda notif: notif.notification_type == 'email' and notif.notification_status in ('exception', 'bounce')).sudo().write({'notification_status': 'canceled'})
- wizard.mail_message_id._notify_mail_failure_update()
+ wizard.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/sms/models/mail_message.py b/addons/sms/models/mail_message.py
index dbc0a78cd75..cd827d67e6b 100644
--- a/addons/sms/models/mail_message.py
+++ b/addons/sms/models/mail_message.py
@@ -4,7 +4,7 @@
from collections import defaultdict
from operator import itemgetter
-from odoo import api, exceptions, fields, models
+from odoo import exceptions, fields, models
from odoo.tools import groupby
@@ -34,58 +34,6 @@ class MailMessage(models.Model):
return ['&', ('notification_ids.notification_status', '=', 'exception'), ('notification_ids.notification_type', '=', 'sms')]
raise NotImplementedError()
- def _format_mail_failures(self):
- """ A shorter message to notify a SMS delivery failure update
-
- TDE FIXME: should be cleaned
- """
- res = super(MailMessage, self)._format_mail_failures()
-
- # prepare notifications computation in batch
- all_notifications = self.env['mail.notification'].sudo().search([
- ('mail_message_id', 'in', self.ids)
- ])
- msgid_to_notif = defaultdict(lambda: self.env['mail.notification'].sudo())
- for notif in all_notifications:
- msgid_to_notif[notif.mail_message_id.id] += notif
-
- for message in self:
- notifications = msgid_to_notif[message.id]
- if not any(notification.notification_type == 'sms' for notification in notifications):
- continue
- info = dict(message._get_mail_failure_dict(),
- failure_type='sms',
- notifications=dict((notif.res_partner_id.id, (notif.notification_status, notif.res_partner_id.name)) for notif in notifications if notif.notification_type == 'sms'),
- module_icon='/sms/static/img/sms_failure.svg'
- )
- res.append(info)
- return res
-
- def _notify_sms_update(self):
- """ Send bus notifications to update status of notifications in chatter.
- Purpose is to send the updated status per author.
-
- TDE FIXME: author_id strategy seems curious, check with JS """
- messages = self.env['mail.message']
- for message in self:
- # YTI FIXME: check allowed_company_ids if necessary
- if message.model and message.res_id:
- record = self.env[message.model].browse(message.res_id)
- try:
- record.check_access_rights('read')
- record.check_access_rule('read')
- except exceptions.AccessError:
- continue
- else:
- messages |= message
-
- """ Notify channels after update of SMS status """
- updates = [[
- (self._cr.dbname, 'res.partner', author.id),
- {'type': 'sms_update', 'elements': self.env['mail.message'].concat(*author_messages)._format_mail_failures()}
- ] for author, author_messages in groupby(messages, itemgetter('author_id'))]
- self.env['bus.bus'].sendmany(updates)
-
def message_format(self):
""" Override in order to retrieves data about SMS (recipient name and
SMS status)
diff --git a/addons/sms/models/sms_sms.py b/addons/sms/models/sms_sms.py
index 081547426ce..f5053397a03 100644
--- a/addons/sms/models/sms_sms.py
+++ b/addons/sms/models/sms_sms.py
@@ -135,6 +135,7 @@ class SmsSms(models.Model):
'failure_type': self.IAP_TO_SMS_STATE[state] if state != 'success' else False,
'failure_reason': failure_reason if failure_reason else False,
})
+ self.mail_message_id._notify_message_notification_update()
if todelete_sms_ids:
self.browse(todelete_sms_ids).sudo().unlink()
diff --git a/addons/sms/static/src/js/mail_failure.js b/addons/sms/static/src/js/mail_failure.js
index 653086895f8..4df13760f8c 100644
--- a/addons/sms/static/src/js/mail_failure.js
+++ b/addons/sms/static/src/js/mail_failure.js
@@ -16,14 +16,36 @@ MailFailure.include({
*/
getPreview: function () {
var preview = this._super.apply(this, arguments);
- if (this._failureType === 'sms') {
+ if (this.getMessageType() === 'sms') {
_.extend(preview, {
- body: _t('An error occurred when sending an SMS'),
id: 'sms_failure',
});
}
return preview;
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _getPreviewBody() {
+ if (this.getMessageType() === 'sms') {
+ return _t("An error occurred when sending an SMS.");
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _getPreviewImage() {
+ if (this.getMessageType() === 'sms') {
+ return '/sms/static/img/sms_failure.svg';
+ }
+ return this._super(...arguments);
+ },
});
});
diff --git a/addons/sms/static/src/js/message.js b/addons/sms/static/src/js/message.js
index 27ab85b0d21..76cc49f266f 100644
--- a/addons/sms/static/src/js/message.js
+++ b/addons/sms/static/src/js/message.js
@@ -1,83 +1,32 @@
odoo.define('sms.model.Message', function (require) {
"use strict";
+const { _t } = require('web.core');
+
var Message = require('mail.model.Message');
Message.include({
-
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
- * Retrieves the list of SMS
- *
- * @returns {Array} Array of Array(smsID, recipientName, smsStatus)
- */
- getSmsIds: function () {
- return this._smsIds;
- },
- /**
- * Retrieves the SMS status
- *
- * @return {string}
- */
- getSmsStatus: function () {
- var self = this;
- this._smsStatus = 'sent';
- _.each(this._smsIds, function (sms) {
- if (sms[2] === 'bounce' || sms[2] === 'exception') {
- self._smsStatus = 'error';
- }
- });
- return this._smsStatus;
- },
- /**
- * Whether message has nay SMS-related notification
- *
- * @returns {boolean}
- */
- hasSmsData: function () {
- return !!(this._smsIds && (this._smsIds.length > 0));
- },
- /**
- * Does the message contains at least one SMS failure
- *
- * @returns {boolean}
- */
- isError: function () {
- return this.getSmsStatus() === 'error';
- },
- /**
- * Update the status of a SMS
- *
- * @param {integer} smsID ID of the SMS to update
- * @param {string} smsStatus New status of the SMS
+ * @override
*/
- setSmsStatus: function (smsID, smsStatus) {
- var self = this;
- var sms = _.find(self._smsIds, function (sms) {
- return sms[0] === smsID;
- });
- if (sms !== undefined && smsStatus !== undefined) {
- sms[2] = smsStatus;
+ getNotificationIcon() {
+ if (this.getType() === 'sms') {
+ return 'fa fa-mobile';
}
+ return this._super(...arguments);
},
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
/**
* @override
- * @private
*/
- _setInitialData: function (data) {
- this._super.apply(this, arguments);
- this._smsIds = data.sms_ids;
- if (this._smsStatus === false) {
- this._smsStatus = 'sent';
+ getNotificationText() {
+ if (this.getType() === 'sms') {
+ return _t("SMS");
}
+ return this._super(...arguments);
},
});
});
diff --git a/addons/sms/static/src/js/sms_notification_manager.js b/addons/sms/static/src/js/sms_notification_manager.js
deleted file mode 100644
index 7d93d96fe51..00000000000
--- a/addons/sms/static/src/js/sms_notification_manager.js
+++ /dev/null
@@ -1,64 +0,0 @@
-odoo.define('sms.NotificationManager', function (require) {
-"use strict";
-
-var MailManager = require('mail.Manager');
-var MailFailure = require('mail.model.MailFailure');
-
-MailManager.include({
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @override
- * @param {Object} data structure depending on the type
- * @param {integer} data.id
- */
- _handlePartnerNotification: function (data) {
- if (data.type === 'sms_update') {
- this._handleSMSUpdateNotification(data);
- } else {
- this._super.apply(this, arguments);
- }
- },
- /**
- * Updates message in thread when there's an update in a SMS letter
- *
- * @private
- * @param {Object} datas
- * @param {Object[]} datas.elements list of SMS failure data
- * @param {string} datas.elements[].message_id ID of related message that
- * has a sms failure.
- */
- _handleSMSUpdateNotification: function (datas) {
- var self = this;
- _.each(datas.elements, function (data) {
- var isNewFailure = data.sms_status === 'error';
- var matchedFailure = _.find(self._mailFailures, function (failure) {
- return failure.getMessageID() === data.message_id;
- });
-
- if (matchedFailure) {
- var index = _.findIndex(self._mailFailures, matchedFailure);
- if (isNewFailure) {
- self._mailFailures[index] = new MailFailure(self, data);
- } else {
- self._mailFailures.splice(index, 1);
- }
- } else if (isNewFailure) {
- self._mailFailures.push(new MailFailure(self, data));
- }
- var message = _.find(self._messages, function (msg) {
- return msg.getID() === data.message_id;
- });
- if (message) {
- message.setSmsStatus(data.sms_id, data.sms_status);
- self._mailBus.trigger('update_message', message);
- }
- });
- this._mailBus.trigger('update_needaction', this.needactionCounter);
- },
-});
-
-});
diff --git a/addons/sms/static/src/js/systray_messaging_menu.js b/addons/sms/static/src/js/systray_messaging_menu.js
index 80987af37e6..4e8a354656d 100644
--- a/addons/sms/static/src/js/systray_messaging_menu.js
+++ b/addons/sms/static/src/js/systray_messaging_menu.js
@@ -13,7 +13,7 @@ MessagingMenu.include({
//--------------------------------------------------------------------------
/**
- * Called when clicking on a preview related to a snailmail failure
+ * Called when clicking on a preview related to an SMS failure.
*
* @private
* @param {$.Element} $target DOM of preview element clicked
diff --git a/addons/sms/static/src/js/thread_widget.js b/addons/sms/static/src/js/thread_widget.js
index 4655732b34d..b64d6b064f0 100644
--- a/addons/sms/static/src/js/thread_widget.js
+++ b/addons/sms/static/src/js/thread_widget.js
@@ -2,88 +2,26 @@ odoo.define('sms.widget.Thread', function (require) {
"use strict";
var ThreadWidget = require('mail.widget.Thread');
-var core = require('web.core');
-
-var QWeb = core.qweb;
ThreadWidget.include({
- events: _.extend({}, ThreadWidget.prototype.events, {
- 'click .o_thread_message_sms_error': '_onClickSMSError'
- }),
- /**
- * @override
- */
- init: function () {
- this._super.apply(this, arguments);
- this._enabledOptions = _.defaults(this._enabledOptions, {
- displaySmsIcons: true,
- });
- this._disabledOptions = _.defaults(this._disabledOptions, {
- displaySmsIcons: false,
- });
- },
- /**
- * @override
- */
- render: function (thread, options) {
- this._super.apply(this, arguments);
- var messages = _.clone(thread.getMessages({domain: options.domain || []}));
- this._renderMessageSmsPopover(messages);
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Render the popover when mouse-hovering on the mail icon of a message
- * in the thread. There is at most one such popover at any given time.
- *
- * @private
- * @param {mail.model.AbstractMessage[]} messages list of messages in the
- * rendered thread, for which popover on mouseover interaction is
- * permitted.
- */
- _renderMessageSmsPopover: function (messages) {
- if (this._messageSmsPopover) {
- this._messageSmsPopover.popover('hide');
- }
- if (!this.$('.o_thread_sms_tooltip').length) {
- return;
- }
- this._messageSmsPopover = this.$('.o_thread_sms_tooltip').popover({
- html: true,
- boundary: 'viewport',
- placement: 'auto',
- trigger: 'hover',
- offset: '0, 1',
- content: function () {
- var messageID = $(this).data('message-id');
- var message = _.find(messages, function (message) {
- return message.getID() === messageID;
- });
- return QWeb.render('sms.widget.Thread.Message.SmsTooltip', {
- data: message.getSmsIds()
- });
- },
- });
- },
-
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
- * @private
- * @param {MouseEvent} ev
+ * @override
*/
- _onClickSMSError: function (ev) {
- var messageID = $(ev.currentTarget).data('message-id');
- this.do_action('sms.sms_resend_action', {
- additional_context: {
- default_mail_message_id: messageID
- }
- });
+ _onClickMessageNotificationError(ev) {
+ if ($(ev.currentTarget).data('message-type') === 'sms') {
+ var messageID = $(ev.currentTarget).data('message-id');
+ this.do_action('sms.sms_resend_action', {
+ additional_context: {
+ default_mail_message_id: messageID
+ }
+ });
+ } else {
+ this._super(...arguments);
+ }
},
});
});
diff --git a/addons/sms/static/src/scss/thread.scss b/addons/sms/static/src/scss/thread.scss
deleted file mode 100644
index 4d7d653eb02..00000000000
--- a/addons/sms/static/src/scss/thread.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.o_thread_message_sms.o_thread_message_sms_error {
- color: red;
- opacity: 1;
- cursor: pointer;
-}
diff --git a/addons/sms/static/src/xml/thread.xml b/addons/sms/static/src/xml/thread.xml
index 6fb9530a8f4..56133864767 100644
--- a/addons/sms/static/src/xml/thread.xml
+++ b/addons/sms/static/src/xml/thread.xml
@@ -1,29 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<template id="template" xml:space="preserve">
-
- <t t-extend="mail.widget.Thread.Message">
- <t t-jquery=".o_thread_tooltip_container" t-operation="after">
- <span t-if="message.getType() === 'sms' and message.hasSmsData() and options.displaySmsIcons" class="o_thread_sms_tooltip_container ml-1">
- <t t-set="thread_icon_class" t-value="'o_thread_sms_tooltip o_thread_message_sms o_thread_message_sms_' + message.getSmsStatus()" />
- <span t-attf-class="d-inline-flex align-items-center o_thread_sms_tooltip o_thread_message_sms o_thread_message_sms_#{message.getSmsStatus()} #{message.isError() ? 'o_thread_message_sms_error' : ''}"
- t-att-data-message-id="message.getID()">
- <i class="fa fa-mobile"/>
- <small class="font-weight-bold ml-1">SMS</small>
- </span>
- </span>
- </t>
+ <t t-inherit="mail.widget.Thread.Message.MailTooltip" t-inherit-mode="extension">
+ <xpath expr="//span[@name='notification_status']" position="inside">
+ <i t-if="notification.notification_status === 'outgoing'" class='fa fa-clock-o' title="Awaiting Dispatch" role="img" aria-label="Awaiting Dispatch"/>
+ </xpath>
</t>
-
- <t t-name="sms.widget.Thread.Message.SmsTooltip">
- <div t-foreach="data" t-as="customerSMSData">
- <span class="d-inline-block text-center o_thread_tooltip_icon">
- <i t-if="customerSMSData[2] === 'sent'" class='fa fa-check' title="Sent" role="img" aria-label="Sent"/>
- <i t-if="customerSMSData[2] === 'canceled'" class='fa fa-trash-o' title="Canceled" role="img" aria-label="Canceled"/>
- <i t-if="customerSMSData[2] === 'outgoing'" class='fa fa-clock-o' title="Awaiting Dispatch" role="img" aria-label="Awaiting Dispatch"/>
- <i t-if="customerSMSData[2] === 'exception'" class='fa fa-exclamation text-danger' title="Error" role="img" aria-label="Error"/>
- </span>
- <span t-esc="customerSMSData[1]"/>
- </div>
- </t>
-
- </template>
+</template>
diff --git a/addons/sms/views/assets.xml b/addons/sms/views/assets.xml
index 4ca1b004d5b..2860d575432 100644
--- a/addons/sms/views/assets.xml
+++ b/addons/sms/views/assets.xml
@@ -7,10 +7,8 @@
<script type="text/javascript" src="/sms/static/src/js/fields_sms_widget.js"></script>
<script type="text/javascript" src="/sms/static/src/js/mail_failure.js"></script>
<script type="text/javascript" src="/sms/static/src/js/message.js"></script>
- <script type="text/javascript" src="/sms/static/src/js/sms_notification_manager.js"></script>
<script type="text/javascript" src="/sms/static/src/js/systray_messaging_menu.js"></script>
<script type="text/javascript" src="/sms/static/src/js/thread_widget.js"></script>
- <link rel="stylesheet" type="text/scss" href="/sms/static/src/scss/thread.scss"/>
</xpath>
</template>
diff --git a/addons/sms/wizard/sms_cancel.py b/addons/sms/wizard/sms_cancel.py
index 945b4d8d843..5120e94cc4f 100644
--- a/addons/sms/wizard/sms_cancel.py
+++ b/addons/sms/wizard/sms_cancel.py
@@ -34,5 +34,5 @@ WHERE notif.notification_type = 'sms' IS TRUE AND notif.notification_status IN (
if notif_ids:
self.env['mail.notification'].browse(notif_ids).sudo().write({'notification_status': 'canceled'})
if message_ids:
- self.env['mail.message'].browse(message_ids)._notify_sms_update()
+ self.env['mail.message'].browse(message_ids)._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/sms/wizard/sms_resend.py b/addons/sms/wizard/sms_resend.py
index 4d747e38b63..3b40e1f2959 100644
--- a/addons/sms/wizard/sms_resend.py
+++ b/addons/sms/wizard/sms_resend.py
@@ -91,7 +91,7 @@ class SMSResend(models.TransientModel):
put_in_queue=False
)
- self.mail_message_id._notify_sms_update()
+ self.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
def action_cancel(self):
@@ -99,7 +99,7 @@ class SMSResend(models.TransientModel):
sudo_self = self.sudo()
sudo_self.mapped('recipient_ids.notification_id').write({'notification_status': 'canceled'})
- self.mail_message_id._notify_sms_update()
+ self.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
def action_buy_credits(self):
diff --git a/addons/snailmail/__manifest__.py b/addons/snailmail/__manifest__.py
index 00d597708fd..f9a0a4c121c 100644
--- a/addons/snailmail/__manifest__.py
+++ b/addons/snailmail/__manifest__.py
@@ -6,7 +6,7 @@ Allows users to send documents by post
=====================================================
""",
'category': 'Hidden/Tools',
- 'version': '0.1',
+ 'version': '0.2',
'depends': ['iap', 'mail'],
'data': [
'data/snailmail_data.xml',
diff --git a/addons/snailmail/models/__init__.py b/addons/snailmail/models/__init__.py
index a05bee36be9..37c70029e12 100644
--- a/addons/snailmail/models/__init__.py
+++ b/addons/snailmail/models/__init__.py
@@ -3,6 +3,7 @@
from . import res_company
from . import res_partner
from . import res_config_settings
+from . import mail_notification
from . import snailmail_letter
from . import ir_actions_report
from . import ir_qweb_fields
diff --git a/addons/snailmail/models/mail_message.py b/addons/snailmail/models/mail_message.py
index 94284fd99a7..3607ff8bfca 100644
--- a/addons/snailmail/models/mail_message.py
+++ b/addons/snailmail/models/mail_message.py
@@ -1,44 +1,31 @@
from odoo import api, fields, models
+
class Message(models.Model):
_inherit = 'mail.message'
snailmail_error = fields.Boolean("Snailmail message in error", compute="_compute_snailmail_error", search="_search_snailmail_error")
- snailmail_status = fields.Char("Snailmail Status", compute="_compute_snailmail_error")
letter_ids = fields.One2many(comodel_name='snailmail.letter', inverse_name='message_id')
message_type = fields.Selection(selection_add=[
('snailmail', 'Snailmail')
], ondelete={'snailmail': lambda recs: recs.write({'message_type': 'email'})})
- def _get_message_format_fields(self):
- res = super(Message, self)._get_message_format_fields()
- res.append('snailmail_error')
- res.append('snailmail_status')
- return res
-
@api.depends('letter_ids', 'letter_ids.state')
def _compute_snailmail_error(self):
for message in self:
if message.message_type == 'snailmail' and message.letter_ids:
message.snailmail_error = message.letter_ids[0].state == 'error'
- message.snailmail_status = message.letter_ids[0].error_code if message.letter_ids[0].state == 'error' else message.letter_ids[0].state
else:
message.snailmail_error = False
- message.snailmail_status = ''
def _search_snailmail_error(self, operator, operand):
if operator == '=' and operand:
return ['&', ('letter_ids.state', '=', 'error'), ('letter_ids.user_id', '=', self.env.user.id)]
- return ['!', '&', ('letter_ids.state', '=', 'error'), ('letter_ids.user_id', '=', self.env.user.id)]
+ return ['!', '&', ('letter_ids.state', '=', 'error'), ('letter_ids.user_id', '=', self.env.user.id)]
def cancel_letter(self):
self.mapped('letter_ids').cancel()
def send_letter(self):
self.mapped('letter_ids')._snailmail_print()
-
- def message_fetch_failed(self):
- res = super(Message, self).message_fetch_failed()
- failed_letters = self.letter_ids.fetch_failed_letters()
- return res + failed_letters
diff --git a/addons/snailmail/models/mail_notification.py b/addons/snailmail/models/mail_notification.py
new file mode 100644
index 00000000000..a368c0a7783
--- /dev/null
+++ b/addons/snailmail/models/mail_notification.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from odoo import fields, models
+
+
+class Notification(models.Model):
+ _inherit = 'mail.notification'
+
+ notification_type = fields.Selection(selection_add=[('snail', 'Snailmail')], ondelete={'snail': 'cascade'})
+ letter_id = fields.Many2one('snailmail.letter', string="Snailmail Letter", index=True, ondelete='cascade')
+ failure_type = fields.Selection(selection_add=[
+ ('sn_credit', "Snailmail Credit Error"),
+ ('sn_trial', "Snailmail Trial Error"),
+ ('sn_price', "Snailmail No Price Available"),
+ ('sn_fields', "Snailmail Missing Required Fields"),
+ ('sn_format', "Snailmail Format Error"),
+ ('sn_error', "Snailmail Unknown Error"),
+ ])
diff --git a/addons/snailmail/models/snailmail_letter.py b/addons/snailmail/models/snailmail_letter.py
index b74e2a42abc..702f2d366ca 100644
--- a/addons/snailmail/models/snailmail_letter.py
+++ b/addons/snailmail/models/snailmail_letter.py
@@ -2,11 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
import base64
-import datetime
-from odoo import fields, models, api, _, tools
+from odoo import fields, models, api, _
from odoo.addons.iap import jsonrpc
-from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
DEFAULT_ENDPOINT = 'https://iap-snailmail.odoo.com'
@@ -56,6 +54,7 @@ class SnailmailLetter(models.Model):
reference = fields.Char(string='Related Record', compute='_compute_reference', readonly=True, store=False)
message_id = fields.Many2one('mail.message', string="Snailmail Status Message")
+ notification_ids = fields.One2many('mail.notification', 'letter_id', "Notifications")
street = fields.Char('Street')
street2 = fields.Char('Street2')
@@ -83,6 +82,7 @@ class SnailmailLetter(models.Model):
body=_("Letter sent by post with Snailmail"),
message_type='snailmail'
)
+
partner_id = self.env['res.partner'].browse(vals['partner_id'])
vals.update({
'message_id': msg_id.id,
@@ -93,7 +93,18 @@ class SnailmailLetter(models.Model):
'state_id': partner_id.state_id.id,
'country_id': partner_id.country_id.id,
})
- return super(SnailmailLetter, self).create(vals)
+ letter = super(SnailmailLetter, self).create(vals)
+
+ self.env['mail.notification'].sudo().create({
+ 'mail_message_id': msg_id.id,
+ 'res_partner_id': partner_id.id,
+ 'notification_type': 'snail',
+ 'letter_id': letter.id,
+ 'is_read': True, # discard Inbox notification
+ 'notification_status': 'ready',
+ })
+
+ return letter
def _fetch_attachment(self):
"""
@@ -230,7 +241,7 @@ class SnailmailLetter(models.Model):
'info_msg': 'The attachment could not be generated.',
'state': 'error',
'error_code': 'ATTACHMENT_ERROR'
- })
+ })
continue
if letter.company_id.external_report_layout_id == self.env.ref('l10n_de.external_layout_din5008', False):
document.update({
@@ -269,6 +280,20 @@ class SnailmailLetter(models.Model):
return _('An unknown error happened. Please contact the support.')
return error
+ def _get_failure_type(self, error):
+ if error == 'CREDIT_ERROR':
+ return 'sn_credit'
+ if error == 'TRIAL_ERROR':
+ return 'sn_trial'
+ if error == 'NO_PRICE_AVAILABLE':
+ return 'sn_price'
+ if error == 'MISSING_REQUIRED_FIELDS':
+ return 'sn_fields'
+ if error == 'FORMAT_ERROR':
+ return 'sn_format'
+ else:
+ return 'sn_error'
+
def _snailmail_print(self, immediate=True):
valid_address_letters = self.filtered(lambda l: l._is_valid_address(l))
invalid_address_letters = self - valid_address_letters
@@ -278,13 +303,19 @@ class SnailmailLetter(models.Model):
self.env.cr.commit()
def _snailmail_print_invalid_address(self):
- for letter in self:
- letter.write({
- 'state': 'error',
- 'error_code': 'MISSING_REQUIRED_FIELDS',
- 'info_msg': _('The address of the recipient is not complete')
- })
- self.send_snailmail_update()
+ error = 'MISSING_REQUIRED_FIELDS'
+ error_message = _("The address of the recipient is not complete")
+ self.write({
+ 'state': 'error',
+ 'error_code': error,
+ 'info_msg': error_message,
+ })
+ self.notification_ids.sudo().write({
+ 'notification_status': 'exception',
+ 'failure_type': self._get_failure_type(error),
+ 'failure_reason': error_message,
+ })
+ self.message_id._notify_message_notification_update()
def _snailmail_print_valid_address(self):
"""
@@ -307,6 +338,11 @@ class SnailmailLetter(models.Model):
if doc.get('sent') and response['request_code'] == 200:
note = _('The document was correctly sent by post.<br>The tracking id is %s' % doc['send_id'])
letter_data = {'info_msg': note, 'state': 'sent', 'error_code': False}
+ notification_data = {
+ 'notification_status': 'sent',
+ 'failure_type': False,
+ 'failure_reason': False,
+ }
else:
error = doc['error'] if response['request_code'] == 200 else response['reason']
@@ -316,26 +352,26 @@ class SnailmailLetter(models.Model):
'state': 'error',
'error_code': error if error in ERROR_CODES else 'UNKNOWN_ERROR'
}
+ notification_data = {
+ 'notification_status': 'exception',
+ 'failure_type': self._get_failure_type(error),
+ 'failure_reason': note,
+ }
letter = self.browse(doc['letter_id'])
letter.write(letter_data)
- self.send_snailmail_update()
-
- def send_snailmail_update(self):
- notifications = []
- for letter in self:
- notifications.append([
- (self._cr.dbname, 'res.partner', letter.user_id.partner_id.id),
- {'type': 'snailmail_update', 'elements': letter._format_snailmail_failures()}
- ])
- self.env['bus.bus'].sendmany(notifications)
+ letter.notification_ids.sudo().write(notification_data)
+ self.message_id._notify_message_notification_update()
def snailmail_print(self):
self._snailmail_print()
def cancel(self):
self.write({'state': 'canceled', 'error_code': False})
- self.send_snailmail_update()
+ self.notification_ids.sudo().write({
+ 'notification_status': 'canceled',
+ })
+ self.message_id._notify_message_notification_update()
@api.model
def _snailmail_cron(self):
@@ -349,34 +385,7 @@ class SnailmailLetter(models.Model):
letters_send._snailmail_print()
@api.model
- def fetch_failed_letters(self):
- failed_letters = self.search([('state', '=', 'error'), ('user_id.id', '=', self.env.user.id), ('res_id', '!=', 0), ('model', '!=', False)])
- return failed_letters._format_snailmail_failures()
-
- @api.model
def _is_valid_address(self, record):
record.ensure_one()
required_keys = ['street', 'city', 'zip', 'country_id']
return all(record[key] for key in required_keys)
-
- def _format_snailmail_failures(self):
- """
- A shorter message to notify a failure update
- """
- failures_infos = []
- for letter in self:
- info = {
- 'message_id': letter.message_id.id,
- 'record_name': letter.message_id.record_name,
- 'model_name': self.env['ir.model']._get(letter.model).display_name,
- 'uuid': letter.message_id.message_id,
- 'res_id': letter.res_id,
- 'model': letter.model,
- 'last_message_date': letter.message_id.date,
- 'module_icon': '/snailmail/static/img/snailmail_failure.png',
- 'snailmail_status': letter.error_code if letter.state == 'error' else '',
- 'snailmail_error': letter.state == 'error',
- 'failure_type': 'snailmail',
- }
- failures_infos.append(info)
- return failures_infos
diff --git a/addons/snailmail/static/src/js/mail_failure.js b/addons/snailmail/static/src/js/mail_failure.js
index cb363051392..903633c42ff 100644
--- a/addons/snailmail/static/src/js/mail_failure.js
+++ b/addons/snailmail/static/src/js/mail_failure.js
@@ -16,14 +16,36 @@ MailFailure.include({
*/
getPreview: function () {
var preview = this._super.apply(this, arguments);
- if (this._failureType === 'snailmail') {
+ if (this._messageType === 'snailmail') {
_.extend(preview, {
- body: _t("An error occured when sending a letter with Snailmail"),
id: 'snailmail_failure',
});
}
return preview;
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _getPreviewBody() {
+ if (this._messageType === 'snailmail') {
+ return _t("An error occured when sending a letter with Snailmail.");
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _getPreviewImage() {
+ if (this._messageType === 'snailmail') {
+ return '/snailmail/static/img/snailmail_failure.png';
+ }
+ return this._super(...arguments);
+ },
});
});
diff --git a/addons/snailmail/static/src/js/message.js b/addons/snailmail/static/src/js/message.js
index 5549fe14261..624f2b4027f 100644
--- a/addons/snailmail/static/src/js/message.js
+++ b/addons/snailmail/static/src/js/message.js
@@ -10,76 +10,39 @@ Message.include({
//--------------------------------------------------------------------------
/**
- * Cancels the 'snailmail.letter' which has a message_id corresponding to
- * the ID of this message, then update the status of the message
- *
+ * Cancels the 'snailmail.letter' which has a message_id corresponding to
+ * the ID of this message.
+ *
* @returns {Deferred}
*/
- cancelLetter: function () {
- var self = this;
- var mailBus = this.call('mail_service', 'getMailBus');
+ cancelLetter() {
return this._rpc({
model: 'mail.message',
method: 'cancel_letter',
args: [[this.getID()]],
- }).then(function () {
- self.setSnailmailStatus('canceled');
- self.setSnailmailError(false);
- mailBus.trigger('update_message', self);
});
},
/**
- * @return {string}
- */
- getSnailmailStatus: function () {
- return this._snailmailStatus.toLowerCase();
- },
- /**
- * Is the sent snailmail letter in error
- *
- * @return {Boolean}
+ * @override
*/
- getSnailmailError: function () {
- return this._snailmailError;
+ getNotificationIcon() {
+ if (this.getType() === 'snail') {
+ return 'fa fa-paper-plane';
+ }
+ return this._super(...arguments);
},
/**
* Retries to send the 'snailmail.letter' corresponding to this message
- *
+ *
* @returns {Deferred}
*/
- resendLetter: function () {
+ resendLetter() {
return this._rpc({
model: 'mail.message',
method: 'send_letter',
args: [[this.getID()]],
});
},
- /**
- * @param {string} snailmailStatus
- */
- setSnailmailStatus: function (snailmailStatus) {
- this._snailmailStatus = snailmailStatus;
- },
- /**
- * @param {Boolean} snailmailError
- */
- setSnailmailError: function (snailmailError) {
- this._snailmailError = snailmailError;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @override
- */
- _setInitialData: function (data){
- this._super.apply(this, arguments);
- this._snailmailStatus = data.snailmail_status;
- this._snailmailError = data.snailmail_error;
- },
});
});
diff --git a/addons/snailmail/static/src/js/snailmail_notification_manager.js b/addons/snailmail/static/src/js/snailmail_notification_manager.js
deleted file mode 100644
index 4d33ba89127..00000000000
--- a/addons/snailmail/static/src/js/snailmail_notification_manager.js
+++ /dev/null
@@ -1,67 +0,0 @@
-odoo.define('snailmail.NotificationManager', function (require) {
-"use strict";
-
-var MailManager = require('mail.Manager');
-var MailFailure = require('mail.model.MailFailure');
-
-MailManager.include({
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @override
- * @param {Object} data structure depending on the type
- * @param {integer} data.id
- */
- _handlePartnerNotification: function (data) {
- if (data.type === 'snailmail_update') {
- this._handleSnailmailUpdateNotification(data);
- } else {
- this._super.apply(this, arguments);
- }
- },
- /**
- * Updates message in thread and systray when there's an update in a snailmail letter
- *
- * @private
- * @param {Object} datas
- * @param {Object[]} datas.elements list of snailmail failure data
- * @param {string} datas.elements[].message_id ID of related message that
- * has a snailmail failure.
- * @param {boolean} datas.elements[].snailmail_error letter is in error
- * @param {string} datas.elements[].snailmail_status status of the letter
- */
- _handleSnailmailUpdateNotification: function (datas) {
- var self = this;
- _.each(datas.elements, function (data) {
- var isNewFailure = data.snailmail_error;
- var matchedFailure = _.find(self._mailFailures, function (failure) {
- return failure.getMessageID() === data.message_id;
- });
-
- if (matchedFailure) {
- var index = _.findIndex(self._mailFailures, matchedFailure);
- if (isNewFailure) {
- self._mailFailures[index] = new MailFailure(self, data);
- } else {
- self._mailFailures.splice(index, 1);
- }
- } else if (isNewFailure) {
- self._mailFailures.push(new MailFailure(self, data));
- }
- var message = _.find(self._messages, function (msg) {
- return msg.getID() === data.message_id;
- });
- if (message) {
- message.setSnailmailStatus(data.snailmail_status);
- message.setSnailmailError(data.snailmail_error);
- self._mailBus.trigger('update_message', message);
- }
- });
- this._mailBus.trigger('update_needaction', this.needactionCounter);
- },
-});
-
-});
diff --git a/addons/snailmail/static/src/js/thread_widget.js b/addons/snailmail/static/src/js/thread_widget.js
index 84cc2a3b2dc..84ab7f9a877 100644
--- a/addons/snailmail/static/src/js/thread_widget.js
+++ b/addons/snailmail/static/src/js/thread_widget.js
@@ -5,39 +5,17 @@ var ThreadWidget = require('mail.widget.Thread');
var Dialog = require('web.Dialog');
var core = require('web.core');
-var QWeb = core.qweb;
var _t = core._t;
ThreadWidget.include({
- dependencies: ['bus_service'],
- events: _.extend({}, ThreadWidget.prototype.events, {
- 'click .o_thread_message_snailmail_missing_required_fields': '_onClickMissingRequiredFields',
- 'click .o_thread_message_snailmail_credit_error': '_onClickCreditError',
- 'click .o_thread_message_snailmail_trial_error': '_onClickTrialError',
- 'click .o_thread_message_snailmail_no_price_available': '_onClickNoPriceAvailable',
- 'click .o_thread_message_snailmail_format_error': '_onClickFormatError',
- 'click .o_thread_message_snailmail_unknown_error': '_onClickUnknownError',
- }),
- /**
- * @override
- */
- init: function () {
- this._super.apply(this, arguments);
- this._enabledOptions = _.defaults(this._enabledOptions, {
- displaySnailmailIcons: true,
- });
- this._disabledOptions = _.defaults(this._disabledOptions, {
- displaySnailmailIcons: false,
- });
- },
+
/**
* @override
*/
render: function (thread, options) {
this._super.apply(this, arguments);
var messages = _.clone(thread.getMessages({ domain: options.domain || [] }));
- this._messages = messages;
- this._renderMessageSnailmailPopover();
+ this._messages = messages;
},
//--------------------------------------------------------------------------
@@ -46,8 +24,8 @@ ThreadWidget.include({
/**
* @private
- * @param {integer} messageID
- * @param {string} content
+ * @param {integer} messageID
+ * @param {string} content
*/
_openCreditErrorDialog: function (messageID, content) {
var message = _.find(this._messages, function (message) {
@@ -57,17 +35,25 @@ ThreadWidget.include({
size: 'medium',
title: _t("Failed letter"),
$content: $('<div>').html(content),
- buttons: [
- {text: _t("Re-send letter"), classes: 'btn-primary', close: true, click: _.bind(message.resendLetter, message)},
- {text: _t("Cancel letter"), close: true, click: _.bind(message.cancelLetter, message)},
- {text: _t("Close"), close: true},
- ],
+ buttons: [{
+ text: _t("Re-send letter"),
+ classes: 'btn-primary',
+ close: true,
+ click: (...args) => message.resendLetter(...args),
+ }, {
+ text: _t("Cancel letter"),
+ close: true,
+ click: (...args) => message.cancelLetter(...args),
+ }, {
+ text: _t("Close"),
+ close: true,
+ }],
}).open();
},
/**
* @private
- * @param {integer} messageID
- * @param {string} content
+ * @param {integer} messageID
+ * @param {string} content
*/
_openGenericErrorDialog: function (messageID, content) {
var message = _.find(this._messages, function (message) {
@@ -77,46 +63,17 @@ ThreadWidget.include({
size: 'medium',
title: _t("Failed letter"),
$content: $('<div>').html(content),
- buttons: [
- {text: _t("Cancel letter"), classes: 'btn-primary', close: true, click: _.bind(message.cancelLetter, message)},
- {text: _t("Close"), close: true},
- ],
+ buttons: [{
+ text: _t("Cancel letter"),
+ classes: 'btn-primary',
+ close: true,
+ click: (...args) => message.cancelLetter(...args),
+ }, {
+ text: _t("Close"),
+ close: true,
+ }],
}).open();
},
- /**
- * Render the popover when mouse-hovering on the mail icon of a message
- * in the thread. There is at most one such popover at any given time.
- *
- * @private
- * @param {mail.model.AbstractMessage[]} messages list of messages in the
- * rendered thread, for which popover on mouseover interaction is
- * permitted.
- */
- _renderMessageSnailmailPopover: function () {
- var self = this;
- if (this._messageSnailmailPopover) {
- this._messageSnailmailPopover.popover('hide');
- }
- if (!this.$('.o_thread_snailmail_tooltip').length) {
- return;
- }
- this._messageSnailmailPopover = this.$('.o_thread_snailmail_tooltip').popover({
- html: true,
- boundary: 'viewport',
- placement: 'auto',
- trigger: 'hover',
- offset: '0, 1',
- content: function () {
- var messageID = $(this).data('message-id');
- var message = _.find(self._messages, function (message) {
- return message.getID() === messageID;
- });
- return QWeb.render('snailmail.widget.Thread.Message.SnailmailTooltip', {
- status: message.getSnailmailStatus()
- });
- },
- });
- },
//--------------------------------------------------------------------------
// Handlers
@@ -158,6 +115,37 @@ ThreadWidget.include({
});
},
/**
+ * Discards the generic error handler since more specific handlers are
+ * defined here.
+ *
+ * @override
+ */
+ _onClickMessageNotificationError(ev) {
+ const $target = $(ev.currentTarget);
+ if ($target.data('message-type') === 'snailmail') {
+ if ($target.data('failure-type') === 'sn_fields') {
+ this._onClickMissingRequiredFields(ev);
+ }
+ if ($target.data('failure-type') === 'sn_credit') {
+ this._onClickCreditError(ev);
+ }
+ if ($target.data('failure-type') === 'sn_trial') {
+ this._onClickTrialError(ev);
+ }
+ if ($target.data('failure-type') === 'sn_price') {
+ this._onClickNoPriceAvailable(ev);
+ }
+ if ($target.data('failure-type') === 'sn_format') {
+ this._onClickFormatError(ev);
+ }
+ if ($target.data('failure-type') === 'sn_error') {
+ this._onClickUnknownError(ev);
+ }
+ } else {
+ this._super(...arguments);
+ }
+ },
+ /**
* @private
* @param {MouseEvent} event
*/
diff --git a/addons/snailmail/static/src/scss/thread.scss b/addons/snailmail/static/src/scss/thread.scss
deleted file mode 100644
index ecf42cef033..00000000000
--- a/addons/snailmail/static/src/scss/thread.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.o_thread_message_snailmail {
- padding: 4px;
-
- &:not(.o_thread_message_snailmail_error) {
- color: grey;
- }
-
- &.o_thread_message_snailmail_error {
- color: red;
- opacity: 1;
- cursor: pointer;
- }
-}
-
-.o_thread_tooltip_snailmail_icon {
- min-width: 1rem;
-}
diff --git a/addons/snailmail/static/src/xml/thread.xml b/addons/snailmail/static/src/xml/thread.xml
index aeda07445be..df3ae1e08d4 100644
--- a/addons/snailmail/static/src/xml/thread.xml
+++ b/addons/snailmail/static/src/xml/thread.xml
@@ -1,37 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<template id="template" xml:space="preserve">
- <t t-extend="mail.widget.Thread.Message">
- <t t-jquery=".o_thread_tooltip_container" t-operation="after">
- <span t-if="message.getType() === 'snailmail' and options.displaySnailmailIcons" class="o_thread_snailmail_tooltip_container">
- <t t-set="thread_icon_class" t-value="'o_thread_snailmail_tooltip o_thread_message_snailmail o_thread_message_snailmail_' + message.getSnailmailStatus()" />
- <t t-if="message.getSnailmailError()">
- <t t-set="thread_icon_class" t-value="thread_icon_class + ' o_thread_message_snailmail_error'"/>
- </t>
- <i t-att-class="thread_icon_class + ' fa fa-paper-plane'" t-att-data-message-id="message.getID()"/>
- </span>
- </t>
+ <t t-inherit="mail.widget.Thread.Message" t-inherit-mode="extension">
+ <xpath expr="//span[@name='notification_icon']" position="attributes">
+ <!--
+ Messages from snaimail are considered to have at most one notification.
+ The failure type of the whole message is considered to be the same as the one from
+ that first notification, and the click action will depend on it.
+ -->
+ <attribute name="t-att-data-failure-type">message.getNotifications()[0].failure_type</attribute>
+ </xpath>
</t>
- <t t-name="snailmail.widget.Thread.Message.SnailmailTooltip">
- <span class="d-inline-block text-center o_thread_tooltip_snailmail">
- <t t-if="status === 'sent'">
- <i class='fa fa-check o_thread_tooltip_snailmail_icon' title="Sent" role="img" aria-label="Sent"/>
- Sent
+ <t t-inherit="mail.widget.Thread.Message.MailTooltip" t-inherit-mode="extension">
+ <!--
+ It was decided that the information displayed for snaimail messages has to be
+ different than for standard messages, see task-1907998.
+ -->
+ <xpath expr="//span[@name='notification_status']" position="replace">
+ <t t-if="notification.notification_type === 'snail'">
+ <span class="d-inline-block text-center o_thread_tooltip_icon">
+ <t t-if="notification.notification_status === 'sent'">
+ <i class="fa fa-check o_thread_tooltip_snailmail_icon" title="Sent" role="img" aria-label="Sent"/>
+ Sent
+ </t>
+ <t t-elif="notification.notification_status === 'canceled'">
+ <i class="fa fa-trash-o o_thread_tooltip_snailmail_icon" title="Canceled" role="img" aria-label="Canceled"/>
+ Canceled
+ </t>
+ <t t-elif="notification.notification_status === 'ready'">
+ <i class="fa fa-clock-o o_thread_tooltip_snailmail_icon" title="Awaiting Dispatch" role="img" aria-label="Awaiting Dispatch"/>
+ Awaiting Dispatch
+ </t>
+ <t t-else="">
+ <i class="fa fa-exclamation text-danger o_thread_tooltip_snailmail_icon" title="Error" role="img" aria-label="Error"/>
+ Error
+ </t>
+ </span>
</t>
- <t t-elif="status === 'canceled'">
- <i class='fa fa-trash-o o_thread_tooltip_snailmail_icon' title="Canceled" role="img" aria-label="Canceled"/>
- Canceled
- </t>
- <t t-elif="status === 'pending'">
- <i class='fa fa-clock-o o_thread_tooltip_snailmail_icon' title="Awaiting Dispatch" role="img" aria-label="Awaiting Dispatch"/>
- Awaiting Dispatch
- </t>
- <t t-else="">
- <i class='fa fa-exclamation text-danger o_thread_tooltip_snailmail_icon' title="Error" role="img" aria-label="Error"/>
- Error
- </t>
- </span>
+ <t t-else="">$0</t>
+ </xpath>
+ <xpath expr="//span[@name='partner_name']" position="replace">
+ <t t-if="notification.notification_type === 'snail'"></t>
+ <t t-else="">$0</t>
+ </xpath>
</t>
</template>
diff --git a/addons/snailmail/static/tests/chatter_snailmail_tests.js b/addons/snailmail/static/tests/chatter_snailmail_tests.js
index 8fcf0ce587b..fe416e424e5 100644
--- a/addons/snailmail/static/tests/chatter_snailmail_tests.js
+++ b/addons/snailmail/static/tests/chatter_snailmail_tests.js
@@ -70,18 +70,14 @@ QUnit.module('Chatter', {
string: "Related Document ID",
type: 'integer',
},
- snailmail_status: {
- string: "Snailmail Status",
- type: "char"
- },
- snailmail_error: {
- string: "Snailmail Error",
- type: "boolean",
- },
message_type: {
string: "Type",
type: 'selection',
},
+ notifications: {
+ string: "Notifications",
+ type: 'array',
+ }
},
records: [{
id: 11,
@@ -106,10 +102,12 @@ QUnit.module('Chatter', {
});
QUnit.test('Sent', async function (assert) {
- assert.expect(7);
+ assert.expect(6);
- this.data['mail.message'].records[0].snailmail_status = 'sent';
- this.data['mail.message'].records[0].snailmail_error = false;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'sent',
+ }];
var form = await createView({
View: FormView,
@@ -120,31 +118,32 @@ QUnit.test('Sent', async function (assert) {
arch: getArch(),
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_sent',
- "Snailmail status of message should be 'sent'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- await testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_sent'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ await testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-check').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-check').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Sent")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Sent")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_sent'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.containsNone($('.modal'), "No modal should open on click");
form.destroy();
});
QUnit.test('Canceled', async function (assert) {
- assert.expect(7);
+ assert.expect(6);
- this.data['mail.message'].records[0].snailmail_status = 'canceled';
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'canceled',
+ }];
var form = await createView({
View: FormView,
@@ -155,32 +154,32 @@ QUnit.test('Canceled', async function (assert) {
arch: getArch(),
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_canceled',
- "Snailmail status of message should be 'canceled'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_canceled'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-trash-o').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-trash-o').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Canceled")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Canceled")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_canceled'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.containsNone($('.modal'), "No modal should open on click");
form.destroy();
});
QUnit.test('Pending', async function (assert) {
- assert.expect(7);
+ assert.expect(6);
- this.data['mail.message'].records[0].snailmail_status = 'pending';
- this.data['mail.message'].records[0].snailmail_error = false;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'ready',
+ }];
var form = await createView({
View: FormView,
@@ -191,32 +190,33 @@ QUnit.test('Pending', async function (assert) {
arch: getArch(),
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_pending',
- "Snailmail status of message should be 'pending'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_pending'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-clock-o').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-clock-o').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Awaiting Dispatch")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Awaiting Dispatch")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_pending'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.containsNone($('.modal'), "No modal should open on click");
form.destroy();
});
QUnit.test('No Price Available', async function (assert) {
- assert.expect(11);
+ assert.expect(10);
- this.data['mail.message'].records[0].snailmail_status = 'no_price_available';
- this.data['mail.message'].records[0].snailmail_error = true;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'exception',
+ 'failure_type': 'sn_price',
+ }];
var form = await createView({
View: FormView,
@@ -234,22 +234,20 @@ QUnit.test('No Price Available', async function (assert) {
}
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_no_price_available',
- "Snailmail status of message should be 'no_price_available'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_no_price_available'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-exclamation').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Error")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Error")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_no_price_available'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
var $modal = $('.modal');
assert.ok($modal.length, "A modal should open on click");
@@ -267,10 +265,13 @@ QUnit.test('No Price Available', async function (assert) {
});
QUnit.test('Format Error', async function (assert) {
- assert.expect(8);
+ assert.expect(7);
- this.data['mail.message'].records[0].snailmail_status = 'format_error';
- this.data['mail.message'].records[0].snailmail_error = true;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'exception',
+ 'failure_type': 'sn_format',
+ }];
testUtils.mock.patch(ThreadWidget, {
do_action: function (action, options) {
@@ -294,22 +295,20 @@ QUnit.test('Format Error', async function (assert) {
}
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_format_error',
- "Snailmail status of message should be 'format_error'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_format_error'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-exclamation').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Error")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Error")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_format_error'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.verifySteps(['do_action'], "'do_action' should have been called");
form.destroy();
@@ -317,10 +316,13 @@ QUnit.test('Format Error', async function (assert) {
});
QUnit.test('Credit Error', async function (assert) {
- assert.expect(14);
+ assert.expect(13);
- this.data['mail.message'].records[0].snailmail_status = 'credit_error';
- this.data['mail.message'].records[0].snailmail_error = true;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'exception',
+ 'failure_type': 'sn_credit',
+ }];
var form = await createView({
View: FormView,
@@ -343,22 +345,20 @@ QUnit.test('Credit Error', async function (assert) {
}
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_credit_error',
- "Snailmail status of message should be 'credit_error'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_credit_error'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-exclamation').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Error")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Error")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_credit_error'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
var $modal = $('.modal');
assert.ok($modal.length, "A modal should open on click");
@@ -369,7 +369,7 @@ QUnit.test('Credit Error', async function (assert) {
assert.notOk($('.modal').length,
"The modal should be closed after click on 'Re-send letter'");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_credit_error'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.containsOnce($modal, 'button:contains("Cancel letter")',
"Modal should have a 'Cancel letter' button");
@@ -385,10 +385,13 @@ QUnit.test('Credit Error', async function (assert) {
});
QUnit.test('Trial Error', async function (assert) {
- assert.expect(14);
+ assert.expect(13);
- this.data['mail.message'].records[0].snailmail_status = 'trial_error';
- this.data['mail.message'].records[0].snailmail_error = true;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'exception',
+ 'failure_type': 'sn_trial',
+ }];
var form = await createView({
View: FormView,
@@ -411,22 +414,20 @@ QUnit.test('Trial Error', async function (assert) {
}
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_trial_error',
- "Snailmail status of message should be 'trial_error'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_trial_error'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-exclamation').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Error")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Error")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_trial_error'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
var $modal = $('.modal');
assert.ok($modal.length, "A modal should open on click");
@@ -437,7 +438,7 @@ QUnit.test('Trial Error', async function (assert) {
assert.containsNone($('.modal'),
"The modal should be closed after click on 'Re-send letter'");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_trial_error'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
$modal = $('.modal');
assert.containsOnce($modal, 'button:contains("Cancel letter")',
@@ -454,10 +455,13 @@ QUnit.test('Trial Error', async function (assert) {
});
QUnit.test('Missing Required Fields', async function (assert) {
- assert.expect(8);
+ assert.expect(7);
- this.data['mail.message'].records[0].snailmail_status = 'missing_required_fields';
- this.data['mail.message'].records[0].snailmail_error = true;
+ this.data['mail.message'].records[0].notifications = [{
+ 'notification_type': 'snail',
+ 'notification_status': 'exception',
+ 'failure_type': 'sn_fields',
+ }];
testUtils.mock.patch(ThreadWidget, {
do_action: function (action, options) {
@@ -480,22 +484,20 @@ QUnit.test('Missing Required Fields', async function (assert) {
}
});
- assert.containsOnce(form, '.o_thread_snailmail_tooltip_container',
+ assert.containsOnce(form, '.o_thread_message_notification',
"Snailmail icon should appear on message");
- assert.containsOnce(form, '.o_thread_message_snailmail_missing_required_fields',
- "Snailmail status of message should be 'missing_required_fields'");
- assert.containsNone(form, '.o_thread_tooltip_snailmail',
+ assert.containsNone(form, '.o_thread_tooltip_icon',
"No tooltip should be present");
- testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_snailmail_missing_required_fields'), 'mouseover');
- assert.ok($('.o_thread_tooltip_snailmail:visible').length,
+ testUtils.dom.triggerMouseEvent(form.$('.o_thread_message_notification'), 'mouseover');
+ assert.ok($('.o_thread_tooltip_icon:visible').length,
"Tooltip should appear when hovering the Snailmail Icon");
- assert.ok($('.o_thread_tooltip_snailmail_icon.fa-exclamation').length,
+ assert.ok($('.o_thread_tooltip_icon .fa-exclamation').length,
"Tooltip should show correct icon");
- assert.ok($('.o_thread_tooltip_snailmail:contains("Error")').length,
+ assert.ok($('.o_thread_tooltip_icon:contains("Error")').length,
"Tooltip should show correct text");
- await testUtils.dom.click(form.$('.o_thread_message_snailmail_missing_required_fields'));
+ await testUtils.dom.click(form.$('.o_thread_message_notification'));
assert.verifySteps(['do_action'], "'do_action' should have been called");
form.destroy();
diff --git a/addons/snailmail/views/assets.xml b/addons/snailmail/views/assets.xml
index 3aacde296c2..def7326b0cd 100644
--- a/addons/snailmail/views/assets.xml
+++ b/addons/snailmail/views/assets.xml
@@ -4,10 +4,8 @@
<xpath expr="." position="inside">
<script type="text/javascript" src="/snailmail/static/src/js/mail_failure.js"></script>
<script type="text/javascript" src="/snailmail/static/src/js/message.js"></script>
- <script type="text/javascript" src="/snailmail/static/src/js/snailmail_notification_manager.js"></script>
<script type="text/javascript" src="/snailmail/static/src/js/systray_messaging_menu.js"></script>
<script type="text/javascript" src="/snailmail/static/src/js/thread_widget.js"></script>
- <link rel="stylesheet" type="text/scss" href="/snailmail/static/src/scss/thread.scss"/>
</xpath>
</template>
diff --git a/addons/test_mail_full/tests/test_sms_management.py b/addons/test_mail_full/tests/test_sms_management.py
index 375ed454f25..e499d981c43 100644
--- a/addons/test_mail_full/tests/test_sms_management.py
+++ b/addons/test_mail_full/tests/test_sms_management.py
@@ -45,14 +45,7 @@ class TestSMSWizards(test_mail_full_common.TestSMSCommon, test_mail_full_common.
{'partner': self.partner_1, 'state': 'sent'},
{'partner': self.partner_2, 'state': 'sent'}
], 'TEST BODY', self.msg, check_sms=True)
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', self.partner_employee.id)],
- [{'type': 'sms_update', 'elements': [{
- 'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['sent', self.partner_2.name]}
- }]}]
- )
+ self.assertMessageBusNotifications(self.msg)
def test_sms_resend_update_number(self):
self._reset_bus()
@@ -67,14 +60,7 @@ class TestSMSWizards(test_mail_full_common.TestSMSCommon, test_mail_full_common.
{'partner': self.partner_1, 'state': 'sent', 'number': self.random_numbers_san[0]},
{'partner': self.partner_2, 'state': 'sent', 'number': self.random_numbers_san[1]}
], 'TEST BODY', self.msg, check_sms=True)
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', self.partner_employee.id)],
- [{'type': 'sms_update', 'elements': [{
- 'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['sent', self.partner_2.name]}
- }]}]
- )
+ self.assertMessageBusNotifications(self.msg)
def test_sms_resend_cancel(self):
self._reset_bus()
@@ -88,14 +74,7 @@ class TestSMSWizards(test_mail_full_common.TestSMSCommon, test_mail_full_common.
{'partner': self.partner_1, 'state': 'canceled', 'number': self.notif_p1.sms_number, 'failure_type': 'sms_number_format'},
{'partner': self.partner_2, 'state': 'canceled', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'}
], 'TEST BODY', self.msg, check_sms=False)
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', self.partner_employee.id)],
- [{'type': 'sms_update', 'elements': [{
- 'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['canceled', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]}
- }]}]
- )
+ self.assertMessageBusNotifications(self.msg)
def test_sms_resend_internals(self):
self._reset_bus()
@@ -123,14 +102,7 @@ class TestSMSWizards(test_mail_full_common.TestSMSCommon, test_mail_full_common.
self.assertSMSNotification([{'partner': self.partner_1, 'state': 'sent'}], 'TEST BODY', self.msg, check_sms=True)
self.assertSMSNotification([{'partner': self.partner_2, 'state': 'canceled', 'number': self.notif_p2.sms_number, 'failure_type': 'sms_credit'}], 'TEST BODY', self.msg, check_sms=False)
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', self.partner_employee.id)],
- [{'type': 'sms_update', 'elements': [{
- 'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['sent', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]}
- }]}]
- )
+ self.assertMessageBusNotifications(self.msg)
def test_sms_cancel(self):
self._reset_bus()
@@ -141,11 +113,4 @@ class TestSMSWizards(test_mail_full_common.TestSMSCommon, test_mail_full_common.
self.assertEqual((self.notif_p1 | self.notif_p2).mapped('notification_status'), ['canceled', 'canceled'])
- self.assertBusNotifications(
- [(self.cr.dbname, 'res.partner', self.partner_employee.id)],
- [{'type': 'sms_update', 'elements': [{
- 'message_id': self.msg.id,
- 'failure_type': 'sms',
- 'notifications': {'%s' % self.partner_1.id: ['canceled', self.partner_1.name], '%s' % self.partner_2.id: ['canceled', self.partner_2.name]}
- }]}]
- )
+ self.assertMessageBusNotifications(self.msg)
diff --git a/addons/test_mail_full/tests/test_sms_performance.py b/addons/test_mail_full/tests/test_sms_performance.py
index 2128c9fe42f..df716786847 100644
--- a/addons/test_mail_full/tests/test_sms_performance.py
+++ b/addons/test_mail_full/tests/test_sms_performance.py
@@ -51,7 +51,7 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.MockSMS):
def test_message_sms_record_1_partner(self):
record = self.test_record.with_user(self.env.user)
pids = self.customer.ids
- with self.mockSMSGateway(), self.assertQueryCount(employee=21): # test_mail_enterprise: 21
+ with self.mockSMSGateway(), self.assertQueryCount(employee=25): # test_mail_enterprise: 25
messages = record._message_sms(
body='Performance Test',
partner_ids=pids,
@@ -66,7 +66,7 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.MockSMS):
def test_message_sms_record_10_partners(self):
record = self.test_record.with_user(self.env.user)
pids = self.partners.ids
- with self.mockSMSGateway(), self.assertQueryCount(employee=39): # test_mail_enterprise: 39
+ with self.mockSMSGateway(), self.assertQueryCount(employee=43): # test_mail_enterprise: 43
messages = record._message_sms(
body='Performance Test',
partner_ids=pids,
@@ -80,7 +80,7 @@ class TestSMSPerformance(BaseMailPerformance, sms_common.MockSMS):
@warmup
def test_message_sms_record_default(self):
record = self.test_record.with_user(self.env.user)
- with self.mockSMSGateway(), self.assertQueryCount(employee=24): # test_mail_enterprise: 24
+ with self.mockSMSGateway(), self.assertQueryCount(employee=28): # test_mail_enterprise: 28
messages = record._message_sms(
body='Performance Test',
)