1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4import logging 5 6from odoo import _, api, fields, models, tools 7from odoo.addons.bus.models.bus_presence import AWAY_TIMER 8from odoo.addons.bus.models.bus_presence import DISCONNECTION_TIMER 9from odoo.exceptions import AccessError 10from odoo.osv import expression 11 12_logger = logging.getLogger(__name__) 13 14 15class Partner(models.Model): 16 """ Update partner to add a field about notification preferences. Add a generic opt-out field that can be used 17 to restrict usage of automatic email templates. """ 18 _name = "res.partner" 19 _inherit = ['res.partner', 'mail.activity.mixin', 'mail.thread.blacklist'] 20 _mail_flat_thread = False 21 22 email = fields.Char(tracking=1) 23 phone = fields.Char(tracking=2) 24 25 channel_ids = fields.Many2many('mail.channel', 'mail_channel_partner', 'partner_id', 'channel_id', string='Channels', copy=False) 26 # override the field to track the visibility of user 27 user_id = fields.Many2one(tracking=True) 28 29 def _compute_im_status(self): 30 super()._compute_im_status() 31 odoobot_id = self.env['ir.model.data'].xmlid_to_res_id('base.partner_root') 32 odoobot = self.env['res.partner'].browse(odoobot_id) 33 if odoobot in self: 34 odoobot.im_status = 'bot' 35 36 def _message_get_suggested_recipients(self): 37 recipients = super(Partner, self)._message_get_suggested_recipients() 38 for partner in self: 39 partner._message_add_suggested_recipient(recipients, partner=partner, reason=_('Partner Profile')) 40 return recipients 41 42 def _message_get_default_recipients(self): 43 return {r.id: { 44 'partner_ids': [r.id], 45 'email_to': False, 46 'email_cc': False} 47 for r in self} 48 49 @api.model 50 @api.returns('self', lambda value: value.id) 51 def find_or_create(self, email, assert_valid_email=False): 52 """ Override to use the email_normalized field. """ 53 if not email: 54 raise ValueError(_('An email is required for find_or_create to work')) 55 56 parsed_name, parsed_email = self._parse_partner_name(email) 57 if parsed_email: 58 email_normalized = tools.email_normalize(parsed_email) 59 if email_normalized: 60 partners = self.search([('email_normalized', '=', email_normalized)], limit=1) 61 if partners: 62 return partners 63 64 return super(Partner, self).find_or_create(email, assert_valid_email=assert_valid_email) 65 66 def mail_partner_format(self): 67 self.ensure_one() 68 internal_users = self.user_ids - self.user_ids.filtered('share') 69 main_user = internal_users[0] if len(internal_users) else self.user_ids[0] if len(self.user_ids) else self.env['res.users'] 70 res = { 71 "id": self.id, 72 "display_name": self.display_name, 73 "name": self.name, 74 "email": self.email, 75 "active": self.active, 76 "im_status": self.im_status, 77 "user_id": main_user.id, 78 } 79 if main_user: 80 res["is_internal_user"] = not main_user.share 81 return res 82 83 @api.model 84 def get_needaction_count(self): 85 """ compute the number of needaction of the current user """ 86 if self.env.user.partner_id: 87 self.env['mail.notification'].flush(['is_read', 'res_partner_id']) 88 self.env.cr.execute(""" 89 SELECT count(*) as needaction_count 90 FROM mail_message_res_partner_needaction_rel R 91 WHERE R.res_partner_id = %s AND (R.is_read = false OR R.is_read IS NULL)""", (self.env.user.partner_id.id,)) 92 return self.env.cr.dictfetchall()[0].get('needaction_count') 93 _logger.error('Call to needaction_count without partner_id') 94 return 0 95 96 @api.model 97 def get_starred_count(self): 98 """ compute the number of starred of the current user """ 99 if self.env.user.partner_id: 100 self.env.cr.execute(""" 101 SELECT count(*) as starred_count 102 FROM mail_message_res_partner_starred_rel R 103 WHERE R.res_partner_id = %s """, (self.env.user.partner_id.id,)) 104 return self.env.cr.dictfetchall()[0].get('starred_count') 105 _logger.error('Call to starred_count without partner_id') 106 return 0 107 108 @api.model 109 def get_static_mention_suggestions(self): 110 """Returns static mention suggestions of partners, loaded once at 111 webclient initialization and stored client side. 112 By default all the internal users are returned. 113 114 The return format is a list of lists. The first level of list is an 115 arbitrary split that allows overrides to return their own list. 116 The second level of list is a list of partner data (as per returned by 117 `mail_partner_format()`). 118 """ 119 suggestions = [] 120 try: 121 suggestions.append([partner.mail_partner_format() for partner in self.env.ref('base.group_user').users.partner_id]) 122 except AccessError: 123 pass 124 return suggestions 125 126 @api.model 127 def get_mention_suggestions(self, search, limit=8, channel_id=None): 128 """ Return 'limit'-first partners' id, name and email such that the name or email matches a 129 'search' string. Prioritize users, and then extend the research to all partners. 130 If channel_id is given, only members of this channel are returned. 131 """ 132 search_dom = expression.OR([[('name', 'ilike', search)], [('email', 'ilike', search)]]) 133 search_dom = expression.AND([[('active', '=', True), ('type', '!=', 'private')], search_dom]) 134 if channel_id: 135 search_dom = expression.AND([[('channel_ids', 'in', channel_id)], search_dom]) 136 137 # Search users 138 domain = expression.AND([[('user_ids.id', '!=', False), ('user_ids.active', '=', True)], search_dom]) 139 users = self.search(domain, limit=limit) 140 141 # Search partners if less than 'limit' users found 142 partners = self.env['res.partner'] 143 if len(users) < limit: 144 partners = self.search(expression.AND([[('id', 'not in', users.ids)], search_dom]), limit=limit) 145 146 return [ 147 [partner.mail_partner_format() for partner in users], 148 [partner.mail_partner_format() for partner in partners], 149 ] 150 151 @api.model 152 def im_search(self, name, limit=20): 153 """ Search partner with a name and return its id, name and im_status. 154 Note : the user must be logged 155 :param name : the partner name to search 156 :param limit : the limit of result to return 157 """ 158 # This method is supposed to be used only in the context of channel creation or 159 # extension via an invite. As both of these actions require the 'create' access 160 # right, we check this specific ACL. 161 if self.env['mail.channel'].check_access_rights('create', raise_exception=False): 162 name = '%' + name + '%' 163 excluded_partner_ids = [self.env.user.partner_id.id] 164 self.env.cr.execute(""" 165 SELECT 166 U.id as user_id, 167 P.id as id, 168 P.name as name, 169 CASE WHEN B.last_poll IS NULL THEN 'offline' 170 WHEN age(now() AT TIME ZONE 'UTC', B.last_poll) > interval %s THEN 'offline' 171 WHEN age(now() AT TIME ZONE 'UTC', B.last_presence) > interval %s THEN 'away' 172 ELSE 'online' 173 END as im_status 174 FROM res_users U 175 JOIN res_partner P ON P.id = U.partner_id 176 LEFT JOIN bus_presence B ON B.user_id = U.id 177 WHERE P.name ILIKE %s 178 AND P.id NOT IN %s 179 AND U.active = 't' 180 LIMIT %s 181 """, ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, name, tuple(excluded_partner_ids), limit)) 182 return self.env.cr.dictfetchall() 183 else: 184 return {} 185