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