summaryrefslogtreecommitdiff
path: root/addons/crm_iap_lead_enrich/models
diff options
context:
space:
mode:
Diffstat (limited to 'addons/crm_iap_lead_enrich/models')
-rw-r--r--addons/crm_iap_lead_enrich/models/__init__.py6
-rw-r--r--addons/crm_iap_lead_enrich/models/crm_lead.py152
-rw-r--r--addons/crm_iap_lead_enrich/models/iap_enrich_api.py39
-rw-r--r--addons/crm_iap_lead_enrich/models/res_config_settings.py16
4 files changed, 213 insertions, 0 deletions
diff --git a/addons/crm_iap_lead_enrich/models/__init__.py b/addons/crm_iap_lead_enrich/models/__init__.py
new file mode 100644
index 00000000..dcb5aa3d
--- /dev/null
+++ b/addons/crm_iap_lead_enrich/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 iap_enrich_api
+from . import res_config_settings
diff --git a/addons/crm_iap_lead_enrich/models/crm_lead.py b/addons/crm_iap_lead_enrich/models/crm_lead.py
new file mode 100644
index 00000000..a5b865ba
--- /dev/null
+++ b/addons/crm_iap_lead_enrich/models/crm_lead.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import datetime
+import logging
+from psycopg2 import OperationalError
+
+
+from odoo import _, api, fields, models, tools
+from odoo.addons.iap.tools import iap_tools
+
+_logger = logging.getLogger(__name__)
+
+EMAIL_PROVIDERS = ['gmail.com', 'hotmail.com', 'yahoo.com', 'qq.com',
+ 'outlook.com', '163.com', 'yahoo.fr', 'live.com',
+ 'hotmail.fr', 'icloud.com', '126.com', 'me.com',
+ 'free.fr', 'ymail.com', 'msn.com', 'mail.com']
+
+class Lead(models.Model):
+ _inherit = 'crm.lead'
+
+ iap_enrich_done = fields.Boolean(string='Enrichment done', help='Whether IAP service for lead enrichment based on email has been performed on this lead.')
+ show_enrich_button = fields.Boolean(string='Allow manual enrich', compute="_compute_show_enrich_button")
+
+ @api.depends('email_from', 'probability', 'iap_enrich_done', 'reveal_id')
+ def _compute_show_enrich_button(self):
+ config = self.env['ir.config_parameter'].sudo().get_param('crm.iap.lead.enrich.setting', 'manual')
+ if not config or config != 'manual':
+ self.show_enrich_button = False
+ return
+ for lead in self:
+ if not lead.active or not lead.email_from or lead.iap_enrich_done or lead.reveal_id or lead.probability == 100:
+ lead.show_enrich_button = False
+ else:
+ lead.show_enrich_button = True
+
+ @api.model
+ def _iap_enrich_leads_cron(self):
+ timeDelta = fields.datetime.now() - datetime.timedelta(hours=1)
+ # Get all leads not lost nor won (lost: active = False)
+ leads = self.search([
+ ('iap_enrich_done', '=', False),
+ ('reveal_id', '=', False),
+ ('probability', '<', 100),
+ ('create_date', '>', timeDelta)
+ ])
+ leads.iap_enrich(from_cron=True)
+
+ def iap_enrich(self, from_cron=False):
+ # Split self in a list of sub-recordsets or 50 records to prevent timeouts
+ batches = [self[index:index + 50] for index in range(0, len(self), 50)]
+ for leads in batches:
+ lead_emails = {}
+ with self._cr.savepoint():
+ try:
+ self._cr.execute(
+ "SELECT 1 FROM {} WHERE id in %(lead_ids)s FOR UPDATE NOWAIT".format(self._table),
+ {'lead_ids': tuple(leads.ids)}, log_exceptions=False)
+ for lead in leads:
+ # If lead is lost, active == False, but is anyway removed from the search in the cron.
+ if lead.probability == 100 or lead.iap_enrich_done:
+ continue
+
+ normalized_email = tools.email_normalize(lead.email_from)
+ if not normalized_email:
+ lead.message_post_with_view(
+ 'crm_iap_lead_enrich.mail_message_lead_enrich_no_email',
+ subtype_id=self.env.ref('mail.mt_note').id)
+ continue
+
+ email_domain = normalized_email.split('@')[1]
+ # Discard domains of generic email providers as it won't return relevant information
+ if email_domain in EMAIL_PROVIDERS:
+ lead.write({'iap_enrich_done': True})
+ lead.message_post_with_view(
+ 'crm_iap_lead_enrich.mail_message_lead_enrich_notfound',
+ subtype_id=self.env.ref('mail.mt_note').id)
+ else:
+ lead_emails[lead.id] = email_domain
+
+ if lead_emails:
+ try:
+ iap_response = self.env['iap.enrich.api']._request_enrich(lead_emails)
+ except iap_tools.InsufficientCreditError:
+ _logger.info('Sent batch %s enrich requests: failed because of credit', len(lead_emails))
+ if not from_cron:
+ data = {
+ 'url': self.env['iap.account'].get_credits_url('reveal'),
+ }
+ leads[0].message_post_with_view(
+ 'crm_iap_lead_enrich.mail_message_lead_enrich_no_credit',
+ values=data,
+ subtype_id=self.env.ref('mail.mt_note').id)
+ # Since there are no credits left, there is no point to process the other batches
+ break
+ except Exception as e:
+ _logger.info('Sent batch %s enrich requests: failed with exception %s', len(lead_emails), e)
+ else:
+ _logger.info('Sent batch %s enrich requests: success', len(lead_emails))
+ self._iap_enrich_from_response(iap_response)
+ except OperationalError:
+ _logger.error('A batch of leads could not be enriched :%s', repr(leads))
+ continue
+ # Commit processed batch to avoid complete rollbacks and therefore losing credits.
+ if not self.env.registry.in_test_mode():
+ self.env.cr.commit()
+
+ @api.model
+ def _iap_enrich_from_response(self, iap_response):
+ """ Handle from the service and enrich the lead accordingly
+
+ :param iap_response: dict{lead_id: company data or False}
+ """
+ for lead in self.search([('id', 'in', list(iap_response.keys()))]): # handle unlinked data by performing a search
+ iap_data = iap_response.get(str(lead.id))
+ if not iap_data:
+ lead.write({'iap_enrich_done': True})
+ lead.message_post_with_view('crm_iap_lead_enrich.mail_message_lead_enrich_notfound', subtype_id=self.env.ref('mail.mt_note').id)
+ continue
+
+ values = {'iap_enrich_done': True}
+ lead_fields = ['partner_name', 'reveal_id', 'street', 'city', 'zip']
+ iap_fields = ['name', 'clearbit_id', 'location', 'city', 'postal_code']
+ for lead_field, iap_field in zip(lead_fields, iap_fields):
+ if not lead[lead_field] and iap_data.get(iap_field):
+ values[lead_field] = iap_data[iap_field]
+
+ if not lead.phone and iap_data.get('phone_numbers'):
+ values['phone'] = iap_data['phone_numbers'][0]
+ if not lead.mobile and iap_data.get('phone_numbers') and len(iap_data['phone_numbers']) > 1:
+ values['mobile'] = iap_data['phone_numbers'][1]
+ if not lead.country_id and iap_data.get('country_code'):
+ country = self.env['res.country'].search([('code', '=', iap_data['country_code'].upper())])
+ values['country_id'] = country.id
+ else:
+ country = lead.country_id
+ if not lead.state_id and country and iap_data.get('state_code'):
+ state = self.env['res.country.state'].search([
+ ('code', '=', iap_data['state_code']),
+ ('country_id', '=', country.id)
+ ])
+ values['state_id'] = state.id
+
+ lead.write(values)
+
+ template_values = iap_data
+ template_values['flavor_text'] = _("Lead enriched based on email address")
+ lead.message_post_with_view(
+ 'iap_mail.enrich_company',
+ values=template_values,
+ subtype_id=self.env.ref('mail.mt_note').id
+ )
diff --git a/addons/crm_iap_lead_enrich/models/iap_enrich_api.py b/addons/crm_iap_lead_enrich/models/iap_enrich_api.py
new file mode 100644
index 00000000..f6a85d28
--- /dev/null
+++ b/addons/crm_iap_lead_enrich/models/iap_enrich_api.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, api
+from odoo.addons.iap.tools import iap_tools
+
+
+class IapEnrichAPI(models.AbstractModel):
+ _name = 'iap.enrich.api'
+ _description = 'IAP Lead Enrichment API'
+ _DEFAULT_ENDPOINT = 'https://iap-services.odoo.com'
+
+ @api.model
+ def _contact_iap(self, local_endpoint, params):
+ account = self.env['iap.account'].get('reveal')
+ dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
+ params['account_token'] = account.account_token
+ params['dbuuid'] = dbuuid
+ base_url = self.env['ir.config_parameter'].sudo().get_param('enrich.endpoint', self._DEFAULT_ENDPOINT)
+ return iap_tools.iap_jsonrpc(base_url + local_endpoint, params=params, timeout=300)
+
+ @api.model
+ def _request_enrich(self, lead_emails):
+ """ Contact endpoint to get enrichment data.
+
+ :param lead_emails: dict{lead_id: email}
+ :return: dict{lead_id: company data or False}
+ :raise: several errors, notably
+ * InsufficientCreditError: {
+ "credit": 4.0,
+ "service_name": "reveal",
+ "base_url": "https://iap.odoo.com/iap/1/credit",
+ "message": "You don't have enough credits on your account to use this service."
+ }
+ """
+ params = {
+ 'domains': lead_emails,
+ }
+ return self._contact_iap('/iap/clearbit/1/lead_enrichment_email', params=params)
diff --git a/addons/crm_iap_lead_enrich/models/res_config_settings.py b/addons/crm_iap_lead_enrich/models/res_config_settings.py
new file mode 100644
index 00000000..4f9d559d
--- /dev/null
+++ b/addons/crm_iap_lead_enrich/models/res_config_settings.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = "res.config.settings"
+
+ @api.onchange('lead_enrich_auto')
+ def _onchange_cron_lead_enrich(self):
+ """ change the active status of the cron according to the settings"""
+ if self.module_crm_iap_lead_enrich == True:
+ cron = self.sudo().with_context(active_test=False).env.ref('crm_iap_lead_enrich.ir_cron_lead_enrichment')
+ if cron:
+ cron.active = self.lead_enrich_auto != 'manual'