summaryrefslogtreecommitdiff
path: root/addons/crm_iap_lead/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/crm_iap_lead/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/crm_iap_lead/models')
-rw-r--r--addons/crm_iap_lead/models/__init__.py6
-rw-r--r--addons/crm_iap_lead/models/crm_iap_lead.py50
-rw-r--r--addons/crm_iap_lead/models/crm_iap_lead_helpers.py70
-rw-r--r--addons/crm_iap_lead/models/crm_iap_lead_mining_request.py263
-rw-r--r--addons/crm_iap_lead/models/crm_lead.py10
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)