diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml
index 8b4c4ddcc65..8afde76abfd 100644
--- a/addons/mail/data/mail_data.xml
+++ b/addons/mail/data/mail_data.xml
@@ -68,5 +68,11 @@
Discussions
+
+ Note
+
+
+
+
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 7e191f9517d..44c0e254776 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -541,7 +541,7 @@ class Message(models.Model):
count=count, access_rights_uid=access_rights_uid)
# Non-employee see only messages with a subtype (aka, no internal logs)
if not self.env['res.users'].has_group('base.group_user'):
- args = ['&', ('subtype_id', '!=', False)] + list(args)
+ args = ['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] + list(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,
@@ -620,7 +620,11 @@ class Message(models.Model):
return
# Non employees see only messages with a subtype (aka, not internal logs)
if not self.env['res.users'].has_group('base.group_user'):
- self._cr.execute('SELECT DISTINCT id FROM "%s" WHERE message_type = %%s AND subtype_id IS NULL AND id = ANY (%%s)' % (self._table), ('comment', self.ids,))
+ self._cr.execute('''SELECT DISTINCT message.id, message.subtype_id, subtype.internal
+ 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,))
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)') %
@@ -783,10 +787,16 @@ class Message(models.Model):
self.ensure_one() # tde: not sure, just for testinh, will see
partners_to_notify = self.env['res.partner']
- # all followers of the mail.message document have to be added as partners and notified if a subtype is defined (otherwise: log message)
+ # all followers of the mail.message document have to be added as partners and notified
+ # and filter to employees only if the subtype is internal
if self.subtype_id and self.model and self.res_id:
- followers = self.env['mail.followers'].sudo().search([('res_model', '=', self.model), ('res_id', '=', self.res_id)])
- partners_to_notify |= followers.filtered(lambda fol: self.subtype_id in fol.subtype_ids).mapped('partner_id')
+ followers = self.env['mail.followers'].sudo().search([
+ ('res_model', '=', self.model),
+ ('res_id', '=', self.res_id)
+ ]).filtered(lambda fol: self.subtype_id in fol.subtype_ids)
+ if self.subtype_id.internal:
+ followers.filtered(lambda fol: fol.partner_id.user_ids and fol.partner_id.user_ids[0].has_group('base.group_user'))
+ partners_to_notify |= followers.mapped('partner_id')
# remove me from notified partners, unless the message is written on my own wall
if self.subtype_id and self.author_id and self.model == "res.partner" and self.res_id == self.author_id.id:
diff --git a/addons/mail/models/mail_message_subtype.py b/addons/mail/models/mail_message_subtype.py
index 26faa0a3e12..83d3f082159 100644
--- a/addons/mail/models/mail_message_subtype.py
+++ b/addons/mail/models/mail_message_subtype.py
@@ -22,9 +22,14 @@ class MailMessageSubtype(models.Model):
'Description', translate=True,
help='Description that will be added in the message posted for this '
'subtype. If void, the name will be added instead.')
+ internal = fields.Boolean(
+ 'Internal Only',
+ help='Messages with internal subtypes will be visible only by employees, aka members of base_user group')
parent_id = fields.Many2one(
'mail.message.subtype', string='Parent', ondelete='set null',
- help='Parent subtype, used for automatic subscription.')
+ help='Parent subtype, used for automatic subscription. This field is not'
+ 'correctly named. For example on a project, project subtypes have'
+ 'parent_id to task-related subtypes.')
relation_field = fields.Char(
'Relation field',
help='Field used to link the related model to the subtype model when '
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 5b9755cafc8..a91b7c18768 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -336,8 +336,17 @@ class MailThread(models.AbstractModel):
if view_type == 'form':
doc = etree.XML(res['arch'])
for node in doc.xpath("//field[@name='message_ids']"):
+ # the 'Log a note' button is employee only
options = json.loads(node.get('options', '{}'))
- options['display_log_button'] = self.env.user.sudo().has_group('base.group_user')
+ is_employee = self.env.user.has_group('base.group_user')
+ options['display_log_button'] = is_employee
+ if is_employee:
+ Subtype = self.env['mail.message.subtype'].sudo()
+ # fetch internal subtypes
+ internal_subtypes = Subtype.search_read([
+ ('res_model', 'in', [False, self._name]),
+ ('internal', '=', True)], ['name', 'description', 'sequence'])
+ options['internal_subtypes'] = internal_subtypes
node.set('options', json.dumps(options))
res['arch'] = etree.tostring(doc)
return res
@@ -1552,8 +1561,9 @@ class MailThread(models.AbstractModel):
attachments, kwargs.pop('attachment_ids', []), model, self.ids and self.ids[0] or None)
# 4: mail.message.subtype
- subtype_id = False
- if subtype:
+ subtype_id = kwargs.get('subtype_id', False)
+ if not subtype_id:
+ subtype = subtype or 'mt_note'
if '.' not in subtype:
subtype = 'mail.%s' % subtype
subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype)
@@ -1607,8 +1617,10 @@ class MailThread(models.AbstractModel):
# Post-process: subscribe author, update message_last_post
if model and model != 'mail.thread' and self.ids and subtype_id:
- # done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access
- self.sudo().write({'message_last_post': fields.datetime.now()})
+ subtype_rec = self.env['mail.message.subtype'].sudo().browse(subtype_id)
+ if not subtype_rec.internal:
+ # done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access
+ self.sudo().write({'message_last_post': fields.Datetime.now()})
if new_message.author_id and model and self.ids and message_type != 'notification' and not self._context.get('mail_create_nosubscribe'):
self.message_subscribe([new_message.author_id.id])
return new_message
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index c8f90dcaeac..c66045d6fe8 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -20,7 +20,7 @@ access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
-access_mail_message_subtype_system,mail.message.subtype.system,model_mail_message_subtype,base.group_system,1,1,1,1
+access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,,1,1,1,1
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,1,0,0,0
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,1,1,1,1
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,1,1,1,1
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index 5384e2aba95..2f07c659c95 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -38,6 +38,13 @@
+
+ mail.message.subtype: portal/public: read public subtypes
+
+ [('internal', '=', False)]
+
+
+