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