diff options
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', ) |
