diff --git a/addons/im_livechat/controllers/main.py b/addons/im_livechat/controllers/main.py index 84f8a83ac47..41c8c0042dd 100644 --- a/addons/im_livechat/controllers/main.py +++ b/addons/im_livechat/controllers/main.py @@ -112,6 +112,7 @@ class LivechatController(http.Controller): 'rating': rate, 'consumed': True, 'feedback': reason, + 'is_internal': False, } if not channel.rating_ids: res_model_id = request.env['ir.model'].sudo().search([('model', '=', channel._name)], limit=1).id diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 539557f93e9..2b906074915 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -73,6 +73,7 @@ class Message(models.Model): mail_activity_type_id = fields.Many2one( 'mail.activity.type', 'Mail Activity Type', index=True, ondelete='set null') + is_internal = fields.Boolean('Employee Only', help='Hide to public / portal users, independently from subtype configuration.') # origin email_from = fields.Char('From', help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.") author_id = fields.Many2one( @@ -234,9 +235,9 @@ class Message(models.Model): return super(Message, self)._search( args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) - # Non-employee see only messages with a subtype (aka, no internal logs) + # Non-employee see only messages with a subtype and not internal if not self.env['res.users'].has_group('base.group_user'): - args = ['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] + list(args) + args = expression.AND([self._get_search_domain_share(), args]) # Perform a super with count as False, to have the ids, not a counter ids = super(Message, self)._search( args, offset=offset, limit=limit, order=order, @@ -359,7 +360,9 @@ class Message(models.Model): FROM "%s" AS message LEFT JOIN "mail_message_subtype" as subtype ON message.subtype_id = subtype.id - WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,)) + WHERE message.message_type = %%s AND + (message.is_internal IS TRUE OR message.subtype_id IS NULL OR subtype.internal IS TRUE) AND + message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,)) if self._cr.fetchall(): raise AccessError( _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) @@ -1279,3 +1282,6 @@ class Message(models.Model): 'message_needaction', 'message_needaction_counter', ], ids=[res_id]) + + def _get_search_domain_share(self): + return ['&', '&', ('is_internal', '=', False), ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index a4abcaae227..91e9c7e16a1 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1056,7 +1056,7 @@ class MailThread(models.AbstractModel): parent_message = self.env['mail.message'].sudo().browse(message_dict['parent_id']) partner_ids = [] if not subtype_id: - if message_dict.pop('internal', False): + if message_dict.get('is_internal'): subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note') if parent_message and parent_message.author_id: partner_ids = [parent_message.author_id.id] @@ -1367,7 +1367,7 @@ class MailThread(models.AbstractModel): 'references': references, 'in_reply_to': in-reply-to, 'parent_id': parent mail.message based on in_reply_to or references, - 'internal': answer to an internal message (note), + 'is_internal': answer to an internal message (note), 'date': date, 'attachments': [('file1', 'bytes'), ('file2', 'bytes')} @@ -1441,14 +1441,14 @@ class MailThread(models.AbstractModel): parent_ids = self.env['mail.message'].search([('message_id', '=', msg_dict['in_reply_to'])], limit=1) if parent_ids: msg_dict['parent_id'] = parent_ids.id - msg_dict['internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False + msg_dict['is_internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False if msg_dict['references'] and 'parent_id' not in msg_dict: references_msg_id_list = tools.mail_header_msgid_re.findall(msg_dict['references']) parent_ids = self.env['mail.message'].search([('message_id', 'in', [x.strip() for x in references_msg_id_list])], limit=1) if parent_ids: msg_dict['parent_id'] = parent_ids.id - msg_dict['internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False + msg_dict['is_internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False msg_dict.update(self._message_parse_extract_payload(message, save_original=save_original)) msg_dict.update(self._message_parse_extract_bounce(message, msg_dict)) @@ -1996,6 +1996,7 @@ class MailThread(models.AbstractModel): 'email_from': email_from, 'partner_ids': partner_ids, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), + 'is_internal': True, 'record_name': False, 'reply_to': MailThread._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), @@ -2024,6 +2025,7 @@ class MailThread(models.AbstractModel): 'model': kwargs.get('model', self._name), 'res_id': self.ids[0] if self.ids else False, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), + 'is_internal': True, 'record_name': False, 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify @@ -2046,6 +2048,7 @@ class MailThread(models.AbstractModel): 'message_type': message_type, 'model': self._name, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), + 'is_internal': True, 'record_name': False, 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index eddf8cb32ce..1cbff66cde5 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -158,6 +158,7 @@ class PortalChatter(http.Controller): 'options': { 'message_count': message_data['message_count'], 'is_user_public': is_user_public, + 'is_user_employee': request.env.user.has_group('base.group_user'), 'is_user_publisher': request.env.user.has_group('website.group_website_publisher'), 'display_composer': display_composer, 'partner_id': request.env.user.partner_id.id @@ -183,13 +184,19 @@ class PortalChatter(http.Controller): raise Forbidden() # Non-employee see only messages with not internal subtype (aka, no internal logs) if not request.env['res.users'].has_group('base.group_user'): - domain = expression.AND([Message._non_employee_message_domain(), domain]) + domain = expression.AND([Message._get_search_domain_share(), domain]) Message = request.env['mail.message'].sudo() return { 'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(), 'message_count': Message.search_count(domain) } + @http.route(['/mail/update_is_internal'], type='json', auth="user", website=True) + def portal_message_update_is_internal(self, message_id, is_internal): + message = request.env['mail.message'].browse(int(message_id)) + message.write({'is_internal': is_internal}) + return message.is_internal + class MailController(MailController): diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py index 1d98ce7cf76..cfda6d5eaa0 100644 --- a/addons/portal/models/mail_message.py +++ b/addons/portal/models/mail_message.py @@ -10,7 +10,7 @@ class MailMessage(models.Model): def portal_message_format(self): return self._portal_message_format([ 'id', 'body', 'date', 'author_id', 'email_from', # base message fields - 'message_type', 'subtype_id', 'subject', # message specific + 'message_type', 'subtype_id', 'is_internal', 'subject', # message specific 'model', 'res_id', 'record_name', # document related ]) @@ -24,7 +24,3 @@ class MailMessage(models.Model): if not attachment.get('access_token'): attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0] return message_values - - @api.model - def _non_employee_message_domain(self): - return ['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js index 0b23ec2c386..8e5c0a3144f 100644 --- a/addons/portal/static/src/js/portal_chatter.js +++ b/addons/portal/static/src/js/portal_chatter.js @@ -20,7 +20,8 @@ var PortalChatter = publicWidget.Widget.extend({ template: 'portal.Chatter', xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], events: { - "click .o_portal_chatter_pager_btn": '_onClickPager', + 'click .o_portal_chatter_pager_btn': '_onClickPager', + 'click .o_portal_chatter_js_is_internal': 'async _onClickUpdateIsInternal', }, /** @@ -45,6 +46,7 @@ var PortalChatter = publicWidget.Widget.extend({ 'pager_scope': 5, 'pager_start': 1, 'is_user_public': true, + 'is_user_employee': false, 'is_user_publisher': false, 'hash': false, 'pid': false, @@ -254,6 +256,35 @@ var PortalChatter = publicWidget.Widget.extend({ var page = $(ev.currentTarget).data('page'); this._changeCurrentPage(page); }, + + /** + * Toggle is_internal state of message. Update both node data and + * classes to ensure DOM is updated accordingly to RPC call result. + * @private + * @returns {Promise} + */ + _onClickUpdateIsInternal: function (ev) { + ev.preventDefault(); + + var $elem = $(ev.currentTarget); + return this._rpc({ + route: '/mail/update_is_internal', + params: { + message_id: $elem.data('message-id'), + is_internal: ! $elem.data('is-internal'), + }, + }).then(function (result) { + $elem.data('is-internal', result); + if (result === true) { + $elem.addClass('o_portal_message_internal_on'); + $elem.removeClass('o_portal_message_internal_off'); + } + else { + $elem.addClass('o_portal_message_internal_off'); + $elem.removeClass('o_portal_message_internal_on'); + } + }); + }, }); publicWidget.registry.portalChatter = publicWidget.Widget.extend({ diff --git a/addons/portal/static/src/scss/portal.scss b/addons/portal/static/src/scss/portal.scss index 471dc7f9c8b..bd59bfc1419 100644 --- a/addons/portal/static/src/scss/portal.scss +++ b/addons/portal/static/src/scss/portal.scss @@ -524,4 +524,16 @@ form label { opacity: 1; } } + + .o_portal_message_internal_off { + .btn-danger { + display: none; + } + } + + .o_portal_message_internal_on { + .btn-success { + display: none; + } + } } diff --git a/addons/portal/static/src/xml/portal_chatter.xml b/addons/portal/static/src/xml/portal_chatter.xml index 520dc46eaa7..52a3d77c7bd 100644 --- a/addons/portal/static/src/xml/portal_chatter.xml +++ b/addons/portal/static/src/xml/portal_chatter.xml @@ -92,6 +92,7 @@