Browse Source

Add support for "Create CRM phonecall" on leads (and not only partners)

Re-organise the codebase to prepare the arrival of a new module for a telephony system other than Asterisk
pull/31/head
Alexis de Lattre 9 years ago
parent
commit
8d4c7fa99b
  1. 40
      asterisk_click2dial/asterisk_click2dial.py
  2. 1
      asterisk_click2dial_crm/__init__.py
  3. 2
      asterisk_click2dial_crm/__openerp__.py
  4. 35
      asterisk_click2dial_crm/asterisk_click2dial_crm.py
  5. 23
      asterisk_click2dial_crm/wizard/__init__.py
  6. 61
      asterisk_click2dial_crm/wizard/create_crm_phonecall.py
  7. 74
      base_phone/base_phone.py
  8. 10
      base_phone/static/src/js/phone_widget.js
  9. 2
      crm_phone/__openerp__.py
  10. 31
      crm_phone/crm_phone.py
  11. 8
      crm_phone/res_users_view.xml
  12. 1
      crm_phone/wizard/__init__.py
  13. 73
      crm_phone/wizard/create_crm_phonecall.py
  14. 0
      crm_phone/wizard/create_crm_phonecall_view.xml

40
asterisk_click2dial/asterisk_click2dial.py

@ -148,36 +148,6 @@ class asterisk_server(orm.Model):
'context', 'alert_info', 'login', 'password']
)]
def _reformat_number(
self, cr, uid, erp_number, ast_server=None, context=None):
'''
This function is dedicated to the transformation of the number
available in OpenERP to the number that Asterisk should dial.
You may have to inherit this function in another module specific
for your company if you are not happy with the way I reformat
the OpenERP numbers.
'''
assert(erp_number), 'Missing phone number'
_logger.debug('Number before reformat = %s' % erp_number)
if not ast_server:
ast_server = self._get_asterisk_server_from_user(
cr, uid, context=context)
# erp_number are supposed to be in E.164 format, so no need to
# give a country code here
parsed_num = phonenumbers.parse(erp_number, None)
country_code = ast_server.company_id.country_id.code
assert(country_code), 'Missing country on company'
_logger.debug('Country code = %s' % country_code)
to_dial_number = phonenumbers.format_out_of_country_calling_number(
parsed_num, country_code.upper()).replace(' ', '').replace('-', '')
# Add 'out prefix' to all numbers
if ast_server.out_prefix:
_logger.debug('Out prefix = %s' % ast_server.out_prefix)
to_dial_number = '%s%s' % (ast_server.out_prefix, to_dial_number)
_logger.debug('Number to be sent to Asterisk = %s' % to_dial_number)
return to_dial_number
def _get_asterisk_server_from_user(self, cr, uid, context=None):
'''Returns an asterisk.server browse object'''
# We check if the user has an Asterisk server configured
@ -429,15 +399,19 @@ class phone_common(orm.AbstractModel):
def click2dial(self, cr, uid, erp_number, context=None):
if not erp_number:
orm.except_orm(
raise orm.except_orm(
_('Error:'),
_('Missing phone number'))
user, ast_server, ast_manager = \
self.pool['asterisk.server']._connect_to_asterisk(
cr, uid, context=context)
ast_number = self.pool['asterisk.server']._reformat_number(
cr, uid, erp_number, ast_server, context=context)
ast_number = self.convert_to_dial_number(erp_number)
# Add 'out prefix'
if ast_server.out_prefix:
_logger.debug('Out prefix = %s' % ast_server.out_prefix)
ast_number = '%s%s' % (ast_server.out_prefix, ast_number)
_logger.debug('Number to be sent to Asterisk = %s' % ast_number)
# The user should have a CallerID
if not user.callerid:

1
asterisk_click2dial_crm/__init__.py

@ -21,4 +21,3 @@
##############################################################################
from . import asterisk_click2dial_crm
from . import wizard

2
asterisk_click2dial_crm/__openerp__.py

@ -56,9 +56,7 @@ http://www.akretion.com/products-and-services/openerp-asterisk-voip-connector
'asterisk_click2dial',
'crm_phone',
],
"demo": [],
"data": [
'wizard/create_crm_phonecall_view.xml',
'res_users_view.xml',
],
"installable": True,

35
asterisk_click2dial_crm/asterisk_click2dial_crm.py

@ -20,45 +20,26 @@
#
##############################################################################
from openerp.osv import orm, fields
from openerp.tools.translate import _
from openerp import models, api, _
class phone_common(orm.AbstractModel):
class PhoneCommon(models.AbstractModel):
_inherit = 'phone.common'
def click2dial(self, cr, uid, erp_number, context=None):
@api.model
def click2dial(self, erp_number):
'''
Inherit the native click2dial function to trigger
a wizard "Create Call in CRM" via the Javascript code
of base_phone
'''
if context is None:
context = {}
res = super(phone_common, self).click2dial(
cr, uid, erp_number, context=context)
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
res = super(PhoneCommon, self).click2dial(erp_number)
if (
context.get('click2dial_model') == 'res.partner'
and user.context_propose_creation_crm_call):
self.env.context.get('click2dial_model') in
('res.partner', 'crm.lead') and
self.env.user.context_propose_creation_crm_call):
res.update({
'action_name': _('Create Call in CRM'),
'action_model': 'wizard.create.crm.phonecall',
})
return res
class res_users(orm.Model):
_inherit = "res.users"
_columns = {
# Field name starts with 'context_' to allow modification by the user
# in his preferences, cf server/openerp/addons/base/res/res_users.py
# in "def write()" of "class res_users(osv.osv)"
'context_propose_creation_crm_call': fields.boolean(
'Propose to create a call in CRM after a click2dial'),
}
_defaults = {
'context_propose_creation_crm_call': True,
}

23
asterisk_click2dial_crm/wizard/__init__.py

@ -1,23 +0,0 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2012-2014 Akretion (http://www.akretion.com/)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import create_crm_phonecall

61
asterisk_click2dial_crm/wizard/create_crm_phonecall.py

@ -1,61 +0,0 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2012-2014 Akretion (http://www.akretion.com)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm
class wizard_create_crm_phonecall(orm.TransientModel):
_name = "wizard.create.crm.phonecall"
def button_create_outgoing_phonecall(self, cr, uid, ids, context=None):
partner = self.pool['res.partner'].browse(
cr, uid, context.get('partner_id'), context=context)
return self._create_open_crm_phonecall(
cr, uid, partner, crm_categ='Outbound', context=context)
def _create_open_crm_phonecall(
self, cr, uid, partner, crm_categ, context=None):
if context is None:
context = {}
categ_ids = self.pool['crm.case.categ'].search(
cr, uid, [('name', '=', crm_categ)], context={'lang': 'en_US'})
case_section_ids = self.pool['crm.case.section'].search(
cr, uid, [('member_ids', 'in', uid)], context=context)
context.update({
'default_partner_id': partner.id or False,
'default_partner_phone': partner.phone,
'default_partner_mobile': partner.mobile,
'default_categ_id': categ_ids and categ_ids[0] or False,
'default_section_id':
case_section_ids and case_section_ids[0] or False,
})
return {
'name': partner.name,
'domain': [('partner_id', '=', partner.id)],
'res_model': 'crm.phonecall',
'view_mode': 'form,tree',
'type': 'ir.actions.act_window',
'nodestroy': False, # close the pop-up wizard after action
'target': 'current',
'context': context,
}

74
base_phone/base_phone.py

@ -19,8 +19,8 @@
#
##############################################################################
from openerp.osv import orm, fields
from openerp.tools.translate import _
from openerp import models, fields, api, _
from openerp.exceptions import Warning
import logging
# Lib for phone number reformating -> pip install phonenumbers
import phonenumbers
@ -28,7 +28,7 @@ import phonenumbers
_logger = logging.getLogger(__name__)
class phone_common(orm.AbstractModel):
class PhoneCommon(models.AbstractModel):
_name = 'phone.common'
def generic_phonenumber_to_e164(
@ -81,8 +81,7 @@ class phone_common(orm.AbstractModel):
# as second arg of phonenumbers.parse(), it will raise an
# exception when you try to enter a phone number in
# national format... so it's better to raise the exception here
raise orm.except_orm(
_('Error:'),
raise Warning(
_("You should set a country on the company '%s'")
% user.company_id.name)
for field in phonefields:
@ -107,8 +106,7 @@ class phone_common(orm.AbstractModel):
"Cannot reformat the phone number '%s' to "
"international format" % vals.get(field))
if context.get('raise_if_phone_parse_fails'):
raise orm.except_orm(
_('Error:'),
raise Warning(
_("Cannot reformat the phone number '%s' to "
"international format. Error message: %s")
% (vals.get(field), e))
@ -215,21 +213,44 @@ class phone_common(orm.AbstractModel):
modules, such as asterisk_click2dial'''
return {'dialed_number': erp_number}
@api.model
def convert_to_dial_number(self, erp_number):
'''
This function is dedicated to the transformation of the number
available in Odoo to the number that can be dialed.
You may have to inherit this function in another module specific
for your company if you are not happy with the way I reformat
the numbers.
'''
assert(erp_number), 'Missing phone number'
_logger.debug('Number before reformat = %s' % erp_number)
# erp_number are supposed to be in E.164 format, so no need to
# give a country code here
parsed_num = phonenumbers.parse(erp_number, None)
country_code = self.env.user.company_id.country_id.code
assert(country_code), 'Missing country on company'
_logger.debug('Country code = %s' % country_code)
to_dial_number = phonenumbers.format_out_of_country_calling_number(
parsed_num, country_code.upper())
to_dial_number = str(to_dial_number).translate(None, ' -.()/')
_logger.debug('Number to be sent to Asterisk = %s' % to_dial_number)
return to_dial_number
class res_partner(orm.Model):
class ResPartner(models.Model):
_name = 'res.partner'
_inherit = ['res.partner', 'phone.common']
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(res_partner, self).create(
return super(ResPartner, self).create(
cr, uid, vals_reformated, context=context)
def write(self, cr, uid, ids, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(res_partner, self).write(
return super(ResPartner, self).write(
cr, uid, ids, vals_reformated, context=context)
def name_get(self, cr, uid, ids, context=None):
@ -247,30 +268,25 @@ class res_partner(orm.Model):
res.append((partner.id, name))
return res
else:
return super(res_partner, self).name_get(
return super(ResPartner, self).name_get(
cr, uid, ids, context=context)
class res_company(orm.Model):
class ResCompany(models.Model):
_inherit = 'res.company'
_columns = {
'number_of_digits_to_match_from_end': fields.integer(
'Number of Digits To Match From End',
help="In several situations, OpenERP will have to find a "
"Partner/Lead/Employee/... from a phone number presented by the "
"calling party. As the phone numbers presented by your phone "
"operator may not always be displayed in a standard format, "
"the best method to find the related Partner/Lead/Employee/... "
"in OpenERP is to try to match the end of the phone number in "
"OpenERP with the N last digits of the phone number presented "
"by the calling party. N is the value you should enter in this "
"field."),
}
_defaults = {
'number_of_digits_to_match_from_end': 8,
}
number_of_digits_to_match_from_end = fields.Integer(
string='Number of Digits To Match From End',
default=8,
help="In several situations, OpenERP will have to find a "
"Partner/Lead/Employee/... from a phone number presented by the "
"calling party. As the phone numbers presented by your phone "
"operator may not always be displayed in a standard format, "
"the best method to find the related Partner/Lead/Employee/... "
"in OpenERP is to try to match the end of the phone number in "
"OpenERP with the N last digits of the phone number presented "
"by the calling party. N is the value you should enter in this "
"field.")
_sql_constraints = [(
'number_of_digits_to_match_from_end_positive',

10
base_phone/static/src/js/phone_widget.js

@ -57,11 +57,11 @@ openerp.base_phone = function (instance) {
_t('Click2dial successfull'),
_t('Number dialed:') + ' ' + r.dialed_number);
if (r.action_model) {
var context = {};
if (self.view.dataset.model == 'res.partner') {
context = {
'partner_id': self.view.datarecord.id};
}
var context = {
'click2dial_model': self.view.dataset.model,
'click2dial_id': self.view.datarecord.id,
'phone_number': phone_num,
};
var action = {
name: r.action_name,
type: 'ir.actions.act_window',

2
crm_phone/__openerp__.py

@ -45,7 +45,9 @@ for any help or question about this module.
'data': [
'security/ir.model.access.csv',
'crm_view.xml',
'res_users_view.xml',
'wizard/number_not_found_view.xml',
'wizard/create_crm_phonecall_view.xml',
],
'images': [],
'installable': True,

31
crm_phone/crm_phone.py

@ -20,23 +20,23 @@
#
##############################################################################
from openerp.osv import orm
from openerp import models, fields
class crm_lead(orm.Model):
class CrmLead(models.Model):
_name = 'crm.lead'
_inherit = ['crm.lead', 'phone.common']
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(crm_lead, self).create(
return super(CrmLead, self).create(
cr, uid, vals_reformated, context=context)
def write(self, cr, uid, ids, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(crm_lead, self).write(
return super(CrmLead, self).write(
cr, uid, ids, vals_reformated, context=context)
def name_get(self, cr, uid, ids, context=None):
@ -58,32 +58,32 @@ class crm_lead(orm.Model):
res.append((lead.id, name))
return res
else:
return super(crm_lead, self).name_get(
return super(CrmLead, self).name_get(
cr, uid, ids, context=context)
class crm_phonecall(orm.Model):
class CrmPhonecall(models.Model):
_name = 'crm.phonecall'
_inherit = ['crm.phonecall', 'phone.common']
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(crm_phonecall, self).create(
return super(CrmPhonecall, self).create(
cr, uid, vals_reformated, context=context)
def write(self, cr, uid, ids, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, vals, context=context)
return super(crm_phonecall, self).write(
return super(CrmPhonecall, self).write(
cr, uid, ids, vals_reformated, context=context)
class phone_common(orm.AbstractModel):
class PhoneCommon(models.AbstractModel):
_inherit = 'phone.common'
def _get_phone_fields(self, cr, uid, context=None):
res = super(phone_common, self)._get_phone_fields(
res = super(PhoneCommon, self)._get_phone_fields(
cr, uid, context=context)
res.update({
'crm.lead': {
@ -96,3 +96,14 @@ class phone_common(orm.AbstractModel):
},
})
return res
class ResUsers(models.Model):
_inherit = "res.users"
# Field name starts with 'context_' to allow modification by the user
# in his preferences, cf server/openerp/addons/base/res/res_users.py
# in "def write()" of "class res_users(osv.osv)"
context_propose_creation_crm_call = fields.Boolean(
string='Propose to create a call in CRM after a click2dial',
default=True)

8
asterisk_click2dial_crm/res_users_view.xml → crm_phone/res_users_view.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2014 Akretion (http://www.akretion.com/)
Copyright (C) 2012-2015 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
The licence is in the file __openerp__.py
-->
@ -14,7 +14,7 @@
<field name="model">res.users</field>
<field name="inherit_id" ref="base_phone.view_users_form"/>
<field name="arch" type="xml">
<!-- Phone tab is already visible thanks to asterisk_click2dial -->
<!-- Phone tab is already invisible, cf base_phone -->
<group name="phone-preferences" position="inside">
<field name="context_propose_creation_crm_call"/>
</group>
@ -26,12 +26,10 @@
<field name="model">res.users</field>
<field name="inherit_id" ref="base_phone.view_users_form_simple_modif" />
<field name="arch" type="xml">
<!-- phone group is already invisible, cf base_phone -->
<group name="phone" position="inside">
<field name="context_propose_creation_crm_call" readonly="0"/>
</group>
<group name="phone" position="attributes">
<attribute name="invisible">0</attribute>
</group>
</field>
</record>

1
crm_phone/wizard/__init__.py

@ -20,3 +20,4 @@
##############################################################################
from . import number_not_found
from . import create_crm_phonecall

73
crm_phone/wizard/create_crm_phonecall.py

@ -0,0 +1,73 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# CRM Phone module for Odoo
# Copyright (c) 2012-2015 Akretion (http://www.akretion.com)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, api, _
import phonenumbers
class wizard_create_crm_phonecall(models.TransientModel):
_name = "wizard.create.crm.phonecall"
@api.multi
def button_create_outgoing_phonecall(self):
self.ensure_one()
return self._create_open_crm_phonecall(crm_categ='Outbound')
@api.model
def _create_open_crm_phonecall(self, crm_categ):
categ = self.with_context(lang='en_US').env['crm.case.categ'].search(
[('name', '=', crm_categ)])
case_section = self.env['crm.case.section'].search(
[('member_ids', 'in', self._uid)])
action_ctx = self.env.context.copy()
action_ctx.update({
'default_categ_id': categ and categ[0].id or False,
'default_section_id':
case_section and case_section[0].id or False,
})
domain = False
if self.env.context.get('click2dial_model') == 'res.partner':
partner_id = self.env.context.get('click2dial_id')
action_ctx['default_partner_id'] = partner_id
domain = [('partner_id', '=', partner_id)]
elif self.env.context.get('click2dial_model') == 'crm.lead':
lead_id = self.env.context.get('click2dial_id')
action_ctx['default_opportunity_id'] = lead_id
domain = [('opportunity_id', '=', lead_id)]
parsed_num = phonenumbers.parse(self.env.context.get('phone_number'))
number_type = phonenumbers.number_type(parsed_num)
if number_type == 1:
action_ctx['default_partner_mobile'] =\
self.env.context.get('phone_number')
else:
action_ctx['default_partner_phone'] =\
self.env.context.get('phone_number')
return {
'name': _('Phone Call'),
'domain': domain,
'res_model': 'crm.phonecall',
'view_mode': 'form,tree',
'type': 'ir.actions.act_window',
'nodestroy': False, # close the pop-up wizard after action
'target': 'current',
'context': action_ctx,
}

0
asterisk_click2dial_crm/wizard/create_crm_phonecall_view.xml → crm_phone/wizard/create_crm_phonecall_view.xml

Loading…
Cancel
Save