[MERGE][ADD] mail_(plugin/client_extension), crm_plugin: be more modular, support multi-contacts / multi-records
PURPOSE
Improve mail_client_extension: be more modular, clean code organization,
support contact and multi-records, ease helpdesk integration.
SPECIFICATIONS: CODE CLEANING
mail_client_extension: split authentication and main controller code. Purpose
is to ease code understanding and split authentication code from business
related code.
crm_*: split crm code from mail_client_extension. Purpose is to allow the
user to use the mail_client_extension without needing to install CRM, this
will also allow the usage of the mail connector in other modules without
the need for CRM.
Rename {crm/mail}_client_extension to {crm/mail}_plugin: easier to read
and to work with.
SPECIFICATIONS: CONTACTS / MULTI RECORDS SUPPORT
This merge adds support for searching multiple contacts using a text query in
the email plugin rather than forcing the user to stick with a single matching
contact. This is done to give more functionality to the user and to enable him
to perform actions (log emails, create opportunities...) on other contacts
and not only on the matched contact. Also add the ability to log emails into
contacts.
Legacy and deprecated routes/methods/actions have not been deleted in order to
ensure that older versions of the plugin would still work after this update,
these are now marked with a comment.
See sub commits for more details about this PR's content and technical
challenges.
LINKS
Task-2427737
closes odoo/odoo#66139
Com-pr: odoo/odoo#66139
Ent-pr: odoo/enterprise#16913
Upg-pr: odoo/upgrade#2156
Related: odoo/upgrade#2156
Related: odoo/enterprise#16913
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -22,6 +22,9 @@ class ResConfigSettings(models.TransientModel):
|
||||
string='Allow the users to synchronize their calendar with Google Calendar')
|
||||
module_microsoft_calendar = fields.Boolean(
|
||||
string='Allow the users to synchronize their calendar with Outlook Calendar')
|
||||
module_mail_plugin = fields.Boolean(
|
||||
string='Allow integration with the mail plugins'
|
||||
)
|
||||
module_google_drive = fields.Boolean("Attach Google documents to any record")
|
||||
module_google_spreadsheet = fields.Boolean("Google Spreadsheet")
|
||||
module_auth_oauth = fields.Boolean("Use external authentication providers (OAuth)")
|
||||
|
||||
@@ -210,17 +210,15 @@
|
||||
|
||||
<h2>Integrations</h2>
|
||||
<div class="row mt16 o_settings_container" name="integration">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="external_pads_setting">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="mail_pluggin_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_pad"/>
|
||||
<field name="module_mail_plugin" />
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_pad"/>
|
||||
<label string="Mail Plugin" for="module_mail_plugin"/>
|
||||
<a href="https://www.odoo.com/documentation/user/crm/optimize/outlook_extension.html" title="Documentation" class="o_doc_link" target="_blank"></a>
|
||||
<div class="text-muted">
|
||||
Use external pads in Odoo Notes
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('module_pad','=',False)]}" id="msg_module_pad">
|
||||
<div class="text-warning mt16"><strong>Save</strong> this page and come back here to set up the feature.</div>
|
||||
Integrate with mail client plugins
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,6 +237,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="external_pads_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_pad"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_pad"/>
|
||||
<div class="text-muted">
|
||||
Use external pads in Odoo Notes
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('module_pad','=',False)]}" id="msg_module_pad">
|
||||
<div class="text-warning mt16"><strong>Save</strong> this page and come back here to set up the feature.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="sync_google_calendar_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_google_calendar" />
|
||||
|
||||
@@ -39,7 +39,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
module_crm_iap_lead = fields.Boolean("Generate new leads based on their country, industries, size, etc.")
|
||||
module_crm_iap_lead_website = fields.Boolean("Create Leads/Opportunities from your website's traffic")
|
||||
module_crm_iap_lead_enrich = fields.Boolean("Enrich your leads automatically with company data based on their email address.")
|
||||
module_mail_client_extension = fields.Boolean("See and manage users, companies, and leads from our mail client extensions.")
|
||||
lead_enrich_auto = fields.Selection([
|
||||
('manual', 'Enrich leads on demand only'),
|
||||
('auto', 'Enrich all leads automatically'),
|
||||
|
||||
@@ -119,19 +119,6 @@
|
||||
|
||||
<h2>Lead Generation</h2>
|
||||
<div class="row mt16 o_settings_container" name="convert_visitor_setting_container">
|
||||
<div class="col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_mail_client_extension"/>
|
||||
</div>
|
||||
|
||||
<div class="o_setting_right_pane" id="mail_client_extension">
|
||||
<label string="Outlook CRM Extension" for="module_mail_client_extension"/>
|
||||
<a href="https://www.odoo.com/documentation/user/crm/optimize/outlook_extension.html" title="Documentation" class="o_doc_link" target="_blank"></a>
|
||||
<div class="text-muted">
|
||||
Turn emails received in your Outlook mailbox into leads and log their content as internal notes.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_crm_iap_lead_enrich"/>
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_lead_enrich
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.tests.common import TestCrmCommon
|
||||
from odoo.addons.crm_iap_lead_enrich.tests.common import MockIAPEnrich
|
||||
from odoo.addons.iap.tests.common import MockIAPEnrich
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'CRM Mail Plugin',
|
||||
'version': '1.0',
|
||||
'category': 'Sales/CRM',
|
||||
'sequence': 5,
|
||||
'summary': 'Turn emails received in your mailbox into leads and log their content as internal notes.',
|
||||
'description': "Turn emails received in your mailbox into leads and log their content as internal notes.",
|
||||
'website': 'https://www.odoo.com/page/crm',
|
||||
'depends': [
|
||||
'crm',
|
||||
'mail_plugin',
|
||||
],
|
||||
'data': [
|
||||
'views/crm_mail_plugin_lead.xml',
|
||||
'views/crm_lead_views.xml'
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': True
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import crm_client
|
||||
from . import mail_plugin
|
||||
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import werkzeug
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.tools import html2plaintext
|
||||
|
||||
|
||||
class CrmClient(http.Controller):
|
||||
|
||||
@http.route(route='/mail_client_extension/log_single_mail_content',
|
||||
type="json", auth="outlook", cors="*")
|
||||
def log_single_mail_content(self, lead, message, **kw):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
crm_lead = request.env['crm.lead'].browse(lead)
|
||||
crm_lead.message_post(body=message)
|
||||
|
||||
@http.route('/mail_client_extension/lead/get_by_partner_id', type="json", auth="outlook", cors="*")
|
||||
def crm_lead_get_by_partner_id(self, partner, limit=5, offset=0, **kwargs):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
partner_instance = request.env['res.partner'].browse(partner)
|
||||
return {'leads': self._fetch_partner_leads(partner_instance, limit, offset)}
|
||||
|
||||
@http.route('/mail_client_extension/lead/create_from_partner', type='http', auth='user', methods=['GET'])
|
||||
def crm_lead_redirect_create_form_view(self, partner_id):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
server_action = http.request.env.ref("crm_mail_plugin.lead_creation_prefilled_action")
|
||||
return werkzeug.utils.redirect(
|
||||
'/web#action=%s&model=crm.lead&partner_id=%s' % (server_action.id, int(partner_id)))
|
||||
|
||||
@http.route('/mail_plugin/lead/create', type='json', auth='outlook', cors="*")
|
||||
def crm_lead_create(self, partner_id, email_body, email_subject):
|
||||
partner = request.env['res.partner'].browse(partner_id).exists()
|
||||
if not partner:
|
||||
return {'error': 'partner_not_found'}
|
||||
|
||||
# In a multi-company environment, we will not be able to create a lead if the user is connected to a different
|
||||
# company then the one linked to the contact, this is not supported, and users are encouraged to check if they
|
||||
# are connected to the right company before performing such operation.
|
||||
record = request.env['crm.lead'].create({
|
||||
'name': html2plaintext(email_subject),
|
||||
'partner_id': partner_id,
|
||||
'description': html2plaintext(email_body),
|
||||
})
|
||||
|
||||
return {'lead_id': record.id}
|
||||
|
||||
@http.route('/mail_client_extension/lead/open', type='http', auth='user')
|
||||
def crm_lead_open(self, lead_id):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
action = http.request.env.ref("crm.crm_lead_view_form")
|
||||
url = '/web#id=%s&action=%s&model=crm.lead&edit=1&model=crm.lead' % (lead_id, action.id)
|
||||
return werkzeug.utils.redirect(url)
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo.http import request
|
||||
from odoo.tools.misc import formatLang
|
||||
|
||||
from odoo.addons.mail_plugin.controllers import mail_plugin
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailPluginController(mail_plugin.MailPluginController):
|
||||
|
||||
def _fetch_partner_leads(self, partner, limit=5, offset=0):
|
||||
"""
|
||||
Returns an array containing partner leads, each lead will have the following structure :
|
||||
{
|
||||
id: the lead's id,
|
||||
name: the lead's name,
|
||||
expected_revenue: the expected revenue field value
|
||||
probability: the value of the probability field,
|
||||
recurring_revenue: the value of the recurring_revenue field if the lead has a recurring revenue
|
||||
recurring_plan: the value of the recurring plan field if the lead has a recurring revenue
|
||||
}
|
||||
"""
|
||||
|
||||
partner_leads = request.env['crm.lead'].search(
|
||||
[('partner_id', '=', partner.id)], offset=offset, limit=limit)
|
||||
recurring_revenues = request.env.user.has_group('crm.group_use_recurring_revenues')
|
||||
|
||||
leads = []
|
||||
for lead in partner_leads:
|
||||
lead_values = {
|
||||
'lead_id': lead.id,
|
||||
'name': lead.name,
|
||||
'expected_revenue': formatLang(request.env, lead.expected_revenue, monetary=True,
|
||||
currency_obj=lead.company_currency),
|
||||
'probability': lead.probability,
|
||||
}
|
||||
|
||||
if recurring_revenues:
|
||||
lead_values.update({
|
||||
'recurring_revenue': formatLang(request.env, lead.recurring_revenue, monetary=True,
|
||||
currency_obj=lead.company_currency),
|
||||
'recurring_plan': lead.recurring_plan.name,
|
||||
})
|
||||
|
||||
leads.append(lead_values)
|
||||
|
||||
return leads
|
||||
|
||||
def _prepare_contact_values(self, partner):
|
||||
contact_values = super(MailPluginController, self)._prepare_contact_values(partner)
|
||||
if not partner:
|
||||
contact_values['leads'] = []
|
||||
else:
|
||||
contact_values['leads'] = self._fetch_partner_leads(partner)
|
||||
return contact_values
|
||||
|
||||
def _mail_content_logging_models_whitelist(self):
|
||||
return super(MailPluginController, self)._mail_content_logging_models_whitelist() + ['crm.lead']
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import crm_lead
|
||||
+5
@@ -3,11 +3,16 @@
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class Lead(models.Model):
|
||||
_inherit = 'crm.lead'
|
||||
|
||||
@api.model
|
||||
def _form_view_auto_fill(self):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Called by the plugin to open a lead in edit mode -->
|
||||
<record id="crm_lead_action_form_edit" model="ir.actions.act_window">
|
||||
<field name="name">Lead: redirect form in edit mode</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="crm.crm_lead_view_form"/>
|
||||
<field name="context">
|
||||
{
|
||||
'form_view_initial_mode': 'edit',
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!--deprecated as of saas-14.3-->
|
||||
<record id="lead_creation_prefilled_action" model="ir.actions.server">
|
||||
<field name="name">Redirection to the lead creation form with prefilled info</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import iap_account
|
||||
from . import res_config_settings
|
||||
from . import iap_enrich_api
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_iap
|
||||
from . import common
|
||||
|
||||
@@ -6,7 +6,7 @@ from unittest.mock import patch
|
||||
|
||||
from odoo import exceptions
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.addons.crm_iap_lead_enrich.models.iap_enrich_api import IapEnrichAPI
|
||||
from odoo.addons.iap.models.iap_enrich_api import IapEnrichAPI
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -1,24 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Mail Client Extension',
|
||||
'version': '1.0',
|
||||
'category': 'Sales/CRM',
|
||||
'sequence': 5,
|
||||
'summary': 'Turn emails received in your Outlook mailbox into leads and log their content as internal notes.',
|
||||
'description': "Turn emails received in your Outlook mailbox into leads and log their content as internal notes.",
|
||||
'website': 'https://www.odoo.com/page/crm',
|
||||
'depends': [
|
||||
'web',
|
||||
'crm',
|
||||
'crm_iap_lead_enrich'
|
||||
],
|
||||
'data': [
|
||||
'views/mail_client_extension_login.xml',
|
||||
'views/mail_client_extension_lead.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*
|
||||
from . import main
|
||||
@@ -1,275 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import base64
|
||||
import datetime
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import odoo
|
||||
import requests
|
||||
import werkzeug
|
||||
|
||||
from odoo import http, tools
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.http import request
|
||||
from odoo.tools.misc import formatLang
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailClientExtensionController(http.Controller):
|
||||
|
||||
@http.route('/mail_client_extension/auth', type='http', auth="user", methods=['GET'], website=True)
|
||||
def auth(self, **values):
|
||||
"""
|
||||
Once authenticated this route renders the view that shows an app wants to access Odoo.
|
||||
The user is invited to allow or deny the app. The form posts to `/mail_client_extension/auth/confirm`.
|
||||
"""
|
||||
return request.render('mail_client_extension.app_auth', values)
|
||||
|
||||
@http.route('/mail_client_extension/auth/confirm', type='http', auth="user", methods=['POST'])
|
||||
def auth_confirm(self, scope, friendlyname, redirect, info=None, do=None, **kw):
|
||||
"""
|
||||
Called by the `app_auth` template. If the user decided to allow the app to access Odoo, a temporary auth code
|
||||
is generated and he is redirected to `redirect` with this code in the URL. It should redirect to the app, and
|
||||
the app should then exchange this auth code for an access token by calling
|
||||
`/mail_client_extension/auth/access_token`.
|
||||
"""
|
||||
parsed_redirect = werkzeug.urls.url_parse(redirect)
|
||||
params = parsed_redirect.decode_query()
|
||||
if do:
|
||||
name = friendlyname if not info else f'{friendlyname}: {info}'
|
||||
auth_code = self._generate_auth_code(scope, name)
|
||||
# params is a MultiDict which does not support .update() with kwargs
|
||||
params.update({'success': 1, 'auth_code': auth_code})
|
||||
else:
|
||||
params['success'] = 0
|
||||
updated_redirect = parsed_redirect.replace(query=werkzeug.urls.url_encode(params))
|
||||
return werkzeug.utils.redirect(updated_redirect.to_url())
|
||||
|
||||
# In this case, an exception will be thrown in case of preflight request if only POST is allowed.
|
||||
@http.route('/mail_client_extension/auth/access_token', type='json', auth="none", cors="*", methods=['POST', 'OPTIONS'])
|
||||
def auth_access_token(self, auth_code, **kw):
|
||||
"""
|
||||
Called by the external app to exchange an auth code, which is temporary and was passed in a URL, for an
|
||||
access token, which is permanent, and can be used in the `Authorization` header to authorize subsequent requests
|
||||
"""
|
||||
auth_message = self._get_auth_code_data(auth_code)
|
||||
if not auth_message:
|
||||
return {"error": "Invalid code"}
|
||||
request.uid = auth_message['uid']
|
||||
scope = 'odoo.plugin.' + auth_message.get('scope', '')
|
||||
api_key = request.env['res.users.apikeys']._generate(scope, auth_message['name'])
|
||||
return {'access_token': api_key }
|
||||
|
||||
def _get_auth_code_data(self, auth_code):
|
||||
data, auth_code_signature = auth_code.split('.')
|
||||
data = base64.b64decode(data)
|
||||
auth_code_signature = base64.b64decode(auth_code_signature)
|
||||
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', data).encode()
|
||||
if not hmac.compare_digest(auth_code_signature, signature):
|
||||
return None
|
||||
|
||||
auth_message = json.loads(data)
|
||||
# Check the expiration
|
||||
if datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(auth_message['timestamp']) > datetime.timedelta(minutes=3):
|
||||
return None
|
||||
|
||||
return auth_message
|
||||
|
||||
# Using UTC explicitly in case of a distributed system where the generation and the signature verification do not
|
||||
# necessarily happen on the same server
|
||||
def _generate_auth_code(self, scope, name):
|
||||
auth_dict = {
|
||||
'scope': scope,
|
||||
'name': name,
|
||||
'timestamp': int(datetime.datetime.utcnow().timestamp()), # <- elapsed time should be < 3 mins when verifying
|
||||
'uid': request.uid,
|
||||
}
|
||||
auth_message = json.dumps(auth_dict, sort_keys=True).encode()
|
||||
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', auth_message).encode()
|
||||
auth_code = "%s.%s" % (base64.b64encode(auth_message).decode(), base64.b64encode(signature).decode())
|
||||
_logger.info('Auth code created - user %s, scope %s', request.env.user, scope)
|
||||
return auth_code
|
||||
|
||||
def _iap_enrich(self, domain):
|
||||
enriched_data = {}
|
||||
try:
|
||||
response = request.env['iap.enrich.api']._request_enrich({domain: domain}) # The key doesn't matter
|
||||
#except odoo.addons.iap.models.iap.InsufficientCreditError as ice:
|
||||
except iap_tools.InsufficientCreditError:
|
||||
enriched_data['enrichment_info'] = {'type': 'insufficient_credit', 'info': request.env['iap.account'].get_credits_url('reveal')}
|
||||
except Exception as e:
|
||||
enriched_data["enrichment_info"] = {'type': 'other', 'info': 'Unknown reason'}
|
||||
else:
|
||||
enriched_data = response.get(domain)
|
||||
if not enriched_data:
|
||||
enriched_data = {'enrichment_info': {'type': 'no_data', 'info': 'The enrichment API found no data for the email provided.'}}
|
||||
return enriched_data
|
||||
|
||||
@http.route('/mail_client_extension/modules/get', type="json", auth="outlook", csrf=False, cors="*")
|
||||
def modules_get(self, **kwargs):
|
||||
return {'modules': ['contacts', 'crm']}
|
||||
|
||||
# Find an existing company based on the email.
|
||||
def _find_existing_company(self, domain):
|
||||
if domain in iap_tools._MAIL_DOMAIN_BLACKLIST:
|
||||
return
|
||||
return request.env['res.partner'].search([('is_company', '=', True), ('email', '=ilike', '%' + domain)], limit=1)
|
||||
|
||||
def _get_company_dict(self, company):
|
||||
if not company:
|
||||
return {'id': -1}
|
||||
|
||||
return {
|
||||
'id': company.id,
|
||||
'name': company.name,
|
||||
'phone': company.phone,
|
||||
'mobile': company.mobile,
|
||||
'email': company.email,
|
||||
'address': {
|
||||
'street': company.street,
|
||||
'city': company.city,
|
||||
'zip': company.zip,
|
||||
'country': company.country_id.name if company.country_id else ''
|
||||
},
|
||||
'website': company.website,
|
||||
'additionalInfo': json.loads(company.iap_enrich_info) if company.iap_enrich_info else {}
|
||||
}
|
||||
|
||||
def _create_company_from_iap(self, domain):
|
||||
iap_data = self._iap_enrich(domain)
|
||||
if 'enrichment_info' in iap_data:
|
||||
return None, iap_data['enrichment_info']
|
||||
|
||||
phone_numbers = iap_data.get('phone_numbers')
|
||||
emails = iap_data.get('email')
|
||||
new_company_info = {
|
||||
'is_company': True,
|
||||
'name': iap_data.get("name"),
|
||||
'street': iap_data.get("street_name"),
|
||||
'city': iap_data.get("city"),
|
||||
'zip': iap_data.get("postal_code"),
|
||||
'phone': phone_numbers[0] if phone_numbers else None,
|
||||
'website': iap_data.get("domain"),
|
||||
'email': emails[0] if emails else None
|
||||
}
|
||||
|
||||
logo_url = iap_data.get('logo')
|
||||
if logo_url:
|
||||
try:
|
||||
response = requests.get(logo_url, timeout=2)
|
||||
if response.ok:
|
||||
new_company_info['image_1920'] = base64.b64encode(response.content)
|
||||
except Exception as e:
|
||||
_logger.warning('Download of image for new company %r failed, error %r' % (new_company_info.name, e))
|
||||
|
||||
if iap_data.get('country_code'):
|
||||
country = request.env['res.country'].search([('code', '=', iap_data['country_code'].upper())])
|
||||
if country:
|
||||
new_company_info['country_id'] = country.id
|
||||
if iap_data.get('state_code'):
|
||||
state = request.env['res.country.state'].search([
|
||||
('code', '=', iap_data['state_code']),
|
||||
('country_id', '=', country.id)
|
||||
])
|
||||
if state:
|
||||
new_company_info['state_id'] = state.id
|
||||
|
||||
new_company_info['iap_enrich_info'] = json.dumps(iap_data)
|
||||
new_company = request.env['res.partner'].create(new_company_info)
|
||||
new_company.message_post_with_view(
|
||||
'iap_mail.enrich_company',
|
||||
values=iap_data,
|
||||
subtype_id=request.env.ref('mail.mt_note').id,
|
||||
)
|
||||
|
||||
return new_company, {'type': 'company_created'}
|
||||
|
||||
@http.route('/mail_client_extension/partner/get', type="json", auth="outlook", cors="*")
|
||||
def res_partner_get_by_email(self, email, name, **kwargs):
|
||||
response = {}
|
||||
|
||||
#compute the sender's domain
|
||||
normalized_email = tools.email_normalize(email)
|
||||
if not normalized_email:
|
||||
response['error'] = 'Bad email.'
|
||||
return response
|
||||
sender_domain = normalized_email.split('@')[1]
|
||||
|
||||
# Search for the partner based on the email.
|
||||
# If multiple are found, take the first one.
|
||||
partner = request.env['res.partner'].search([('email', 'in', [normalized_email, email])], limit=1)
|
||||
if partner:
|
||||
response['partner'] = {
|
||||
'id': partner.id,
|
||||
'name': partner.name,
|
||||
'title': partner.function,
|
||||
'email': partner.email,
|
||||
'image': partner.image_128,
|
||||
'phone': partner.phone,
|
||||
'mobile': partner.mobile,
|
||||
'enrichment_info': None
|
||||
}
|
||||
# if there is already a company for this partner, just take it without enrichment.
|
||||
if partner.parent_id:
|
||||
response['partner']['company'] = self._get_company_dict(partner.parent_id)
|
||||
else:
|
||||
company = self._find_existing_company(sender_domain)
|
||||
if not company: # create and enrich company
|
||||
company, enrichment_info = self._create_company_from_iap(sender_domain)
|
||||
response['enrichment_info'] = enrichment_info
|
||||
partner.write({'parent_id': company})
|
||||
response['partner']['company'] = self._get_company_dict(company)
|
||||
else: #no partner found
|
||||
response['partner'] = {
|
||||
'id': -1,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'enrichment_info': None
|
||||
}
|
||||
company = self._find_existing_company(sender_domain)
|
||||
if not company: # create and enrich company
|
||||
company, enrichment_info = self._create_company_from_iap(sender_domain)
|
||||
response['enrichment_info'] = enrichment_info
|
||||
response['partner']['company'] = self._get_company_dict(company)
|
||||
|
||||
return response
|
||||
|
||||
@http.route('/mail_client_extension/partner/create', type="json", auth="outlook", cors="*")
|
||||
def res_partner_create(self, email, name, company, **kwargs):
|
||||
# TODO search the company again instead of relying on the one provided here?
|
||||
# Create the partner if needed.
|
||||
partner_info = {
|
||||
'name': name,
|
||||
'email': email,
|
||||
}
|
||||
if company > -1:
|
||||
partner_info['parent_id'] = company
|
||||
partner = request.env['res.partner'].create(partner_info)
|
||||
|
||||
response = {'id': partner.id}
|
||||
return response
|
||||
|
||||
@http.route('/mail_client_extension/log_single_mail_content', type="json", auth="outlook", cors="*")
|
||||
def log_single_mail_content(self, lead, message, **kw):
|
||||
crm_lead = request.env['crm.lead'].browse(lead)
|
||||
crm_lead.message_post(body=message)
|
||||
|
||||
@http.route('/mail_client_extension/lead/get_by_partner_id', type="json", auth="outlook", cors="*")
|
||||
def crm_lead_get_by_partner_id(self, partner, limit, offset, **kwargs):
|
||||
partner_leads = request.env['crm.lead'].search([('partner_id', '=', partner)], offset=offset, limit=limit)
|
||||
leads = []
|
||||
for lead in partner_leads:
|
||||
leads.append({
|
||||
'id': lead.id,
|
||||
'name': lead.name,
|
||||
'expected_revenue': formatLang(request.env, lead.expected_revenue, monetary=True, currency_obj=lead.company_currency),
|
||||
})
|
||||
|
||||
return {'leads': leads}
|
||||
|
||||
@http.route('/mail_client_extension/lead/create_from_partner', type='http', auth='user', methods=['GET'])
|
||||
def crm_lead_redirect_form_view(self, partner_id):
|
||||
server_action = http.request.env.ref("mail_client_extension.lead_creation_prefilled_action")
|
||||
return werkzeug.utils.redirect('/web#action=%s&model=crm.lead&partner_id=%s' % (server_action.id, int(partner_id)))
|
||||
@@ -1,3 +0,0 @@
|
||||
from . import crm_lead
|
||||
from . import res_partner
|
||||
from . import ir_http
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Mail Plugin',
|
||||
'version': '1.0',
|
||||
'category': 'Sales/CRM',
|
||||
'sequence': 5,
|
||||
'summary': 'Allows integration with mail plugins.',
|
||||
'description': "Integrate Odoo with your mailbox, get information about contacts directly inside your mailbox, log content of emails as internal notes",
|
||||
'depends': [
|
||||
'web',
|
||||
'contacts',
|
||||
'iap'
|
||||
],
|
||||
'data': [
|
||||
'views/mail_plugin_login.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import authenticate
|
||||
from . import mail_plugin
|
||||
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import hmac
|
||||
import json
|
||||
import logging
|
||||
import odoo
|
||||
import werkzeug
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Authenticate(http.Controller):
|
||||
|
||||
@http.route(['/mail_client_extension/auth', '/mail_plugin/auth'], type='http', auth="user", methods=['GET'], website=True)
|
||||
def auth(self, **values):
|
||||
"""
|
||||
Once authenticated this route renders the view that shows an app wants to access Odoo.
|
||||
The user is invited to allow or deny the app. The form posts to `/mail_client_extension/auth/confirm`.
|
||||
|
||||
old route name "/mail_client_extension/auth is deprecated as of saas-14.3,it is not needed for newer
|
||||
versions of the mail plugin but necessary for supporting older versions
|
||||
"""
|
||||
return request.render('mail_plugin.app_auth', values)
|
||||
|
||||
@http.route(['/mail_client_extension/auth/confirm', '/mail_plugin/auth/confirm'], type='http', auth="user", methods=['POST'])
|
||||
def auth_confirm(self, scope, friendlyname, redirect, info=None, do=None, **kw):
|
||||
"""
|
||||
Called by the `app_auth` template. If the user decided to allow the app to access Odoo, a temporary auth code
|
||||
is generated and he is redirected to `redirect` with this code in the URL. It should redirect to the app, and
|
||||
the app should then exchange this auth code for an access token by calling
|
||||
`/mail_client/auth/access_token`.
|
||||
|
||||
old route name "/mail_client_extension/auth/confirm is deprecated as of saas-14.3,it is not needed for newer
|
||||
versions of the mail plugin but necessary for supporting older versions
|
||||
"""
|
||||
parsed_redirect = werkzeug.urls.url_parse(redirect)
|
||||
params = parsed_redirect.decode_query()
|
||||
if do:
|
||||
name = friendlyname if not info else f'{friendlyname}: {info}'
|
||||
auth_code = self._generate_auth_code(scope, name)
|
||||
# params is a MultiDict which does not support .update() with kwargs
|
||||
# the state attribute is needed for the gmail connector
|
||||
params.update({'success': 1, 'auth_code': auth_code, 'state': kw.get('state', '')})
|
||||
else:
|
||||
params.update({'success': 0, 'state': kw.get('state', '')})
|
||||
updated_redirect = parsed_redirect.replace(query=werkzeug.urls.url_encode(params))
|
||||
return werkzeug.utils.redirect(updated_redirect.to_url())
|
||||
|
||||
# In this case, an exception will be thrown in case of preflight request if only POST is allowed.
|
||||
@http.route(['/mail_client_extension/auth/access_token', '/mail_plugin/auth/access_token'], type='json', auth="none", cors="*",
|
||||
methods=['POST', 'OPTIONS'])
|
||||
def auth_access_token(self, auth_code, **kw):
|
||||
"""
|
||||
Called by the external app to exchange an auth code, which is temporary and was passed in a URL, for an
|
||||
access token, which is permanent, and can be used in the `Authorization` header to authorize subsequent requests
|
||||
|
||||
old route name "/mail_client_extension/auth/access_token is deprecated as of saas-14.3,it is not needed for newer
|
||||
versions of the mail plugin but necessary for supporting older versions
|
||||
"""
|
||||
auth_message = self._get_auth_code_data(auth_code)
|
||||
if not auth_message:
|
||||
return {"error": "Invalid code"}
|
||||
request.uid = auth_message['uid']
|
||||
scope = 'odoo.plugin.' + auth_message.get('scope', '')
|
||||
api_key = request.env['res.users.apikeys']._generate(scope, auth_message['name'])
|
||||
return {'access_token': api_key}
|
||||
|
||||
def _get_auth_code_data(self, auth_code):
|
||||
data, auth_code_signature = auth_code.split('.')
|
||||
data = base64.b64decode(data)
|
||||
auth_code_signature = base64.b64decode(auth_code_signature)
|
||||
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_plugin', data).encode()
|
||||
if not hmac.compare_digest(auth_code_signature, signature):
|
||||
return None
|
||||
|
||||
auth_message = json.loads(data)
|
||||
# Check the expiration
|
||||
if datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(auth_message['timestamp']) > datetime.timedelta(
|
||||
minutes=3):
|
||||
return None
|
||||
|
||||
return auth_message
|
||||
|
||||
# Using UTC explicitly in case of a distributed system where the generation and the signature verification do not
|
||||
# necessarily happen on the same server
|
||||
def _generate_auth_code(self, scope, name):
|
||||
auth_dict = {
|
||||
'scope': scope,
|
||||
'name': name,
|
||||
'timestamp': int(datetime.datetime.utcnow().timestamp()),
|
||||
# <- elapsed time should be < 3 mins when verifying
|
||||
'uid': request.uid,
|
||||
}
|
||||
auth_message = json.dumps(auth_dict, sort_keys=True).encode()
|
||||
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_plugin', auth_message).encode()
|
||||
auth_code = "%s.%s" % (base64.b64encode(auth_message).decode(), base64.b64encode(signature).decode())
|
||||
_logger.info('Auth code created - user %s, scope %s', request.env.user, scope)
|
||||
return auth_code
|
||||
@@ -0,0 +1,293 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from odoo import http, tools, _
|
||||
from odoo.addons.iap.tools import iap_tools
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailPluginController(http.Controller):
|
||||
|
||||
@http.route('/mail_client_extension/modules/get', type="json", auth="outlook", csrf=False, cors="*")
|
||||
def modules_get(self, **kwargs):
|
||||
"""
|
||||
deprecated as of saas-14.3, not needed for newer versions of the mail plugin but necessary
|
||||
for supporting older versions
|
||||
"""
|
||||
return {'modules': ['contacts', 'crm']}
|
||||
|
||||
@http.route('/mail_plugin/partner/enrich_and_create_company',
|
||||
type="json", auth="outlook", cors="*")
|
||||
def res_partner_enrich_and_create_company(self, partner_id):
|
||||
"""
|
||||
Route used when the user clicks on the create and enrich partner button
|
||||
it will try to find a company using IAP, if a company is found
|
||||
the enriched company will then be created in the database
|
||||
"""
|
||||
response = {}
|
||||
|
||||
partner = request.env['res.partner'].browse(partner_id)
|
||||
|
||||
if partner.parent_id:
|
||||
return {'error': _("The partner already has a company related to him")}
|
||||
|
||||
normalized_email = partner.email_normalized
|
||||
if not normalized_email:
|
||||
response = {'error': _('Contact has no valid email')}
|
||||
return response
|
||||
|
||||
company_domain = tools.email_domain_extract(normalized_email)
|
||||
|
||||
company, enrichment_info = self._create_company_from_iap(company_domain)
|
||||
|
||||
response['enrichment_info'] = enrichment_info
|
||||
response['company'] = self._prepare_company_values(company)
|
||||
if company:
|
||||
partner.write({'parent_id': company})
|
||||
|
||||
return response
|
||||
|
||||
@http.route(['/mail_client_extension/partner/get', '/mail_plugin/partner/get']
|
||||
, type="json", auth="outlook", cors="*")
|
||||
def res_partner_get(self, email=None, name=None, partner_id=None, **kwargs):
|
||||
"""
|
||||
returns a partner given it's id or an email and a name.
|
||||
In case the partner does not exist, we return partner having an id -1, we also look if an existing company
|
||||
matching the contact exists in the database, if none is found a new company is enriched and created automatically
|
||||
|
||||
old route name "/mail_client_extension/partner/get is deprecated as of saas-14.3, it is not needed for newer
|
||||
versions of the mail plugin but necessary for supporting older versions, only the route name is deprecated not
|
||||
the entire method.
|
||||
"""
|
||||
|
||||
if not (partner_id or (name and email)):
|
||||
return {'error': _('You need to specify at least the partner_id or the name and the email')}
|
||||
|
||||
if partner_id:
|
||||
partner = request.env['res.partner'].browse(partner_id)
|
||||
return self._prepare_contact_values(partner)
|
||||
|
||||
normalized_email = tools.email_normalize(email)
|
||||
if not normalized_email:
|
||||
return {'error': _('Bad Email.')}
|
||||
|
||||
# Search for the partner based on the email.
|
||||
# If multiple are found, take the first one.
|
||||
partner = request.env['res.partner'].search(['|', ('email', 'in', [normalized_email, email]),
|
||||
('email_normalized', '=', normalized_email)], limit=1)
|
||||
|
||||
response = self._prepare_contact_values(partner)
|
||||
|
||||
# if no partner is found in the database, we should also return an empty one having id = -1, otherwise older versions of
|
||||
# plugin won't work
|
||||
if not response['partner']:
|
||||
sender_domain = tools.email_domain_extract(email)
|
||||
response['partner'] = {
|
||||
'id': -1,
|
||||
'email': email,
|
||||
'name': name,
|
||||
'enrichment_info': None,
|
||||
}
|
||||
company = self._find_existing_company(normalized_email)
|
||||
if not company: # create and enrich company
|
||||
company, enrichment_info = self._create_company_from_iap(sender_domain)
|
||||
response['partner']['enrichment_info'] = enrichment_info
|
||||
response['partner']['company'] = self._prepare_company_values(company)
|
||||
|
||||
return response
|
||||
|
||||
@http.route('/mail_plugin/partner/search', type="json", auth="outlook", cors="*")
|
||||
def res_partners_search(self, search_term, limit=30, **kwargs):
|
||||
"""
|
||||
Used for the plugin search contact functionality where the user types a string query in order to search for
|
||||
matching contacts, the string query can either be the name of the contact, it's reference or it's email.
|
||||
We choose these fields because these are probably the most interesting fields that the user can perform a
|
||||
search on.
|
||||
The method returns an array containing the dicts of the matched contacts.
|
||||
"""
|
||||
|
||||
#In a multi-company environment, the method may return contacts not belonging to the company that the user
|
||||
#is connected to, this may result in the user not being able to view the contact in Odoo, while this may happen
|
||||
#it is not supported for now and users are encouraged to check if they are connected to the correct company before
|
||||
# clicking on a contact.
|
||||
|
||||
normalized_email = tools.email_normalize(search_term)
|
||||
|
||||
if normalized_email:
|
||||
filter_domain = [('email_normalized', '=', search_term)]
|
||||
else:
|
||||
filter_domain = ['|', '|', ('display_name', 'ilike', search_term), ('ref', '=', search_term),
|
||||
('email', 'ilike', search_term)]
|
||||
|
||||
# Search for the partner based on the email.
|
||||
# If multiple are found, take the first one.
|
||||
partners = request.env['res.partner'].search(filter_domain, limit=limit)
|
||||
|
||||
partners = [
|
||||
self._prepare_partner_values(partner)
|
||||
for partner in partners
|
||||
]
|
||||
return {"partners": partners}
|
||||
|
||||
@http.route(['/mail_client_extension/partner/create', '/mail_plugin/partner/create'],
|
||||
type="json", auth="outlook", cors="*")
|
||||
def res_partner_create(self, email, name, company):
|
||||
"""
|
||||
params email: email of the new partner
|
||||
params name: name of the new partner
|
||||
params company: parent company id of the new partner
|
||||
"""
|
||||
# old route name "/mail_client_extension/partner/create is deprecated as of saas-14.3,it is not needed for newer
|
||||
# versions of the mail plugin but necessary for supporting older versions
|
||||
# TODO search the company again instead of relying on the one provided here?
|
||||
# Create the partner if needed.
|
||||
partner_info = {
|
||||
'name': name,
|
||||
'email': email,
|
||||
}
|
||||
|
||||
#see if the partner has a parent company
|
||||
if company and company > -1:
|
||||
partner_info['parent_id'] = company
|
||||
partner = request.env['res.partner'].create(partner_info)
|
||||
|
||||
response = {'id': partner.id}
|
||||
return response
|
||||
|
||||
@http.route('/mail_plugin/log_mail_content', type="json", auth="outlook", cors="*")
|
||||
def log_mail_content(self, model, res_id, message):
|
||||
if model not in self._mail_content_logging_models_whitelist():
|
||||
raise Forbidden()
|
||||
request.env[model].browse(res_id).message_post(body=message)
|
||||
|
||||
def _iap_enrich(self, domain):
|
||||
enriched_data = {}
|
||||
try:
|
||||
response = request.env['iap.enrich.api']._request_enrich({domain: domain}) # The key doesn't matter
|
||||
except iap_tools.InsufficientCreditError:
|
||||
enriched_data['enrichment_info'] = {'type': 'insufficient_credit', 'info': request.env['iap.account'].get_credits_url('reveal')}
|
||||
except Exception:
|
||||
enriched_data["enrichment_info"] = {'type': 'other', 'info': 'Unknown reason'}
|
||||
else:
|
||||
enriched_data = response.get(domain)
|
||||
if not enriched_data:
|
||||
enriched_data = {'enrichment_info': {'type': 'no_data', 'info': 'The enrichment API found no data for the email provided.'}}
|
||||
return enriched_data
|
||||
|
||||
def _find_existing_company(self, normalized_email):
|
||||
sender_domain = tools.email_domain_extract(normalized_email)
|
||||
search = sender_domain if sender_domain not in iap_tools._MAIL_DOMAIN_BLACKLIST else normalized_email
|
||||
return request.env['res.partner'].search([('is_company', '=', True), ('email_normalized', '=ilike', '%' + search)],
|
||||
limit=1)
|
||||
|
||||
def _prepare_company_values(self, company):
|
||||
|
||||
if not company:
|
||||
return {'id': -1}
|
||||
|
||||
fields_list = ['id', 'name', 'phone', 'mobile', 'email', 'website']
|
||||
|
||||
company_values = dict((fname, company[fname]) for fname in fields_list)
|
||||
company_values['address'] = {'street': company.street,
|
||||
'city': company.city,
|
||||
'zip': company.zip,
|
||||
'country': company.country_id.name if company.country_id else ''}
|
||||
company_values['additionalInfo'] = json.loads(company.iap_enrich_info) if company.iap_enrich_info else {}
|
||||
company_values['image'] = company.image_1920
|
||||
|
||||
return company_values
|
||||
|
||||
|
||||
def _create_company_from_iap(self, domain):
|
||||
iap_data = self._iap_enrich(domain)
|
||||
if 'enrichment_info' in iap_data:
|
||||
return None, iap_data['enrichment_info']
|
||||
|
||||
phone_numbers = iap_data.get('phone_numbers')
|
||||
emails = iap_data.get('email')
|
||||
new_company_info = {
|
||||
'is_company': True,
|
||||
'name': iap_data.get("name") or domain,
|
||||
'street': iap_data.get("street_name"),
|
||||
'city': iap_data.get("city"),
|
||||
'zip': iap_data.get("postal_code"),
|
||||
'phone': phone_numbers[0] if phone_numbers else None,
|
||||
'website': iap_data.get("domain"),
|
||||
'email': emails[0] if emails else None
|
||||
}
|
||||
|
||||
logo_url = iap_data.get('logo')
|
||||
if logo_url:
|
||||
try:
|
||||
response = requests.get(logo_url, timeout=2)
|
||||
if response.ok:
|
||||
new_company_info['image_1920'] = base64.b64encode(response.content)
|
||||
except Exception as e:
|
||||
_logger.warning('Download of image for new company %s failed, error %s', new_company_info.name, e)
|
||||
|
||||
if iap_data.get('country_code'):
|
||||
country = request.env['res.country'].search([('code', '=', iap_data['country_code'].upper())])
|
||||
if country:
|
||||
new_company_info['country_id'] = country.id
|
||||
if iap_data.get('state_code'):
|
||||
state = request.env['res.country.state'].search([
|
||||
('code', '=', iap_data['state_code']),
|
||||
('country_id', '=', country.id)
|
||||
])
|
||||
if state:
|
||||
new_company_info['state_id'] = state.id
|
||||
|
||||
new_company_info['iap_enrich_info'] = json.dumps(iap_data)
|
||||
new_company = request.env['res.partner'].create(new_company_info)
|
||||
new_company.message_post_with_view(
|
||||
'iap_mail.enrich_company',
|
||||
values=iap_data,
|
||||
subtype_id=request.env.ref('mail.mt_note').id,
|
||||
)
|
||||
|
||||
return new_company, {'type': 'company_created'}
|
||||
|
||||
def _prepare_partner_values(self, partner):
|
||||
|
||||
fields_list = ['id', 'name', 'email', 'phone', 'mobile']
|
||||
|
||||
partner_values = dict((fname, partner[fname]) for fname in fields_list)
|
||||
partner_values['image'] = partner.image_128
|
||||
partner_values['title'] = partner.function
|
||||
partner_values['enrichment_info'] = None
|
||||
|
||||
return partner_values
|
||||
|
||||
|
||||
def _prepare_contact_values(self, partner):
|
||||
"""
|
||||
method used to return partner related values, it can be overridden by other modules if extra information have to
|
||||
be returned with the partner (e.g., leads, ...)
|
||||
"""
|
||||
if partner:
|
||||
partner_response = self._prepare_partner_values(partner)
|
||||
if partner.company_type == 'company':
|
||||
partner_response['company'] = self._prepare_company_values(partner)
|
||||
elif partner.parent_id:
|
||||
partner_response['company'] = self._prepare_company_values(partner.parent_id)
|
||||
else:
|
||||
partner_response['company'] = self._prepare_company_values(None)
|
||||
else: # no partner found
|
||||
partner_response = {}
|
||||
|
||||
return {'partner': partner_response}
|
||||
|
||||
def _mail_content_logging_models_whitelist(self):
|
||||
"""
|
||||
Returns all models that emails can be logged to and that can be used by the "log_mail_content" method,
|
||||
it can be overridden by sub modules in order to whitelist more models
|
||||
"""
|
||||
return ['res.partner']
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_http
|
||||
from . import res_partner
|
||||
+1
-1
@@ -23,4 +23,4 @@ class IrHttp(models.AbstractModel):
|
||||
raise BadRequest('Access token invalid')
|
||||
|
||||
# take the identity of the API key user
|
||||
request.uid = user_id
|
||||
request.uid = user_id
|
||||
+4
-3
@@ -1,12 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="mail_client_extension.app_auth" name="Accept app">
|
||||
<template id="mail_plugin.app_auth" name="Accept app">
|
||||
<t t-call="web.login_layout">
|
||||
<t t-set="disable_database_manager" t-value="1"/>
|
||||
<form class="oe_login_form" role="form" method="post" action="/mail_client_extension/auth/confirm">
|
||||
<form class="oe_login_form" role="form" method="post" action="/mail_plugin/auth/confirm">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect"/>
|
||||
<input type="hidden" name="scope" t-att-value="scope"/>
|
||||
<input type="hidden" name="state" t-att-value="state"/>
|
||||
<input type="hidden" name="friendlyname" t-att-value="friendlyname"/>
|
||||
<p class="text-center">
|
||||
Let <t t-esc="friendlyname" /> access your Odoo database?
|
||||
@@ -18,4 +19,4 @@
|
||||
</form>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user