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, _
7from odoo.addons.iap.tools import iap_tools
8
9_logger = logging.getLogger(__name__)
10
11DEFAULT_ENDPOINT = 'https://iap-services.odoo.com'
12
13MAX_LEAD = 200
14
15MAX_CONTACT = 5
16
17CREDIT_PER_COMPANY = 1
18CREDIT_PER_CONTACT = 1
19
20
21class CRMLeadMiningRequest(models.Model):
22    _name = 'crm.iap.lead.mining.request'
23    _description = 'CRM Lead Mining Request'
24
25    def _default_lead_type(self):
26        if self.env.user.has_group('crm.group_use_lead'):
27            return 'lead'
28        else:
29            return 'opportunity'
30
31    def _default_country_ids(self):
32        return self.env.user.company_id.country_id
33
34    name = fields.Char(string='Request Number', required=True, readonly=True, default=lambda self: _('New'), copy=False)
35    state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('error', 'Error')], string='Status', required=True, default='draft')
36
37    # Request Data
38    lead_number = fields.Integer(string='Number of Leads', required=True, default=3)
39    search_type = fields.Selection([('companies', 'Companies'), ('people', 'Companies and their Contacts')], string='Target', required=True, default='companies')
40    error = fields.Text(string='Error', readonly=True)
41
42    # Lead / Opportunity Data
43
44    lead_type = fields.Selection([('lead', 'Leads'), ('opportunity', 'Opportunities')], string='Type', required=True, default=_default_lead_type)
45    display_lead_label = fields.Char(compute='_compute_display_lead_label')
46    team_id = fields.Many2one(
47        'crm.team', string='Sales Team',
48        domain="[('use_opportunities', '=', True)]", readonly=False, compute='_compute_team_id', store=True)
49    user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user)
50    tag_ids = fields.Many2many('crm.tag', string='Tags')
51    lead_ids = fields.One2many('crm.lead', 'lead_mining_request_id', string='Generated Lead / Opportunity')
52    lead_count = fields.Integer(compute='_compute_lead_count', string='Number of Generated Leads')
53
54    # Company Criteria Filter
55    filter_on_size = fields.Boolean(string='Filter on Size', default=False)
56    company_size_min = fields.Integer(string='Size', default=1)
57    company_size_max = fields.Integer(default=1000)
58    country_ids = fields.Many2many('res.country', string='Countries', default=_default_country_ids)
59    state_ids = fields.Many2many('res.country.state', string='States')
60    industry_ids = fields.Many2many('crm.iap.lead.industry', string='Industries')
61
62    # Contact Generation Filter
63    contact_number = fields.Integer(string='Number of Contacts', default=10)
64    contact_filter_type = fields.Selection([('role', 'Role'), ('seniority', 'Seniority')], string='Filter on', default='role')
65    preferred_role_id = fields.Many2one('crm.iap.lead.role', string='Preferred Role')
66    role_ids = fields.Many2many('crm.iap.lead.role', string='Other Roles')
67    seniority_id = fields.Many2one('crm.iap.lead.seniority', string='Seniority')
68
69    # Fields for the blue tooltip
70    lead_credits = fields.Char(compute='_compute_tooltip', readonly=True)
71    lead_contacts_credits = fields.Char(compute='_compute_tooltip', readonly=True)
72    lead_total_credits = fields.Char(compute='_compute_tooltip', readonly=True)
73
74    @api.depends('lead_type', 'lead_number')
75    def _compute_display_lead_label(self):
76        selection_description_values = {
77            e[0]: e[1] for e in self._fields['lead_type']._description_selection(self.env)}
78        for request in self:
79            lead_type = selection_description_values[request.lead_type]
80            request.display_lead_label = '%s %s' % (request.lead_number, lead_type)
81
82
83    @api.onchange('lead_number', 'contact_number')
84    def _compute_tooltip(self):
85        for record in self:
86            company_credits = CREDIT_PER_COMPANY * record.lead_number
87            contact_credits = CREDIT_PER_CONTACT * record.contact_number
88            total_contact_credits = contact_credits * record.lead_number
89            record.lead_contacts_credits = _("Up to %d additional credits will be consumed to identify %d contacts per company.") % (contact_credits*company_credits, record.contact_number)
90            record.lead_credits = _('%d credits will be consumed to find %d companies.') % (company_credits, record.lead_number)
91            record.lead_total_credits = _("This makes a total of %d credits for this request.") % (total_contact_credits + company_credits)
92
93    @api.depends('lead_ids.lead_mining_request_id')
94    def _compute_lead_count(self):
95        if self.ids:
96            leads_data = self.env['crm.lead'].read_group(
97                [('lead_mining_request_id', 'in', self.ids)],
98                ['lead_mining_request_id'], ['lead_mining_request_id'])
99        else:
100            leads_data = []
101        mapped_data = dict(
102            (m['lead_mining_request_id'][0], m['lead_mining_request_id_count'])
103            for m in leads_data)
104        for request in self:
105            request.lead_count = mapped_data.get(request.id, 0)
106
107    @api.depends('user_id')
108    def _compute_team_id(self):
109        for record in self:
110            record.team_id = record.user_id.sale_team_id
111
112    @api.onchange('lead_number')
113    def _onchange_lead_number(self):
114        if self.lead_number <= 0:
115            self.lead_number = 1
116        elif self.lead_number > MAX_LEAD:
117            self.lead_number = MAX_LEAD
118
119    @api.onchange('contact_number')
120    def _onchange_contact_number(self):
121        if self.contact_number <= 0:
122            self.contact_number = 1
123        elif self.contact_number > MAX_CONTACT:
124            self.contact_number = MAX_CONTACT
125
126    @api.onchange('country_ids')
127    def _onchange_country_ids(self):
128        self.state_ids = []
129
130    @api.onchange('company_size_min')
131    def _onchange_company_size_min(self):
132        if self.company_size_min <= 0:
133            self.company_size_min = 1
134        elif self.company_size_min > self.company_size_max:
135            self.company_size_min = self.company_size_max
136
137    @api.onchange('company_size_max')
138    def _onchange_company_size_max(self):
139        if self.company_size_max < self.company_size_min:
140            self.company_size_max = self.company_size_min
141
142    def _prepare_iap_payload(self):
143        """
144        This will prepare the data to send to the server
145        """
146        self.ensure_one()
147        payload = {'lead_number': self.lead_number,
148                   'search_type': self.search_type,
149                   'countries': self.country_ids.mapped('code')}
150        if self.state_ids:
151            payload['states'] = self.state_ids.mapped('code')
152        if self.filter_on_size:
153            payload.update({'company_size_min': self.company_size_min,
154                            'company_size_max': self.company_size_max})
155        if self.industry_ids:
156            payload['industry_ids'] = self.industry_ids.mapped('reveal_id')
157        if self.search_type == 'people':
158            payload.update({'contact_number': self.contact_number,
159                            'contact_filter_type': self.contact_filter_type})
160            if self.contact_filter_type == 'role':
161                payload.update({'preferred_role': self.preferred_role_id.reveal_id,
162                                'other_roles': self.role_ids.mapped('reveal_id')})
163            elif self.contact_filter_type == 'seniority':
164                payload['seniority'] = self.seniority_id.reveal_id
165        return payload
166
167    def _perform_request(self):
168        """
169        This will perform the request and create the corresponding leads.
170        The user will be notified if he hasn't enough credits.
171        """
172        server_payload = self._prepare_iap_payload()
173        reveal_account = self.env['iap.account'].get('reveal')
174        dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
175        endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request'
176        params = {
177            'account_token': reveal_account.account_token,
178            'dbuuid': dbuuid,
179            'data': server_payload
180        }
181        try:
182            response = iap_tools.iap_jsonrpc(endpoint, params=params, timeout=300)
183            return response['data']
184        except iap_tools.InsufficientCreditError as e:
185            self.error = 'Insufficient credits. Recharge your account and retry.'
186            self.state = 'error'
187            self._cr.commit()
188            raise e
189
190    def _create_leads_from_response(self, result):
191        """ This method will get the response from the service and create the leads accordingly """
192        self.ensure_one()
193        lead_vals_list = []
194        messages_to_post = {}
195        for data in result:
196            lead_vals_list.append(self._lead_vals_from_response(data))
197
198            template_values = data['company_data']
199            template_values.update({
200                'flavor_text': _("Opportunity created by Odoo Lead Generation"),
201                'people_data': data.get('people_data'),
202            })
203            messages_to_post[data['company_data']['clearbit_id']] = template_values
204        leads = self.env['crm.lead'].create(lead_vals_list)
205        for lead in leads:
206            if messages_to_post.get(lead.reveal_id):
207                lead.message_post_with_view('iap_mail.enrich_company', values=messages_to_post[lead.reveal_id], subtype_id=self.env.ref('mail.mt_note').id)
208
209    # Methods responsible for format response data into valid odoo lead data
210    @api.model
211    def _lead_vals_from_response(self, data):
212        self.ensure_one()
213        company_data = data.get('company_data')
214        people_data = data.get('people_data')
215        lead_vals = self.env['crm.iap.lead.helpers'].lead_vals_from_response(self.lead_type, self.team_id.id, self.tag_ids.ids, self.user_id.id, company_data, people_data)
216        lead_vals['lead_mining_request_id'] = self.id
217        return lead_vals
218
219    @api.model
220    def get_empty_list_help(self, help):
221        help_title = _('Create a Lead Mining Request')
222        sub_title = _('Generate new leads based on their country, industry, size, etc.')
223        return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (help_title, sub_title)
224
225    def action_draft(self):
226        self.ensure_one()
227        self.name = _('New')
228        self.state = 'draft'
229
230    def action_submit(self):
231        self.ensure_one()
232        if self.name == _('New'):
233            self.name = self.env['ir.sequence'].next_by_code('crm.iap.lead.mining.request') or _('New')
234        results = self._perform_request()
235        if results:
236            self._create_leads_from_response(results)
237            self.state = 'done'
238        if self.lead_type == 'lead':
239            return self.action_get_lead_action()
240        elif self.lead_type == 'opportunity':
241            return self.action_get_opportunity_action()
242
243    def action_get_lead_action(self):
244        self.ensure_one()
245        action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_all_leads")
246        action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'lead')]
247        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
248            No leads found
249        </p><p>
250            No leads could be generated according to your search criteria
251        </p>""")
252        return action
253
254    def action_get_opportunity_action(self):
255        self.ensure_one()
256        action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_opportunities")
257        action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'opportunity')]
258        action['help'] = _("""<p class="o_view_nocontent_empty_folder">
259            No opportunities found
260        </p><p>
261            No opportunities could be generated according to your search criteria
262        </p>""")
263        return action
264