diff --git a/addons/contacts/__manifest__.py b/addons/contacts/__manifest__.py index 0a910c80538..0e7702f34ad 100644 --- a/addons/contacts/__manifest__.py +++ b/addons/contacts/__manifest__.py @@ -15,6 +15,9 @@ You can track your vendors, customers and other contacts. 'data': [ 'views/contact_views.xml', ], + 'demo': [ + 'data/mail_demo.xml', + ], 'application': True, 'license': 'LGPL-3', 'assets': { diff --git a/addons/contacts/data/mail_demo.xml b/addons/contacts/data/mail_demo.xml new file mode 100644 index 00000000000..12eb254cbd0 --- /dev/null +++ b/addons/contacts/data/mail_demo.xml @@ -0,0 +1,19 @@ + + + + + + +

Hello! Could you send us your address ?

+ + res.partner + + email + +
+ + + + + +
diff --git a/addons/im_livechat/tests/test_message.py b/addons/im_livechat/tests/test_message.py index 3957660a45c..79a8a2c4b7d 100644 --- a/addons/im_livechat/tests/test_message.py +++ b/addons/im_livechat/tests/test_message.py @@ -73,6 +73,7 @@ class TestImLivechatMessage(TransactionCase): 'recipients': [], 'record_name': "test1 Ernest Employee", 'res_id': channel_livechat_1.id, + 'scheduledDatetime': False, 'sms_ids': [], 'starred_partner_ids': [], 'subject': False, diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 03456375469..4ef8133eaef 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -904,10 +904,17 @@ class Message(models.Model): domain = expression.AND([domain, [('id', '>', min_id)]]) return self.search(domain, limit=limit) - def message_format(self, format_reply=True): - """ Get the message values in the format for web client. Since message values can be broadcasted, - computed fields MUST NOT BE READ and broadcasted. - :returns list(dict). + def message_format(self, format_reply=True, msg_vals=None): + """ Get the message values in the format for web client. Since message + values can be broadcasted, computed fields MUST NOT BE READ and + broadcasted. + + :param msg_vals: dictionary of values used to create the message. If + given it may be used to access values related to ``message`` without + accessing it directly. It lessens query count in some optimized use + cases by avoiding access message content in db; + + :returns list(dict). Example : { 'body': HTML content of the message @@ -958,6 +965,19 @@ class Message(models.Model): com_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment') note_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_note') + + # fetch scheduled notifications once, only if msg_vals is not given to + # avoid useless queries when notifying Inbox right after a message_post + scheduled_dt_by_msg_id = {} + if msg_vals: + scheduled_dt_by_msg_id = {msg.id: msg_vals.get('scheduled_date') for msg in self} + elif self: + schedulers = self.env['mail.message.schedule'].sudo().search([ + ('mail_message_id', 'in', self.ids) + ]) + for scheduler in schedulers: + scheduled_dt_by_msg_id[scheduler.mail_message_id.id] = scheduler.scheduled_datetime + for vals in vals_list: message_sudo = self.browse(vals['id']).sudo().with_prefetch(self.ids) notifs = message_sudo.notification_ids.filtered(lambda n: n.res_partner_id) @@ -969,6 +989,7 @@ class Message(models.Model): 'subtype_description': message_sudo.subtype_id.description, 'is_notification': vals['message_type'] == 'user_notification', 'recipients': [{'id': p.id, 'name': p.name} for p in message_sudo.partner_ids], + 'scheduledDatetime': scheduled_dt_by_msg_id.get(vals['id'], False), }) if vals['model'] and self.env[vals['model']]._original_module: vals['module_icon'] = modules.module.get_module_icon(self.env[vals['model']]._original_module) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index f65807673d2..dd60edc395a 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2857,7 +2857,7 @@ class MailThread(models.AbstractModel): } for pid in inbox_pids] self.env['mail.notification'].sudo().create(notif_create_values) - message_format_values = message.message_format()[0] + message_format_values = message.message_format(msg_vals=msg_vals)[0] for partner_id in inbox_pids: bus_notifications.append((self.env['res.partner'].browse(partner_id), 'mail.message/inbox', dict(message_format_values))) self.env['bus.bus'].sudo()._sendmany(bus_notifications) diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml index ed5c98dfcfd..e96eef8fa04 100644 --- a/addons/mail/static/src/components/message/message.xml +++ b/addons/mail/static/src/components/message/message.xml @@ -76,6 +76,11 @@ on document +
+ + + +
diff --git a/addons/mail/static/src/models/message.js b/addons/mail/static/src/models/message.js index 17bae319e1b..70d2e56d8f1 100644 --- a/addons/mail/static/src/models/message.js +++ b/addons/mail/static/src/models/message.js @@ -99,6 +99,9 @@ Model({ if ("recipients" in data) { data2.recipients = data.recipients; } + if ("scheduledDatetime" in data) { + data2.scheduledDatetime = data.scheduledDatetime; + } if ("starred_partner_ids" in data && this.messaging.currentPartner) { data2.isStarred = data.starred_partner_ids.includes( this.messaging.currentPartner.id @@ -698,6 +701,23 @@ Model({ }, }), recipients: many("Partner"), + /** + * In case of delayed notification, a scheduled datetime linked to the + * message scheduler is given in order to display it new to the post + * datetime. + */ + scheduledDatetime: attr(), + scheduledMomentDate: attr({ + compute() { + if (!this.scheduledDatetime) { + return clear(); + } + if (!moment.isMoment(this.scheduledDatetime)) { + return moment(str_to_datetime(this.scheduledDatetime)); + } + return this.scheduledDatetime; + }, + }), shortTime: attr({ compute() { if (!this.momentDate) { diff --git a/addons/mail/static/src/models/message_view.js b/addons/mail/static/src/models/message_view.js index d488451344c..b685547f642 100644 --- a/addons/mail/static/src/models/message_view.js +++ b/addons/mail/static/src/models/message_view.js @@ -713,5 +713,22 @@ Model({ return this.env._t("Read More"); }, }), + /** + * Scheduled for sending + */ + scheduledFromNow: attr({ + compute() { + if (!this.message) { + return clear(); + } + if (!this.message.scheduledMomentDate) { + return clear(); + } + if (!this.clockWatcher.clock.date) { + return clear(); + } + return this.message.scheduledMomentDate.fromNow(); + }, + }), }, }); diff --git a/addons/rating/models/mail_message.py b/addons/rating/models/mail_message.py index dcd72234c3b..5a8419af34a 100644 --- a/addons/rating/models/mail_message.py +++ b/addons/rating/models/mail_message.py @@ -26,8 +26,8 @@ class MailMessage(models.Model): ]) return [('id', 'in', ratings.mapped('message_id').ids)] - def message_format(self, format_reply=True): - message_values = super().message_format(format_reply=format_reply) + def message_format(self, format_reply=True, msg_vals=None): + message_values = super().message_format(format_reply=format_reply, msg_vals=msg_vals) rating_mixin_messages = self.filtered(lambda message: message.model and message.res_id diff --git a/addons/sms/models/mail_message.py b/addons/sms/models/mail_message.py index e3a4c67c5df..65906097a84 100644 --- a/addons/sms/models/mail_message.py +++ b/addons/sms/models/mail_message.py @@ -33,13 +33,13 @@ class MailMessage(models.Model): return ['&', ('notification_ids.notification_status', '=', 'exception'), ('notification_ids.notification_type', '=', 'sms')] raise NotImplementedError() - def message_format(self, format_reply=True): + def message_format(self, format_reply=True, msg_vals=None): """ Override in order to retrieves data about SMS (recipient name and SMS status) TDE FIXME: clean the overall message_format thingy """ - message_values = super(MailMessage, self).message_format(format_reply=format_reply) + message_values = super(MailMessage, self).message_format(format_reply=format_reply, msg_vals=msg_vals) all_sms_notifications = self.env['mail.notification'].sudo().search([ ('mail_message_id', 'in', [r['id'] for r in message_values]), ('notification_type', '=', 'sms') diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 914f0542f45..ba1b99a8ecd 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -1187,7 +1187,7 @@ class TestMailComplexPerformance(BaseMailPerformance): ] }]) - with self.assertQueryCount(employee=6): + with self.assertQueryCount(employee=7): res = messages.message_format() self.assertEqual(len(res), 2) for message in res: @@ -1196,7 +1196,7 @@ class TestMailComplexPerformance(BaseMailPerformance): self.env.flush_all() self.env.invalidate_all() - with self.assertQueryCount(employee=19): + with self.assertQueryCount(employee=20): res = messages.message_format() self.assertEqual(len(res), 2) for message in res: @@ -1217,14 +1217,14 @@ class TestMailComplexPerformance(BaseMailPerformance): 'res_id': record.id } for record in records]) - with self.assertQueryCount(employee=5): + with self.assertQueryCount(employee=6): res = messages.message_format() self.assertEqual(len(res), 6) self.env.flush_all() self.env.invalidate_all() - with self.assertQueryCount(employee=14): + with self.assertQueryCount(employee=15): res = messages.message_format() self.assertEqual(len(res), 6)