diff --git a/addons/mail/data/ir_cron_data.xml b/addons/mail/data/ir_cron_data.xml
index 27ae8795804..10073110a3a 100644
--- a/addons/mail/data/ir_cron_data.xml
+++ b/addons/mail/data/ir_cron_data.xml
@@ -43,5 +43,17 @@
1000
+
+
+ Notification: Delete Notifications older than 6 Month
+ 1
+ days
+ -1
+
+
+ model._gc_notifications(max_age_days=180)
+ code
+
+
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 38426d629bd..a7061c77d34 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -190,53 +190,28 @@ class Message(models.Model):
#------------------------------------------------------
@api.model
- def mark_all_as_read(self, channel_ids=None, domain=None):
- """ Remove all needactions of the current partner. If channel_ids is
- given, restrict to messages written in one of those channels. """
+ def mark_all_as_read(self, domain=None):
+ # not really efficient method: it does one db request for the
+ # search, and one for each message in the result set is_read to True in the
+ # current notifications from the relation.
partner_id = self.env.user.partner_id.id
- delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
- if not domain and delete_mode:
- query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s"
- args = [(partner_id,)]
- if channel_ids:
- query += """
- AND mail_message_id in
- (SELECT mail_message_id
- FROM mail_message_mail_channel_rel
- WHERE mail_channel_id in %s)"""
- args += [tuple(channel_ids)]
- query += " RETURNING mail_message_id as id"
- self._cr.execute(query, args)
- self.invalidate_cache()
+ msg_domain = [('needaction_partner_ids', 'in', partner_id)]
+ unread_messages = self.search(expression.AND([msg_domain, domain]))
+ ids = unread_messages.ids
+ notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', ids),
+ ('res_partner_id', '=', partner_id),
+ ('is_read', '=', False)])
+ notifications.write({'is_read': True})
- ids = [m['id'] for m in self._cr.dictfetchall()]
- else:
- # not really efficient method: it does one db request for the
- # search, and one for each message in the result set to remove the
- # current user from the relation.
- msg_domain = [('needaction_partner_ids', 'in', partner_id)]
- if channel_ids:
- msg_domain += [('channel_ids', 'in', channel_ids)]
- unread_messages = self.search(expression.AND([msg_domain, domain]))
- notifications = self.env['mail.notification'].sudo().search([
- ('mail_message_id', 'in', unread_messages.ids),
- ('res_partner_id', '=', self.env.user.partner_id.id),
- ('is_read', '=', False)])
- if delete_mode:
- notifications.unlink()
- else:
- notifications.write({'is_read': True})
- ids = unread_messages.mapped('id')
-
- notification = {'type': 'mark_as_read', 'message_ids': ids, 'channel_ids': channel_ids}
- self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification)
+ notification = {'type': 'mark_as_read', 'message_ids': ids}
+ self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', partner_id), notification)
return ids
def set_message_done(self):
""" Remove the needaction from messages for the current partner. """
partner_id = self.env.user.partner_id
- delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
notifications = self.env['mail.notification'].sudo().search([
('mail_message_id', 'in', self.ids),
@@ -264,10 +239,7 @@ class Message(models.Model):
current_group = [record.id]
current_channel_ids = record.channel_ids
- if delete_mode:
- notifications.unlink()
- else:
- notifications.write({'is_read': True})
+ notifications.write({'is_read': True})
for (msg_ids, channel_ids) in groups:
notification = {'type': 'mark_as_read', 'message_ids': msg_ids, 'channel_ids': [c.id for c in channel_ids]}
@@ -501,20 +473,27 @@ class Message(models.Model):
note_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
# fetch notification status
- notif_dict = {}
- notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('res_partner_id', '!=', False), ('is_read', '=', False)])
+
+ notif_dict = defaultdict(lambda: defaultdict(list))
+ notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('res_partner_id', '!=', False)])
+
for notif in notifs:
mid = notif.mail_message_id.id
- if not notif_dict.get(mid):
- notif_dict[mid] = {'partner_id': list()}
- notif_dict[mid]['partner_id'].append(notif.res_partner_id.id)
+ if notif.is_read:
+ notif_dict[mid]['history_partner_ids'].append(notif.res_partner_id.id)
+ else:
+ notif_dict[mid]['needaction_partner_ids'].append(notif.res_partner_id.id)
for message in message_values:
- message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', [])
- message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id
- message['is_discussion'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id
+ message.update({
+ 'needaction_partner_ids': notif_dict[message['id']]['needaction_partner_ids'],
+ 'history_partner_ids': notif_dict[message['id']]['history_partner_ids'],
+ 'is_note': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id,
+ 'is_discussion': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id,
+ 'subtype_description': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
+ })
message['is_notification'] = message['message_type'] == 'user_notification'
- message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
+
if message['model'] and self.env[message['model']]._original_module:
message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module)
return message_values
diff --git a/addons/mail/models/mail_notification.py b/addons/mail/models/mail_notification.py
index c89ce0fd57d..e47f337e491 100644
--- a/addons/mail/models/mail_notification.py
+++ b/addons/mail/models/mail_notification.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
+from dateutil.relativedelta import relativedelta
+
from odoo import api, fields, models
from odoo.tools.translate import _
@@ -38,6 +40,7 @@ class Notification(models.Model):
("UNKNOWN", "Unknown error"),
], string='Failure type')
failure_reason = fields.Text('Failure reason', copy=False)
+ read_date = fields.Datetime('Read Date', copy=False)
_sql_constraints = [
# email notification;: partner is required
@@ -57,3 +60,25 @@ class Notification(models.Model):
return dict(type(self).failure_type.selection).get(self.failure_type, _('No Error'))
else:
return _("Unknown error") + ": %s" % (self.failure_reason or '')
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('is_read'):
+ vals['read_date'] = fields.Datetime.now()
+ return super(Notification, self).create(vals_list)
+
+ def write(self, vals):
+ if vals.get('is_read'):
+ vals['read_date'] = fields.Datetime.now()
+ return super(Notification, self).write(vals)
+
+ @api.model
+ def _gc_notifications(self, max_age_days=180):
+ domain = [
+ ('is_read', '=', True),
+ ('read_date', '<', fields.Datetime.now() - relativedelta(days=max_age_days)),
+ ('res_partner_id.partner_share', '=', False),
+ ('notification_type', '=', 'email')
+ ]
+ return self.search(domain).unlink()
diff --git a/addons/mail/static/src/js/discuss_mobile.js b/addons/mail/static/src/js/discuss_mobile.js
index 617700c20c1..f90491636a7 100644
--- a/addons/mail/static/src/js/discuss_mobile.js
+++ b/addons/mail/static/src/js/discuss_mobile.js
@@ -71,7 +71,7 @@ Discuss.include({
* @returns {Boolean} true iff we currently are in the Inbox tab
*/
_isInInboxTab: function () {
- return _.contains(['mailbox_inbox', 'mailbox_starred'], this._currentState);
+ return _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], this._currentState);
},
/**
* @override
@@ -160,7 +160,7 @@ Discuss.include({
*/
_updateContent: function (type) {
var self = this;
- var inMailbox = type === 'mailbox_inbox' || type === 'mailbox_starred';
+ var inMailbox = _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], type);
if (!inMailbox && this._isInInboxTab()) {
// we're leaving the inbox, so store the thread scrolltop
this._storeThreadState();
@@ -230,8 +230,8 @@ Discuss.include({
// update bottom buttons
self.$('.o_mail_mobile_tab').removeClass('active');
- // mailbox_inbox and mailbox_starred share the same tab
- type = type === 'mailbox_starred' ? 'mailbox_inbox' : type;
+ // mailbox_inbox, mailbox_starred and mailbox_history share the same tab
+ type = _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], type) ? 'mailbox_inbox' : type;
self.$('.o_mail_mobile_tab[data-type=' + type + ']').addClass('active');
});
},
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index d22569461e5..e5470d219a2 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -32,6 +32,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {string} [data.moderation_status='accepted']
* @param {string} [data.module_icon]
* @param {Array} [data.needaction_partner_ids = []]
+ * @param {Array} [data.history_partner_ids = []]
* @param {string} [data.record_name]
* @param {integer} [data.res_id]
* @param {Array} [data.starred_partner_ids = []]
@@ -648,6 +649,9 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
if (_.contains(this._starredPartnerIDs, session.partner_id)) {
this.setStarred(true);
}
+ if (_.contains(this._historyPartnerIDs, session.partner_id)) {
+ this._setHistory(true);
+ }
if (
this.originatesFromChannel() &&
_.contains(
@@ -739,6 +743,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {string} [data.moderation_status='accepted']
* @param {string} [data.module_icon]
* @param {Array} [data.needaction_partner_ids = []]
+ * @param {Array} [data.history_partner_ids = []]
* @param {string} [data.record_name]
* @param {integer} [data.res_id]
* @param {Array} [data.starred_partner_ids = []]
@@ -757,6 +762,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
this._moduleIcon = data.module_icon;
this._needactionPartnerIDs = data.needaction_partner_ids || [];
this._starredPartnerIDs = data.starred_partner_ids || [];
+ this._historyPartnerIDs = data.history_partner_ids || [];
this._subject = data.subject;
this._subtypeDescription = data.subtype_description;
this._threadIDs = data.channel_ids || [];
@@ -764,6 +770,21 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
this._moderationStatus = data.moderation_status || 'accepted';
},
+ /*
+ * Set whether the message is history or not.
+ * If it is history, the message is moved to the "History" mailbox.
+ * Note that this function only applies it locally, the server is not aware
+ *
+ * @private
+ * @param {boolean} history if set, the message is history
+ */
+ _setHistory: function (history) {
+ if (history) {
+ this._addThread('mailbox_history');
+ } else {
+ this.removeThread('mailbox_history');
+ }
+ },
/**
* Set whether the message is moderated by current user or not.
* If it is moderated by the current user, the message is moved to the
diff --git a/addons/mail/static/src/js/models/threads/mailbox.js b/addons/mail/static/src/js/models/threads/mailbox.js
index bedd6af27e1..236d960c02d 100644
--- a/addons/mail/static/src/js/models/threads/mailbox.js
+++ b/addons/mail/static/src/js/models/threads/mailbox.js
@@ -128,7 +128,6 @@ var Mailbox = SearchableThread.extend({
model: 'mail.message',
method: 'mark_all_as_read',
kwargs: {
- channel_ids: [],
domain: domain,
},
});
@@ -183,6 +182,8 @@ var Mailbox = SearchableThread.extend({
return [['needaction', '=', true]];
} else if (this._id === 'mailbox_starred') {
return [['starred', '=', true]];
+ } else if (this._id === 'mailbox_history') {
+ return [['needaction', '=', false]];
} else if (this._id === 'mailbox_moderation') {
return [['need_moderation', '=', true]];
} else {
diff --git a/addons/mail/static/src/js/models/threads/searchable_thread.js b/addons/mail/static/src/js/models/threads/searchable_thread.js
index 511bc6348c8..74018af1bc0 100644
--- a/addons/mail/static/src/js/models/threads/searchable_thread.js
+++ b/addons/mail/static/src/js/models/threads/searchable_thread.js
@@ -135,11 +135,12 @@ var SearchableThread = Thread.extend({
* @override
* @private
* @param {mail.model.Message} message
- * @param {Object} options
+ * @param {Object} [options={}]
* @param {Array} [options.domain=[]]
* @param {boolean} [options.incrementUnread=false]
*/
_addMessage: function (message, options) {
+ options = options || {};
this._super.apply(this, arguments);
var cache = this._getCache(options.domain || []);
var index = _.sortedIndex(cache.messages, message, function (msg) {
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 6c7cefe42e3..2aa083a56d2 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -1257,7 +1257,7 @@ var MailManager = AbstractService.extend({
},
/**
* Update the mailboxes with mail data fetched from server, namely 'Inbox',
- * 'Starred', and 'Moderation Queue' if the user is a moderator of a channel
+ * 'Starred', 'History', and 'Moderation Queue' if the user is a moderator of a channel
*
* @private
* @param {Object} data
@@ -1281,6 +1281,10 @@ var MailManager = AbstractService.extend({
name: _t("Starred"),
mailboxCounter: data.starred_counter || 0,
});
+ this._addMailbox({
+ id: 'history',
+ name: _t("History"),
+ });
if (data.is_moderator) {
this._addMailbox({
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 c22276867b1..704e114d8bd 100644
--- a/addons/mail/static/src/js/services/mail_notification_manager.js
+++ b/addons/mail/static/src/js/services/mail_notification_manager.js
@@ -319,12 +319,14 @@ MailManager.include({
*/
_handlePartnerMarkAsReadNotification: function (data) {
var self = this;
+ var history = this.getMailbox('history');
_.each(data.message_ids, function (messageID) {
var message = _.find(self._messages, function (msg) {
return msg.getID() === messageID;
});
if (message) {
self._removeMessageFromThread('mailbox_inbox', message);
+ history.addMessage(message);
self._mailBus.trigger('update_message', message, data.type);
}
});
diff --git a/addons/mail/static/src/scss/thread.scss b/addons/mail/static/src/scss/thread.scss
index 89e5f220ea2..5654020c783 100644
--- a/addons/mail/static/src/scss/thread.scss
+++ b/addons/mail/static/src/scss/thread.scss
@@ -229,6 +229,12 @@
margin-bottom: 20px;
font-weight: bold;
font-size: 125%;
+
+ &.o_neutral_face_icon:before {
+ @extend %o-nocontent-init-image;
+ @include size(120px, 140px);
+ background: transparent url(/web/static/src/img/neutral_face.svg) no-repeat center;
+ }
}
.o_mail_no_content {
diff --git a/addons/mail/static/src/xml/discuss.xml b/addons/mail/static/src/xml/discuss.xml
index afee3a84da2..71138ae1f24 100644
--- a/addons/mail/static/src/xml/discuss.xml
+++ b/addons/mail/static/src/xml/discuss.xml
@@ -60,6 +60,10 @@
+
+ History
+
Moderation Queue
@@ -242,6 +246,9 @@
+
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index dada8901491..ef21b2c5ede 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -128,9 +128,13 @@
New messages appear here.
- No starred message
+ No starred messages
You can mark any message as 'starred', and it shows up in this mailbox.
+
+ No history messages
+ Messages marked as read will appear in the history.
+
You have no message to moderate
Pending moderation messages appear here.
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 8c65266a392..7473194d2b5 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -50,6 +50,11 @@ QUnit.module('Discuss', {
type: 'many2many',
relation: 'res.partner',
},
+ history_partner_ids: {
+ string: "Partners with History",
+ type: 'many2many',
+ relation: 'res.partner',
+ },
model: {
string: "Related Document model",
type: 'char',
@@ -72,6 +77,24 @@ QUnit.module('Discuss', {
im_status: 'online',
}]
},
+ 'mail.notification': {
+ fields: {
+ is_read: {
+ string: "Is Read",
+ type: 'boolean',
+ },
+ mail_message_id: {
+ string: "Message",
+ type: 'many2one',
+ relation: 'mail.message',
+ },
+ res_partner_id: {
+ string: "Needaction Recipient",
+ type: 'many2one',
+ relation: 'res.partner',
+ },
+ },
+ },
};
this.services = mailTestUtils.getMailServices();
},
@@ -83,7 +106,7 @@ QUnit.module('Discuss', {
});
QUnit.test('basic rendering', async function (assert) {
- assert.expect(5);
+ assert.expect(6);
var discuss = await createDiscuss({
id: 1,
@@ -105,11 +128,15 @@ QUnit.test('basic rendering', async function (assert) {
var $inbox = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_inbox]');
assert.strictEqual($inbox.length, 1,
- "should have the channel item 'mailbox_inbox' in the sidebar");
+ "should have the mailbox item 'mailbox_inbox' in the sidebar");
var $starred = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_starred]');
assert.strictEqual($starred.length, 1,
- "should have the channel item 'mailbox_starred' in the sidebar");
+ "should have the mailbox item 'mailbox_starred' in the sidebar");
+
+ var $history = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_history]');
+ assert.strictEqual($history.length, 1,
+ "should have the mailbox item 'mailbox_history' in the sidebar");
discuss.destroy();
});
@@ -1490,54 +1517,382 @@ QUnit.test('custom-named DM conversation', async function (assert) {
discuss.destroy();
});
-QUnit.test('input not cleared on unresolved message_post rpc', async function (assert) {
- assert.expect(2);
- // Promise to simulate late server response on message post
- var messagePostPromise = testUtils.makeTestPromise();
+QUnit.test('messages marked as read move to "History" mailbox', async function (assert) {
+ assert.expect(3);
+
+ this.data['mail.message'].records = [{
+ author_id: [5, 'Demo User'],
+ body: 'test 1
',
+ id: 1,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }, {
+ author_id: [6, 'Test User'],
+ body: 'test 2
',
+ id: 2,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }];
+ this.data['mail.notification'].records = [{
+ id: 50,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }, {
+ id: 51,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }];
this.data.initMessaging = {
- channel_slots: {
- channel_channel: [{
- id: 1,
- channel_type: "channel",
- name: "general",
- }],
- },
+ needaction_inbox_counter: 2,
};
+ var markAllReadDef = testUtils.makeTestPromise();
+ var objectDiscuss;
+
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
+ session: { partner_id: 3 },
mockRPC: function (route, args) {
- if (args.method === 'message_post') {
- return messagePostPromise;
+ if (args.method === 'mark_all_as_read') {
+ _.each(this.data['mail.message'].records, function (message) {
+ message.history_partner_ids = [3];
+ message.needaction_partner_ids = [];
+ });
+ var notificationData = {
+ type: 'mark_as_read',
+ message_ids: [1, 2],
+ };
+ var notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ return this._super.apply(this, arguments);
+ },
+ })
+ objectDiscuss = discuss;
+
+ var $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
+ var $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+
+ await testUtils.dom.click($history);
+ assert.containsOnce(discuss, '.o_mail_no_content',
+ "should display no content message");
+
+ await testUtils.dom.click($inbox);
+
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ testUtils.dom.click($markAllReadButton);
+
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+
+ assert.containsN(discuss, '.o_thread_message', 2,
+ "there should be two messages in History");
+
+ discuss.destroy();
+});
+
+QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
+ assert.expect(10);
+
+ var messagesData = [];
+
+ for (var i = 0; i < 40; i++) {
+ messagesData.push({
+ author_id: [i, 'User ' + i],
+ body: 'test ' + i + '
',
+ id: i,
+ needaction: true,
+ needaction_partner_ids: [3],
+ });
+ }
+
+ this.data['mail.message'].records = messagesData;
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
+ };
+
+ var messageFetchCount = 0;
+ var loadMoreDef = testUtils.makeTestPromise();
+ var markAllReadDef = testUtils.makeTestPromise();
+ var objectDiscuss;
+
+ var discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC: function (route, args) {
+ if (args.method === 'mark_all_as_read') {
+ var messageIDs = [];
+ for (var i = 0; i < messagesData.length; i++) {
+ this.data['mail.message'].records[i].history_partner_ids = [3];
+ this.data['mail.message'].records[i].needaction_partner_ids = [];
+ this.data['mail.message'].records[i].needaction = false;
+ messageIDs.push(i);
+ }
+ var notificationData = {
+ type: 'mark_as_read',
+ message_ids: messageIDs,
+ };
+ var notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ if (args.method === 'message_fetch') {
+ // 1st message_fetch: 'Inbox' initially
+ // 2nd message_fetch: 'History' initially
+ // 3rd message_fetch: 'History' load more
+ assert.step(args.method);
+
+ messageFetchCount++;
+ if (messageFetchCount === 3) {
+ loadMoreDef.resolve();
+ }
}
return this._super.apply(this, arguments);
},
});
+ objectDiscuss = discuss;
- // Click on channel 'general'
- var $general = discuss.$('.o_mail_discuss_sidebar').find('.o_mail_discuss_item[data-thread-id=1]');
- await testUtils.dom.click($general);
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for needaction messages (Inbox)");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages that are loaded in Inbox");
- // Type message
- var $input = discuss.$('textarea.o_composer_text_field').first();
- $input.focus();
- $input.val('test message');
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
- // Send message
- await testUtils.fields.triggerKeydown($input, 'enter');
- assert.strictEqual($input.val(), 'test message', "composer should not be cleared on send without server response");
+ await testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
- // Simulate server response
- messagePostPromise.resolve();
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ var $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+
+ await testUtils.dom.click($history);
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for history");
+
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages in History");
+
+ // simulate a scroll to top to load more messages
+ discuss.$('.o_mail_thread').scrollTop(0);
+
+ await loadMoreDef;
await testUtils.nextTick();
- assert.strictEqual($input.val(), '', "composer should be cleared on send after server response");
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch more messages in history for loadMore");
+ assert.containsN(discuss, '.o_thread_message', 40,
+ "there should be 40 messages in History");
+
discuss.destroy();
});
+
+QUnit.test('messages marked as read move to "History" mailbox', async function (assert) {
+ assert.expect(3);
+
+ this.data['mail.message'].records = [{
+ author_id: [5, 'Demo User'],
+ body: 'test 1
',
+ id: 1,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }, {
+ author_id: [6, 'Test User'],
+ body: 'test 2
',
+ id: 2,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }];
+ this.data['mail.notification'].records = [{
+ id: 50,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }, {
+ id: 51,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }];
+
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
+ };
+
+ const markAllReadDef = testUtils.makeTestPromise();
+ let objectDiscuss;
+
+ const discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC(route, args) {
+ if (args.method === 'mark_all_as_read') {
+ for (const message of (this.data['mail.message'].records)) {
+ message.history_partner_ids = [3];
+ message.needaction_partner_ids = [];
+ }
+ const notificationData = {
+ type: 'mark_as_read',
+ message_ids: [1, 2],
+ };
+ const notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ return this._super.apply(this, arguments);
+ },
+ })
+ objectDiscuss = discuss;
+
+ const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
+ let $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.containsOnce(discuss, '.o_mail_no_content',
+ "should display no content message");
+
+ await testUtils.dom.click($inbox);
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.containsN(discuss, '.o_thread_message', 2,
+ "there should be two messages in History");
+
+ discuss.destroy();
+});
+
+QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
+ assert.expect(10);
+
+ const messagesData = [];
+ for (let i = 0; i < 40; i++) {
+ messagesData.push({
+ author_id: [i, 'User ' + i],
+ body: 'test ' + i + '
',
+ id: i,
+ needaction: true,
+ needaction_partner_ids: [3],
+ });
+ }
+
+ this.data['mail.message'].records = messagesData;
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
+ };
+
+ let messageFetchCount = 0;
+ const loadMoreDef = testUtils.makeTestPromise();
+ const markAllReadDef = testUtils.makeTestPromise();
+ let objectDiscuss;
+
+ const discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC(route, args) {
+ if (args.method === 'mark_all_as_read') {
+ const messageIDs = [];
+ for (let i = 0; i < messagesData.length; i++) {
+ this.data['mail.message'].records[i].history_partner_ids = [3];
+ this.data['mail.message'].records[i].needaction_partner_ids = [];
+ this.data['mail.message'].records[i].needaction = false;
+ messageIDs.push(i);
+ }
+ const notificationData = {
+ type: 'mark_as_read',
+ message_ids: messageIDs,
+ };
+ const notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ if (args.method === 'message_fetch') {
+ // 1st message_fetch: 'Inbox' initially
+ // 2nd message_fetch: 'History' initially
+ // 3rd message_fetch: 'History' load more
+ assert.step(args.method);
+
+ messageFetchCount++;
+ if (messageFetchCount === 3) {
+ loadMoreDef.resolve();
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ objectDiscuss = discuss;
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for needaction messages (Inbox)");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages that are loaded in Inbox");
+
+ const $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ await testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ const $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for history");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages in History");
+
+ // simulate a scroll to top to load more messages
+ discuss.$('.o_mail_thread').scrollTop(0);
+ await loadMoreDef;
+ await testUtils.nextTick();
+ assert.verifySteps(['message_fetch'],
+ "should fetch more messages in history for loadMore");
+ assert.containsN(discuss, '.o_thread_message', 40,
+ "there should be 40 messages in History");
+
+ discuss.destroy();
+});
+
});
});
diff --git a/addons/test_mail/tests/common.py b/addons/test_mail/tests/common.py
index 752c489cadb..d3c41a16ab1 100644
--- a/addons/test_mail/tests/common.py
+++ b/addons/test_mail/tests/common.py
@@ -73,7 +73,7 @@ class BaseFunctionalTest(common.SavepointCase):
for partner_attribute in counters.keys():
counter, notif_type, notif_read = counters[partner_attribute]
partner = getattr(self, partner_attribute)
- partner_notif = new_notifications.filtered(lambda n: n.res_partner_id == partner)
+ partner_notif = new_notifications.filtered(lambda n: n.res_partner_id == partner and (n.is_read == (notif_read not in ['unread', ''])))
self.assertEqual(len(partner_notif), counter)
diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py
index 07116885592..d4181f5b334 100644
--- a/addons/test_mail/tests/test_mail_message.py
+++ b/addons/test_mail/tests/test_mail_message.py
@@ -336,7 +336,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
# mark all as read clear needactions
group_private.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[emp_partner.id])
- emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ emp_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all as read should conclude all needactions")
@@ -353,7 +353,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 1, "message not accessible is currently still counted")
- emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ emp_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones")
@@ -364,7 +364,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
# mark all as read clear needactions
self.group_pigs.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[portal_partner.id])
- portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ portal_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all as read should conclude all needactions")
@@ -380,7 +380,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 1, "message not accessible is currently still counted")
- portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ portal_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones")