diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/crm_iap_lead/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/crm_iap_lead/models')
| -rw-r--r-- | addons/crm_iap_lead/models/__init__.py | 6 | ||||
| -rw-r--r-- | addons/crm_iap_lead/models/crm_iap_lead.py | 50 | ||||
| -rw-r--r-- | addons/crm_iap_lead/models/crm_iap_lead_helpers.py | 70 | ||||
| -rw-r--r-- | addons/crm_iap_lead/models/crm_iap_lead_mining_request.py | 263 | ||||
| -rw-r--r-- | addons/crm_iap_lead/models/crm_lead.py | 10 |
5 files changed, 399 insertions, 0 deletions
diff --git a/addons/crm_iap_lead/models/__init__.py b/addons/crm_iap_lead/models/__init__.py new file mode 100644 index 00000000..d529ee24 --- /dev/null +++ b/addons/crm_iap_lead/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import crm_lead +from . import crm_iap_lead +from . import crm_iap_lead_helpers +from . import crm_iap_lead_mining_request diff --git a/addons/crm_iap_lead/models/crm_iap_lead.py b/addons/crm_iap_lead/models/crm_iap_lead.py new file mode 100644 index 00000000..1383027b --- /dev/null +++ b/addons/crm_iap_lead/models/crm_iap_lead.py @@ -0,0 +1,50 @@ +from odoo import api, fields, models + + +class IndustryTag(models.Model): + """ Industry Tags of Acquisition Rules """ + _name = 'crm.iap.lead.industry' + _description = 'Industry Tag' + + name = fields.Char(string='Tag Name', required=True, translate=True) + reveal_id = fields.Char(required=True) + color = fields.Integer(string='Color Index') + + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Tag name already exists!'), + ] + + +class PeopleRole(models.Model): + """ CRM Reveal People Roles for People """ + _name = 'crm.iap.lead.role' + _description = 'People Role' + + name = fields.Char(string='Role Name', required=True, translate=True) + reveal_id = fields.Char(required=True) + color = fields.Integer(string='Color Index') + + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Role name already exists!'), + ] + + @api.depends('name') + def name_get(self): + return [(role.id, role.name.replace('_', ' ').title()) for role in self] + + +class PeopleSeniority(models.Model): + """ Seniority for People Rules """ + _name = 'crm.iap.lead.seniority' + _description = 'People Seniority' + + name = fields.Char(string='Name', required=True, translate=True) + reveal_id = fields.Char(required=True) + + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Name already exists!'), + ] + + @api.depends('name') + def name_get(self): + return [(seniority.id, seniority.name.replace('_', ' ').title()) for seniority in self] diff --git a/addons/crm_iap_lead/models/crm_iap_lead_helpers.py b/addons/crm_iap_lead/models/crm_iap_lead_helpers.py new file mode 100644 index 00000000..5a2d8092 --- /dev/null +++ b/addons/crm_iap_lead/models/crm_iap_lead_helpers.py @@ -0,0 +1,70 @@ +from math import floor, log10 +from odoo import api, models + + +class CRMHelpers(models.Model): + _name = 'crm.iap.lead.helpers' + _description = 'Helper methods for crm_iap_lead modules' + + @api.model + def notify_no_more_credit(self, service_name, model_name, notification_parameter): + """ + Notify about the number of credit. + In order to avoid to spam people each hour, an ir.config_parameter is set + """ + already_notified = self.env['ir.config_parameter'].sudo().get_param(notification_parameter, False) + if already_notified: + return + mail_template = self.env.ref('crm_iap_lead.lead_generation_no_credits') + iap_account = self.env['iap.account'].search([('service_name', '=', service_name)], limit=1) + # Get the email address of the creators of the records + res = self.env[model_name].search_read([], ['create_uid']) + uids = set(r['create_uid'][0] for r in res if r.get('create_uid')) + res = self.env['res.users'].search_read([('id', 'in', list(uids))], ['email']) + emails = set(r['email'] for r in res if r.get('email')) + + email_values = { + 'email_to': ','.join(emails) + } + mail_template.send_mail(iap_account.id, force_send=True, email_values=email_values) + self.env['ir.config_parameter'].sudo().set_param(notification_parameter, True) + + @api.model + def lead_vals_from_response(self, lead_type, team_id, tag_ids, user_id, company_data, people_data): + country_id = self.env['res.country'].search([('code', '=', company_data['country_code'])]).id + website_url = 'https://www.%s' % company_data['domain'] if company_data['domain'] else False + lead_vals = { + # Lead vals from record itself + 'type': lead_type, + 'team_id': team_id, + 'tag_ids': [(6, 0, tag_ids)], + 'user_id': user_id, + 'reveal_id': company_data['clearbit_id'], + # Lead vals from data + 'name': company_data['name'] or company_data['domain'], + 'partner_name': company_data['legal_name'] or company_data['name'], + 'email_from': next(iter(company_data.get('email', [])), ''), + 'phone': company_data['phone'] or (company_data['phone_numbers'] and company_data['phone_numbers'][0]) or '', + 'website': website_url, + 'street': company_data['location'], + 'city': company_data['city'], + 'zip': company_data['postal_code'], + 'country_id': country_id, + 'state_id': self._find_state_id(company_data['state_code'], country_id), + } + + # If type is people then add first contact in lead data + if people_data: + lead_vals.update({ + 'contact_name': people_data[0]['full_name'], + 'email_from': people_data[0]['email'], + 'function': people_data[0]['title'], + }) + return lead_vals + + @api.model + def _find_state_id(self, state_code, country_id): + state_id = self.env['res.country.state'].search([('code', '=', state_code), ('country_id', '=', country_id)]) + if state_id: + return state_id.id + return False diff --git a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py new file mode 100644 index 00000000..ede89baa --- /dev/null +++ b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import api, fields, models, _ +from odoo.addons.iap.tools import iap_tools + +_logger = logging.getLogger(__name__) + +DEFAULT_ENDPOINT = 'https://iap-services.odoo.com' + +MAX_LEAD = 200 + +MAX_CONTACT = 5 + +CREDIT_PER_COMPANY = 1 +CREDIT_PER_CONTACT = 1 + + +class CRMLeadMiningRequest(models.Model): + _name = 'crm.iap.lead.mining.request' + _description = 'CRM Lead Mining Request' + + def _default_lead_type(self): + if self.env.user.has_group('crm.group_use_lead'): + return 'lead' + else: + return 'opportunity' + + def _default_country_ids(self): + return self.env.user.company_id.country_id + + name = fields.Char(string='Request Number', required=True, readonly=True, default=lambda self: _('New'), copy=False) + state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('error', 'Error')], string='Status', required=True, default='draft') + + # Request Data + lead_number = fields.Integer(string='Number of Leads', required=True, default=3) + search_type = fields.Selection([('companies', 'Companies'), ('people', 'Companies and their Contacts')], string='Target', required=True, default='companies') + error = fields.Text(string='Error', readonly=True) + + # Lead / Opportunity Data + + lead_type = fields.Selection([('lead', 'Leads'), ('opportunity', 'Opportunities')], string='Type', required=True, default=_default_lead_type) + display_lead_label = fields.Char(compute='_compute_display_lead_label') + team_id = fields.Many2one( + 'crm.team', string='Sales Team', + domain="[('use_opportunities', '=', True)]", readonly=False, compute='_compute_team_id', store=True) + user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user) + tag_ids = fields.Many2many('crm.tag', string='Tags') + lead_ids = fields.One2many('crm.lead', 'lead_mining_request_id', string='Generated Lead / Opportunity') + lead_count = fields.Integer(compute='_compute_lead_count', string='Number of Generated Leads') + + # Company Criteria Filter + filter_on_size = fields.Boolean(string='Filter on Size', default=False) + company_size_min = fields.Integer(string='Size', default=1) + company_size_max = fields.Integer(default=1000) + country_ids = fields.Many2many('res.country', string='Countries', default=_default_country_ids) + state_ids = fields.Many2many('res.country.state', string='States') + industry_ids = fields.Many2many('crm.iap.lead.industry', string='Industries') + + # Contact Generation Filter + contact_number = fields.Integer(string='Number of Contacts', default=10) + contact_filter_type = fields.Selection([('role', 'Role'), ('seniority', 'Seniority')], string='Filter on', default='role') + preferred_role_id = fields.Many2one('crm.iap.lead.role', string='Preferred Role') + role_ids = fields.Many2many('crm.iap.lead.role', string='Other Roles') + seniority_id = fields.Many2one('crm.iap.lead.seniority', string='Seniority') + + # Fields for the blue tooltip + lead_credits = fields.Char(compute='_compute_tooltip', readonly=True) + lead_contacts_credits = fields.Char(compute='_compute_tooltip', readonly=True) + lead_total_credits = fields.Char(compute='_compute_tooltip', readonly=True) + + @api.depends('lead_type', 'lead_number') + def _compute_display_lead_label(self): + selection_description_values = { + e[0]: e[1] for e in self._fields['lead_type']._description_selection(self.env)} + for request in self: + lead_type = selection_description_values[request.lead_type] + request.display_lead_label = '%s %s' % (request.lead_number, lead_type) + + + @api.onchange('lead_number', 'contact_number') + def _compute_tooltip(self): + for record in self: + company_credits = CREDIT_PER_COMPANY * record.lead_number + contact_credits = CREDIT_PER_CONTACT * record.contact_number + total_contact_credits = contact_credits * record.lead_number + 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) + record.lead_credits = _('%d credits will be consumed to find %d companies.') % (company_credits, record.lead_number) + record.lead_total_credits = _("This makes a total of %d credits for this request.") % (total_contact_credits + company_credits) + + @api.depends('lead_ids.lead_mining_request_id') + def _compute_lead_count(self): + if self.ids: + leads_data = self.env['crm.lead'].read_group( + [('lead_mining_request_id', 'in', self.ids)], + ['lead_mining_request_id'], ['lead_mining_request_id']) + else: + leads_data = [] + mapped_data = dict( + (m['lead_mining_request_id'][0], m['lead_mining_request_id_count']) + for m in leads_data) + for request in self: + request.lead_count = mapped_data.get(request.id, 0) + + @api.depends('user_id') + def _compute_team_id(self): + for record in self: + record.team_id = record.user_id.sale_team_id + + @api.onchange('lead_number') + def _onchange_lead_number(self): + if self.lead_number <= 0: + self.lead_number = 1 + elif self.lead_number > MAX_LEAD: + self.lead_number = MAX_LEAD + + @api.onchange('contact_number') + def _onchange_contact_number(self): + if self.contact_number <= 0: + self.contact_number = 1 + elif self.contact_number > MAX_CONTACT: + self.contact_number = MAX_CONTACT + + @api.onchange('country_ids') + def _onchange_country_ids(self): + self.state_ids = [] + + @api.onchange('company_size_min') + def _onchange_company_size_min(self): + if self.company_size_min <= 0: + self.company_size_min = 1 + elif self.company_size_min > self.company_size_max: + self.company_size_min = self.company_size_max + + @api.onchange('company_size_max') + def _onchange_company_size_max(self): + if self.company_size_max < self.company_size_min: + self.company_size_max = self.company_size_min + + def _prepare_iap_payload(self): + """ + This will prepare the data to send to the server + """ + self.ensure_one() + payload = {'lead_number': self.lead_number, + 'search_type': self.search_type, + 'countries': self.country_ids.mapped('code')} + if self.state_ids: + payload['states'] = self.state_ids.mapped('code') + if self.filter_on_size: + payload.update({'company_size_min': self.company_size_min, + 'company_size_max': self.company_size_max}) + if self.industry_ids: + payload['industry_ids'] = self.industry_ids.mapped('reveal_id') + if self.search_type == 'people': + payload.update({'contact_number': self.contact_number, + 'contact_filter_type': self.contact_filter_type}) + if self.contact_filter_type == 'role': + payload.update({'preferred_role': self.preferred_role_id.reveal_id, + 'other_roles': self.role_ids.mapped('reveal_id')}) + elif self.contact_filter_type == 'seniority': + payload['seniority'] = self.seniority_id.reveal_id + return payload + + def _perform_request(self): + """ + This will perform the request and create the corresponding leads. + The user will be notified if he hasn't enough credits. + """ + server_payload = self._prepare_iap_payload() + reveal_account = self.env['iap.account'].get('reveal') + dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid') + endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request' + params = { + 'account_token': reveal_account.account_token, + 'dbuuid': dbuuid, + 'data': server_payload + } + try: + response = iap_tools.iap_jsonrpc(endpoint, params=params, timeout=300) + return response['data'] + except iap_tools.InsufficientCreditError as e: + self.error = 'Insufficient credits. Recharge your account and retry.' + self.state = 'error' + self._cr.commit() + raise e + + def _create_leads_from_response(self, result): + """ This method will get the response from the service and create the leads accordingly """ + self.ensure_one() + lead_vals_list = [] + messages_to_post = {} + for data in result: + lead_vals_list.append(self._lead_vals_from_response(data)) + + template_values = data['company_data'] + template_values.update({ + 'flavor_text': _("Opportunity created by Odoo Lead Generation"), + 'people_data': data.get('people_data'), + }) + messages_to_post[data['company_data']['clearbit_id']] = template_values + leads = self.env['crm.lead'].create(lead_vals_list) + for lead in leads: + if messages_to_post.get(lead.reveal_id): + 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) + + # Methods responsible for format response data into valid odoo lead data + @api.model + def _lead_vals_from_response(self, data): + self.ensure_one() + company_data = data.get('company_data') + people_data = data.get('people_data') + 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) + lead_vals['lead_mining_request_id'] = self.id + return lead_vals + + @api.model + def get_empty_list_help(self, help): + help_title = _('Create a Lead Mining Request') + sub_title = _('Generate new leads based on their country, industry, size, etc.') + return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (help_title, sub_title) + + def action_draft(self): + self.ensure_one() + self.name = _('New') + self.state = 'draft' + + def action_submit(self): + self.ensure_one() + if self.name == _('New'): + self.name = self.env['ir.sequence'].next_by_code('crm.iap.lead.mining.request') or _('New') + results = self._perform_request() + if results: + self._create_leads_from_response(results) + self.state = 'done' + if self.lead_type == 'lead': + return self.action_get_lead_action() + elif self.lead_type == 'opportunity': + return self.action_get_opportunity_action() + + def action_get_lead_action(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_all_leads") + action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'lead')] + action['help'] = _("""<p class="o_view_nocontent_empty_folder"> + No leads found + </p><p> + No leads could be generated according to your search criteria + </p>""") + return action + + def action_get_opportunity_action(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_opportunities") + action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'opportunity')] + action['help'] = _("""<p class="o_view_nocontent_empty_folder"> + No opportunities found + </p><p> + No opportunities could be generated according to your search criteria + </p>""") + return action diff --git a/addons/crm_iap_lead/models/crm_lead.py b/addons/crm_iap_lead/models/crm_lead.py new file mode 100644 index 00000000..eaed1d92 --- /dev/null +++ b/addons/crm_iap_lead/models/crm_lead.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class Lead(models.Model): + _inherit = 'crm.lead' + + lead_mining_request_id = fields.Many2one('crm.iap.lead.mining.request', string='Lead Mining Request', index=True) |
