From 8d4c7fa99bafd101bc8d7793067392a05474bd95 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 12 Mar 2015 12:43:05 +0100 Subject: [PATCH] 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 --- asterisk_click2dial/asterisk_click2dial.py | 40 ++-------- asterisk_click2dial_crm/__init__.py | 1 - asterisk_click2dial_crm/__openerp__.py | 2 - .../asterisk_click2dial_crm.py | 35 ++------- asterisk_click2dial_crm/wizard/__init__.py | 23 ------ .../wizard/create_crm_phonecall.py | 61 --------------- base_phone/base_phone.py | 74 +++++++++++-------- base_phone/static/src/js/phone_widget.js | 10 +-- crm_phone/__openerp__.py | 2 + crm_phone/crm_phone.py | 31 +++++--- .../res_users_view.xml | 8 +- crm_phone/wizard/__init__.py | 1 + crm_phone/wizard/create_crm_phonecall.py | 73 ++++++++++++++++++ .../wizard/create_crm_phonecall_view.xml | 0 14 files changed, 165 insertions(+), 196 deletions(-) delete mode 100644 asterisk_click2dial_crm/wizard/__init__.py delete mode 100644 asterisk_click2dial_crm/wizard/create_crm_phonecall.py rename {asterisk_click2dial_crm => crm_phone}/res_users_view.xml (80%) create mode 100644 crm_phone/wizard/create_crm_phonecall.py rename {asterisk_click2dial_crm => crm_phone}/wizard/create_crm_phonecall_view.xml (100%) diff --git a/asterisk_click2dial/asterisk_click2dial.py b/asterisk_click2dial/asterisk_click2dial.py index 125f910..84f90ab 100644 --- a/asterisk_click2dial/asterisk_click2dial.py +++ b/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: diff --git a/asterisk_click2dial_crm/__init__.py b/asterisk_click2dial_crm/__init__.py index c561ae1..a42e8f0 100644 --- a/asterisk_click2dial_crm/__init__.py +++ b/asterisk_click2dial_crm/__init__.py @@ -21,4 +21,3 @@ ############################################################################## from . import asterisk_click2dial_crm -from . import wizard diff --git a/asterisk_click2dial_crm/__openerp__.py b/asterisk_click2dial_crm/__openerp__.py index a46ac9e..1e3e3dc 100644 --- a/asterisk_click2dial_crm/__openerp__.py +++ b/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, diff --git a/asterisk_click2dial_crm/asterisk_click2dial_crm.py b/asterisk_click2dial_crm/asterisk_click2dial_crm.py index a77cef3..50e3065 100644 --- a/asterisk_click2dial_crm/asterisk_click2dial_crm.py +++ b/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, - } diff --git a/asterisk_click2dial_crm/wizard/__init__.py b/asterisk_click2dial_crm/wizard/__init__.py deleted file mode 100644 index 9f6c118..0000000 --- a/asterisk_click2dial_crm/wizard/__init__.py +++ /dev/null @@ -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 -# -# 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 . -# -############################################################################## - -from . import create_crm_phonecall diff --git a/asterisk_click2dial_crm/wizard/create_crm_phonecall.py b/asterisk_click2dial_crm/wizard/create_crm_phonecall.py deleted file mode 100644 index d9b9578..0000000 --- a/asterisk_click2dial_crm/wizard/create_crm_phonecall.py +++ /dev/null @@ -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 -# -# 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 . -# -############################################################################## - -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, - } diff --git a/base_phone/base_phone.py b/base_phone/base_phone.py index 51a70ee..2131978 100644 --- a/base_phone/base_phone.py +++ b/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', diff --git a/base_phone/static/src/js/phone_widget.js b/base_phone/static/src/js/phone_widget.js index 478a30f..5cbefa6 100644 --- a/base_phone/static/src/js/phone_widget.js +++ b/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', diff --git a/crm_phone/__openerp__.py b/crm_phone/__openerp__.py index 6b9233b..f2978bf 100644 --- a/crm_phone/__openerp__.py +++ b/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, diff --git a/crm_phone/crm_phone.py b/crm_phone/crm_phone.py index 481dfb3..42e61b0 100644 --- a/crm_phone/crm_phone.py +++ b/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) diff --git a/asterisk_click2dial_crm/res_users_view.xml b/crm_phone/res_users_view.xml similarity index 80% rename from asterisk_click2dial_crm/res_users_view.xml rename to crm_phone/res_users_view.xml index fa9641c..75589dc 100644 --- a/asterisk_click2dial_crm/res_users_view.xml +++ b/crm_phone/res_users_view.xml @@ -1,6 +1,6 @@ @@ -14,7 +14,7 @@ res.users - + @@ -26,12 +26,10 @@ res.users + - - 0 - diff --git a/crm_phone/wizard/__init__.py b/crm_phone/wizard/__init__.py index 1694fd8..967c5c8 100644 --- a/crm_phone/wizard/__init__.py +++ b/crm_phone/wizard/__init__.py @@ -20,3 +20,4 @@ ############################################################################## from . import number_not_found +from . import create_crm_phonecall diff --git a/crm_phone/wizard/create_crm_phonecall.py b/crm_phone/wizard/create_crm_phonecall.py new file mode 100644 index 0000000..f863b2e --- /dev/null +++ b/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 +# +# 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 . +# +############################################################################## + +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, + } diff --git a/asterisk_click2dial_crm/wizard/create_crm_phonecall_view.xml b/crm_phone/wizard/create_crm_phonecall_view.xml similarity index 100% rename from asterisk_click2dial_crm/wizard/create_crm_phonecall_view.xml rename to crm_phone/wizard/create_crm_phonecall_view.xml