From ab71a000cf1b361791305e91b6027a9b9e0a2e65 Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Wed, 30 Sep 2015 07:57:56 -0600 Subject: [PATCH] Add freeswitch_click2dial and freeswitch_click2dial_crm modules. --- base_phone_popup/popup.py | 25 + freeswitch_click2dial/README.rst | 127 ++++ freeswitch_click2dial/__init__.py | 23 + freeswitch_click2dial/__openerp__.py | 98 +++ freeswitch_click2dial/controller.py | 31 + .../freeswitch_click2dial.py | 459 +++++++++++++ .../freeswitch_click2dial_demo.xml | 38 ++ .../freeswitch_server_view.xml | 86 +++ freeswitch_click2dial/i18n/bg.po | 411 ++++++++++++ freeswitch_click2dial/i18n/de.po | 615 ++++++++++++++++++ freeswitch_click2dial/i18n/es.po | 448 +++++++++++++ freeswitch_click2dial/i18n/fr.po | 535 +++++++++++++++ .../i18n/freeswitch_click2dial.pot | 532 +++++++++++++++ freeswitch_click2dial/i18n/hr.po | 410 ++++++++++++ freeswitch_click2dial/i18n/pt_BR.po | 421 ++++++++++++ freeswitch_click2dial/requirements.txt | 3 + freeswitch_click2dial/res_users_view.xml | 40 ++ .../scripts/get_caller_name.py | 272 ++++++++ .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 5314 bytes .../static/src/css/freeswitch_click2dial.css | 27 + .../static/src/js/freeswitch_click2dial.js | 73 +++ .../static/src/xml/freeswitch_click2dial.xml | 25 + .../web_freeswitch_click2dial.xml | 22 + freeswitch_click2dial_crm/__init__.py | 21 + freeswitch_click2dial_crm/__openerp__.py | 52 ++ 26 files changed, 4797 insertions(+) create mode 100644 freeswitch_click2dial/README.rst create mode 100644 freeswitch_click2dial/__init__.py create mode 100644 freeswitch_click2dial/__openerp__.py create mode 100644 freeswitch_click2dial/controller.py create mode 100644 freeswitch_click2dial/freeswitch_click2dial.py create mode 100644 freeswitch_click2dial/freeswitch_click2dial_demo.xml create mode 100644 freeswitch_click2dial/freeswitch_server_view.xml create mode 100644 freeswitch_click2dial/i18n/bg.po create mode 100644 freeswitch_click2dial/i18n/de.po create mode 100644 freeswitch_click2dial/i18n/es.po create mode 100644 freeswitch_click2dial/i18n/fr.po create mode 100644 freeswitch_click2dial/i18n/freeswitch_click2dial.pot create mode 100644 freeswitch_click2dial/i18n/hr.po create mode 100644 freeswitch_click2dial/i18n/pt_BR.po create mode 100644 freeswitch_click2dial/requirements.txt create mode 100644 freeswitch_click2dial/res_users_view.xml create mode 100644 freeswitch_click2dial/scripts/get_caller_name.py create mode 100644 freeswitch_click2dial/security/ir.model.access.csv create mode 100644 freeswitch_click2dial/static/description/icon.png create mode 100644 freeswitch_click2dial/static/src/css/freeswitch_click2dial.css create mode 100644 freeswitch_click2dial/static/src/js/freeswitch_click2dial.js create mode 100644 freeswitch_click2dial/static/src/xml/freeswitch_click2dial.xml create mode 100644 freeswitch_click2dial/web_freeswitch_click2dial.xml create mode 100644 freeswitch_click2dial_crm/__init__.py create mode 100644 freeswitch_click2dial_crm/__openerp__.py diff --git a/base_phone_popup/popup.py b/base_phone_popup/popup.py index 47bf1c5..d679eeb 100644 --- a/base_phone_popup/popup.py +++ b/base_phone_popup/popup.py @@ -84,6 +84,31 @@ class phone_common(orm.AbstractModel): callerid = False return callerid + def incall_notify_by_extension( + self, cr, uid, number, extension_list, context=None): + assert isinstance(extension_list, list), 'extension_list must be a list' + res = self.get_record_from_phone_number( + cr, uid, number, context=context) + user_ids = self.pool['res.users'].search( + cr, uid, [('internal_number', 'in', extension_list)], context=context) + logger.debug( + 'Notify incoming call from number %s to users %s' + % (number, user_ids)) + action = self._prepare_incall_pop_action( + cr, uid, res, number, context=context) + if action: + users = self.pool['res.users'].read( + cr, uid, user_ids, ['context_incall_popup'], context=context) + for user in users: + if user['context_incall_popup']: + self.pool['action.request'].notify( + cr, user['id'], action) + if res: + callerid = res[2] + else: + callerid = False + return callerid + class res_users(orm.Model): _inherit = 'res.users' diff --git a/freeswitch_click2dial/README.rst b/freeswitch_click2dial/README.rst new file mode 100644 index 0000000..81ed12c --- /dev/null +++ b/freeswitch_click2dial/README.rst @@ -0,0 +1,127 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +===================== +FreeSWITCH Click2Dial +===================== + +The technical name of this module is *freeswitch_click2dial*, but this module +implements much more than a simple *click2dial* ! This module adds 3 +functionalities: + +1) It adds a *Dial* button in the partner form view so that users can directly + dial a phone number through FreeSWITCH. This feature is usually known as + *click2dial*. Here is how it works : + + * In OpenERP, the user clicks on the *Dial* button next to a phone number + field in the partner view. + + * OpenERP connects to the FreeSWITCH Event Socket and FreeSWITCH makes the + user's phone ring. + + * The user answers his own phone (if he doesn't, the process stops here). + + * FreeSWITCH dials the phone number found in OpenERP in place of the user. + + * If the remote party answers, the user can talk to his correspondent. + +2) It adds the ability to show the name of the calling party on the screen of + your IP phone on incoming phone calls if the presented phone number is + present in the partner/leads/employees/... of OpenERP. To understand how to + use this, please see get_caller_name.py, which should be installed per the + instructions in the script on the OpenERP/Odoo server. This works for + incoming and outgoing calls, per instructions in the script. + +3) It adds a phone icon (*Open Caller*) in the top menu bar + (next to the Preferences) to get the partner/lead/candidate/registrations + corresponding to the calling party in one click. Here is how it works : + + * When the user clicks on the phone icon, OpenERP sends a query to the + FreeSWITCH Manager Interface to get a list of the current phone calls + + * If it finds a phone call involving the user's phone, it gets the phone + number of the calling party + + * It searches the phone number of the calling party in the + Partners/Leads/Candidates/Registrations of OpenERP. If a record matches, + it takes you to the form view of this record. If no record matchs, it + opens a wizard which proposes to create a new Partner with the presented + phone number as *Phone* or *Mobile* number or update an existing Partner. + + It is possible to get a pop-up of the record corresponding to the calling + party without any action from the user via the module *base_phone_popup*. + +Installation +============ + +To install this module, you need to: + +* Click on the module and install it + +Configuration +============= + +To configure this module, you need to: + +* Settings > Technical > FreeSWITCH Servers +* Setup you server +* Configure users under Settings > Users > $USER > Telphony tab + +Usage +===== + +To use this module, you need to: + +* See scripts/get_caller_name.py to see how to set caller and callee name + +* Click on Dial next to any phone number covered by associated modules + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/{repo_id}/{branch} + +.. repo_id is available in https://github.com/OCA/maintainer-tools/blob/master/tools/repos_with_ids.txt +.. branch is "8.0" for example + +Known issues / Roadmap +====================== + +* None + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here `_. + + +Credits +======= + +Contributors +------------ + +* Firstname Lastname +* Second Person + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. + diff --git a/freeswitch_click2dial/__init__.py b/freeswitch_click2dial/__init__.py new file mode 100644 index 0000000..7d9205a --- /dev/null +++ b/freeswitch_click2dial/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# FreeSWITCH Click2Dial module for OpenERP +# Copyright (C) 2010-2013 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 freeswitch_click2dial +from . import controller diff --git a/freeswitch_click2dial/__openerp__.py b/freeswitch_click2dial/__openerp__.py new file mode 100644 index 0000000..7c9e926 --- /dev/null +++ b/freeswitch_click2dial/__openerp__.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# FreeSWITCH Click2dial module for OpenERP +# Copyright (C) 2010-2014 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 . +# +############################################################################## + + +{ + 'name': 'FreeSWITCH Click2dial', + 'version': '0.4', + 'category': 'Phone', + 'license': 'AGPL-3', + 'summary': 'FreeSWITCH-OpenERP connector', + 'description': """ +FreeSWITCH-OpenERP connector +========================== + +The technical name of this module is *freeswitch_click2dial*, but this module +implements much more than a simple *click2dial* ! This module adds 3 +functionalities: + +1) It adds a *Dial* button in the partner form view so that users can directly + dial a phone number through FreeSWITCH. This feature is usually known as + *click2dial*. Here is how it works : + + * In OpenERP, the user clicks on the *Dial* button next to a phone number + field in the partner view. + + * OpenERP connects to the FreeSWITCH Event Socket and FreeSWITCH makes the + user's phone ring. + + * The user answers his own phone (if he doesn't, the process stops here). + + * FreeSWITCH dials the phone number found in OpenERP in place of the user. + + * If the remote party answers, the user can talk to his correspondent. + +2) It adds the ability to show the name of the calling party on the screen of + your IP phone on incoming phone calls if the presented phone number is + present in the partner/leads/employees/... of OpenERP. To understand how to + use this, please see get_caller_name.py, which should be installed per the + instructions in the script on the OpenERP/Odoo server. This works for + incoming and outgoing calls, per instructions in the script. + +3) It adds a phone icon (*Open Caller*) in the top menu bar + (next to the Preferences) to get the partner/lead/candidate/registrations + corresponding to the calling party in one click. Here is how it works : + + * When the user clicks on the phone icon, OpenERP sends a query to the + FreeSWITCH Manager Interface to get a list of the current phone calls + + * If it finds a phone call involving the user's phone, it gets the phone + number of the calling party + + * It searches the phone number of the calling party in the + Partners/Leads/Candidates/Registrations of OpenERP. If a record matches, + it takes you to the form view of this record. If no record matchs, it + opens a wizard which proposes to create a new Partner with the presented + phone number as *Phone* or *Mobile* number or update an existing Partner. + + It is possible to get a pop-up of the record corresponding to the calling + party without any action from the user via the module *base_phone_popup*. + +A detailed documentation for this module is available on the Akretion Web site: +http://www.akretion.com/products-and-services/openerp-freeswitch-voip-connector +""", + 'author': "Trever L. Adams,Akretion,Odoo Community Association (OCA)", + 'website': 'https://github.com/treveradams/connector-telephony', + 'depends': ['base_phone'], + 'external_dependencies': {'python': ['ESL']}, + 'data': [ + 'freeswitch_server_view.xml', + 'res_users_view.xml', + 'security/ir.model.access.csv', + 'web_freeswitch_click2dial.xml', + ], + 'demo': ['freeswitch_click2dial_demo.xml'], + 'qweb': ['static/src/xml/*.xml'], + 'css': ['static/src/css/*.css'], + 'application': True, + 'installable': True, + 'active': False, +} diff --git a/freeswitch_click2dial/controller.py b/freeswitch_click2dial/controller.py new file mode 100644 index 0000000..9eff379 --- /dev/null +++ b/freeswitch_click2dial/controller.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# FreeSWITCH click2dial module for OpenERP +# Copyright (C) 2014 Alexis de Lattre (alexis@via.ecp.fr) +# +# 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 . +# +############################################################################## + +import openerp + + +class FreeSWITCHClick2dialController(openerp.addons.web.http.Controller): + _cp_path = '/freeswitch_click2dial' + + @openerp.addons.web.http.jsonrequest + def get_record_from_my_channel(self, req): + res = req.session.model('freeswitch.server').get_record_from_my_channel() + return res diff --git a/freeswitch_click2dial/freeswitch_click2dial.py b/freeswitch_click2dial/freeswitch_click2dial.py new file mode 100644 index 0000000..9c832a2 --- /dev/null +++ b/freeswitch_click2dial/freeswitch_click2dial.py @@ -0,0 +1,459 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# FreeSWITCH Click2dial module for OpenERP +# Copyright (C) 2014 Trever L. Adams +# Copyright (C) 2010-2013 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 fields, orm +from openerp.tools.translate import _ +import logging +import ESL +import sys +import csv +import StringIO +import re + +_logger = logging.getLogger(__name__) + + +class freeswitch_server(orm.Model): + '''FreeSWITCH server object, stores the parameters of the FreeSWITCH IPBXs''' + _name = "freeswitch.server" + _description = "FreeSWITCH Servers" + _columns = { + 'name': fields.char('FreeSWITCH Server Name', size=50, required=True), + 'active': fields.boolean( + 'Active', help="The active field allows you to hide the FreeSWITCH " + "server without deleting it."), + 'ip_address': fields.char( + 'FreeSWITCH IP address or DNS', size=50, required=True, + help="IP address or DNS name of the FreeSWITCH server."), + 'port': fields.integer( + 'Port', required=True, + help="TCP port on which the FreeSWITCH Event Socket listens. " + "Defined in /etc/freeswitch/autoload_configs/event_socket.conf.xml on FreeSWITCH."), + 'out_prefix': fields.char( + 'Out Prefix', size=4, help="Prefix to dial to make outgoing " + "calls. If you don't use a prefix to make outgoing calls, " + "leave empty."), + 'password': fields.char( + 'Event Socket Password', size=30, required=True, + help="Password that OpenERP will use to communicate with the " + "FreeSWITCH Event Socket. Refer to " + "/etc/freeswitch/autoload_configs/event_socket.conf.xml " + "on your FreeSWITCH server."), + 'context': fields.char( + 'Dialplan Context', size=50, required=True, + help="FreeSWITCH dialplan context from which the calls will be " + "made; e.g. 'XML default'. Refer to /etc/freeswitch/dialplan/* on your FreeSWITCH " + "server."), + 'wait_time': fields.integer( + 'Wait Time (sec)', required=True, + help="Amount of time (in seconds) FreeSWITCH will try to reach " + "the user's phone before hanging up."), + 'alert_info': fields.char( + 'Alert-Info SIP Header', size=255, + help="Set Alert-Info header in SIP request to user's IP Phone " + "for the click2dial feature. If empty, the Alert-Info header " + "will not be added. You can use it to have a special ring tone " + "for click2dial (a silent one !) or to activate auto-answer " + "for example."), + 'company_id': fields.many2one( + 'res.company', 'Company', + help="Company who uses the FreeSWITCH server."), + } + + _defaults = { + 'active': True, + 'port': 8021, # Default Event Socket port + 'context': 'XML default', + 'wait_time': 60, + 'company_id': lambda self, cr, uid, context: + self.pool['res.company']._company_default_get( + cr, uid, 'freeswitch.server', context=context), + } + + def _check_validity(self, cr, uid, ids): + for server in self.browse(cr, uid, ids): + out_prefix = ('Out prefix', server.out_prefix) + dialplan_context = ('Dialplan context', server.context) + alert_info = ('Alert-Info SIP header', server.alert_info) + password = ('Event Socket password', server.password) + + if out_prefix[1] and not out_prefix[1].isdigit(): + raise orm.except_orm( + _('Error:'), + _("Only use digits for the '%s' on the FreeSWITCH server " + "'%s'" % (out_prefix[0], server.name))) + if server.wait_time < 1 or server.wait_time > 120: + raise orm.except_orm( + _('Error:'), + _("You should set a 'Wait time' value between 1 and 120 " + "seconds for the FreeSWITCH server '%s'" % server.name)) + if server.port > 65535 or server.port < 1: + raise orm.except_orm( + _('Error:'), + _("You should set a TCP port between 1 and 65535 for the " + "FreeSWITCH server '%s'" % server.name)) + for check_str in [dialplan_context, alert_info, password]: + if check_str[1]: + try: + check_str[1].encode('ascii') + except UnicodeEncodeError: + raise orm.except_orm( + _('Error:'), + _("The '%s' should only have ASCII caracters for " + "the FreeSWITCH server '%s'" + % (check_str[0], server.name))) + return True + + _constraints = [( + _check_validity, + "Error message in raise", + [ + 'out_prefix', 'wait_time', 'port', + 'context', 'alert_info', 'password'] + )] + + def _get_freeswitch_server_from_user(self, cr, uid, context=None): + '''Returns an freeswitch.server browse object''' + # We check if the user has an FreeSWITCH server configured + user = self.pool['res.users'].browse(cr, uid, uid, context=context) + if user.freeswitch_server_id.id: + fs_server = user.freeswitch_server_id + else: + freeswitch_server_ids = self.search( + cr, uid, [('company_id', '=', user.company_id.id)], + context=context) + # If the user doesn't have an freeswitch server, + # we take the first one of the user's company + if not freeswitch_server_ids: + raise orm.except_orm( + _('Error:'), + _("No FreeSWITCH server configured for the company '%s'.") + % user.company_id.name) + else: + fs_server = self.browse( + cr, uid, freeswitch_server_ids[0], context=context) + servers = self.pool.get('freeswitch.server') + server_ids = servers.search(cr, uid, [('id', '=', fs_server.id)], context=context) + fake_fs_server = servers.browse(cr, uid, server_ids, context=context) + for rec in fake_fs_server: + fs_server = rec + break + return fs_server + + def _connect_to_freeswitch(self, cr, uid, context=None): + ''' + Open the connection to the FreeSWITCH Event Socket + Returns an instance of the FreeSWITCH Event Socket + + ''' + user = self.pool['res.users'].browse(cr, uid, uid, context=context) + + fs_server = self._get_freeswitch_server_from_user( + cr, uid, context=context) + # We check if the current user has a chan type + if not user.freeswitch_chan_type: + raise orm.except_orm( + _('Error:'), + _('No channel type configured for the current user.')) + + # We check if the current user has an internal number + if not user.resource: + raise orm.except_orm( + _('Error:'), + _('No resource name configured for the current user')) + + _logger.debug( + "User's phone: %s/%s" % (user.freeswitch_chan_type, user.resource)) + _logger.debug( + "FreeSWITCH server: %s:%d" + % (fs_server.ip_address, fs_server.port)) + + # Connect to the FreeSWITCH Event Socket + try: + fs_manager = ESL.ESLconnection(str(fs_server.ip_address), + str(fs_server.port), str(fs_server.password)) + except Exception, e: + _logger.error( + "Error in the request to the FreeSWITCH Event Socket %s" + % fs_server.ip_address) + _logger.error("Here is the error message: %s" % e) + raise orm.except_orm( + _('Error:'), + _("Problem in the request from OpenERP to FreeSWITCH. " + "Here is the error message: %s" % e)) + # return (False, False, False) + + return (user, fs_server, fs_manager) + + def test_es_connection(self, cr, uid, ids, context=None): + assert len(ids) == 1, 'Only 1 ID' + fs_server = self.browse(cr, uid, ids[0], context=context) + fs_manager = False + try: + fs_manager = ESL.ESLconnection(str(fs_server.ip_address), + str(fs_server.port), str(fs_server.password)) + except Exception, e: + raise orm.except_orm( + _("Connection Test Failed!"), + _("Here is the error message: %s" % e)) + finally: + if fs_manager.connected() is not 1: + raise orm.except_orm( + _("Connection Test Failed!"), + _("Check Host, Port and Password")) + else: + try: + if fs_manager: + fs_manager.disconnect() + raise orm.except_orm( + _("Connection Test Successfull!"), + _("OpenERP can successfully login to the FreeSWITCH Event " + "Socket.")) + + def _get_calling_number(self, cr, uid, context=None): + + user, fs_server, fs_manager = self._connect_to_freeswitch( + cr, uid, context=context) + calling_party_number = False + try: + ret = fs_manager.api('show', "calls as delim | like callee_cid_num " + + str(user.internal_number)) + f = StringIO.StringIO(ret.getBody()) + reader = csv.DictReader(f, delimiter='|') + for row in reader: + if "uuid" not in row or row["uuid"] == "" or row["uuid"] == "uuid": + break + if row["callstate"] not in ["EARLY", "ACTIVE", "RINGING"]: + continue + if row["b_cid_num"] and row["b_cid_num"] is not None: + calling_party_number = row["b_cid_num"] + elif row["cid_num"] and row["cid_num"] is not None: + calling_party_number = row["cid_num"] + except Exception, e: + _logger.error( + "Error in the Status request to FreeSWITCH server %s" + % fs_server.ip_address) + _logger.error( + "Here are the details of the error: '%s'" % unicode(e)) + raise orm.except_orm( + _('Error:'), + _("Can't get calling number from FreeSWITCH.\nHere is the " + "error: '%s'" % unicode(e))) + + finally: + fs_manager.disconnect() + + _logger.debug("Calling party number: '%s'" % calling_party_number) + return calling_party_number + + def get_record_from_my_channel(self, cr, uid, context=None): + calling_number = self.pool['freeswitch.server']._get_calling_number( + cr, uid, context=context) + # calling_number = "0641981246" + if calling_number: + record = self.pool['phone.common'].get_record_from_phone_number( + cr, uid, calling_number, context=context) + if record: + return record + else: + return calling_number + else: + return False + + +class res_users(orm.Model): + _inherit = "res.users" + + _columns = { + 'internal_number': fields.char( + 'Internal Number', size=15, + help="User's internal phone number."), + 'dial_suffix': fields.char( + 'User-specific Dial Suffix', size=15, + help="User-specific dial suffix such as aa=2wb for SCCP " + "auto answer."), + 'callerid': fields.char( + 'Caller ID', size=50, + help="Caller ID used for the calls initiated by this user."), + # You'd probably think: FreeSWITCH should reuse the callerID of sip.conf! + # But it cannot, cf + # http://lists.digium.com/pipermail/freeswitch-users/ + # 2012-January/269787.html + 'cdraccount': fields.char( + 'CDR Account', size=50, + help="Call Detail Record (CDR) account used for billing this " + "user."), + 'freeswitch_chan_type': fields.selection([ + ('user', 'SIP'), + ('FreeTDM', 'FreeTDM'), + ('Skinny', 'Skinny'), + ('MGCP', 'MGCP'), + ('mISDN', 'mISDN'), + ('H323', 'H323'), + ('SCCP', 'SCCP'), + ('Local', 'Local'), + ], 'FreeSWITCH Channel Type', + help="FreeSWITCH channel type, as used in the FreeSWITCH dialplan. " + "If the user has a regular IP phone, the channel type is 'SIP'."), + 'resource': fields.char( + 'Resource Name', size=64, + help="Resource name for the channel type selected. For example, " + "if you use 'Dial(SIP/phone1)' in your FreeSWITCH dialplan to ring " + "the SIP phone of this user, then the resource name for this user " + "is 'phone1'. For a SIP phone, the phone number is often used as " + "resource name, but not always."), + 'alert_info': fields.char( + 'User-specific Alert-Info SIP Header', size=255, + help="Set a user-specific Alert-Info header in SIP request to " + "user's IP Phone for the click2dial feature. If empty, the " + "Alert-Info header will not be added. You can use it to have a " + "special ring tone for click2dial (a silent one !) or to " + "activate auto-answer for example."), + 'variable': fields.char( + 'User-specific Variable', size=255, + help="Set a user-specific 'Variable' field in the FreeSWITCH " + "Event Socket 'originate' request for the click2dial " + "feature. If you want to have several variable headers, separate " + "them with '|'."), + 'freeswitch_server_id': fields.many2one( + 'freeswitch.server', 'FreeSWITCH Server', + help="FreeSWITCH server on which the user's phone is connected. " + "If you leave this field empty, it will use the first FreeSWITCH " + "server of the user's company."), + } + + _defaults = { + 'freeswitch_chan_type': 'SIP', + } + + def _check_validity(self, cr, uid, ids): + for user in self.browse(cr, uid, ids): + strings_to_check = [ + (_('Resource Name'), user.resource), + (_('Internal Number'), user.internal_number), + (_('Caller ID'), user.callerid), + ] + for check_string in strings_to_check: + if check_string[1]: + try: + check_string[1].encode('ascii') + except UnicodeEncodeError: + raise orm.except_orm( + _('Error:'), + _("The '%s' for the user '%s' should only have " + "ASCII caracters") + % (check_string[0], user.name)) + return True + + _constraints = [( + _check_validity, + "Error message in raise", + ['resource', 'internal_number', 'callerid'] + )] + + +class PhoneCommon(orm.AbstractModel): + _inherit = 'phone.common' + + def click2dial(self, cr, uid, erp_number, context=None): + res = super(PhoneCommon, self).click2dial( + cr, uid, erp_number, context=context) + if not erp_number: + raise orm.except_orm( + _('Error:'), + _('Missing phone number')) + + user, fs_server, fs_manager = \ + self.pool['freeswitch.server']._connect_to_freeswitch( + cr, uid, context=context) + fs_number = self.convert_to_dial_number( + cr, uid, erp_number, context=context) + # Add 'out prefix' + if fs_server.out_prefix: + _logger.debug('Out prefix = %s' % fs_server.out_prefix) + fs_number = '%s%s' % (fs_server.out_prefix, fs_number) + _logger.debug('Number to be sent to FreeSWITCH = %s' % fs_number) + + # The user should have a CallerID + if not user.callerid: + raise orm.except_orm( + _('Error:'), + _('No callerID configured for the current user')) + + variable = "" + if user.freeswitch_chan_type == 'SIP': + # We can only have one alert-info header in a SIP request + if user.alert_info: + variable += 'alert_info=' + user.alert_info + elif fs_server.alert_info: + variable += 'alert_info=' + fs_server.alert_info + if user.variable: + for user_variable in user.variable.split('|'): + if len(variable) and len(user_variable): + variable += ',' + variable += user_variable.strip() + if user.callerid: + caller_name = re.search(r'([^<]*).*', user.callerid).group(1).strip() + caller_number = re.search(r'.*<(.*)>.*', user.callerid).group(1).strip() + if caller_name: + if len(variable): + variable += ',' + caller_name = caller_name.replace(",", r"\,") + variable += 'effective_caller_id_name=' + caller_name + if caller_number: + if len(variable): + variable += ',' + variable += 'effective_caller_id_number=' + caller_number + if fs_server.wait_time != 60: + if len(variable): + variable += ',' + variable += 'ignore_early_media=true' + ',' + variable += 'originate_timeout=' + str(fs_server.wait_time) + channel = '%s/%s' % (user.freeswitch_chan_type, user.resource) + if user.dial_suffix: + channel += '/%s' % user.dial_suffix + + try: + # originate FreeTDM/1/3 1234567 XML Internal-FXS + # originate user/2005 1003 XML Internal-FXS + # originate user/2005 1005 XML Internal-FXS 'Caller ID showed to OpenERP user' 90125 + dial_string = (('<' + variable + '>') if variable else '') + \ + channel + ' ' + fs_number + ' ' + fs_server.context + ' ' + \ + '\'FreeSWITCH/Odoo Connector\' ' + fs_number + # raise orm.except_orm(_('Error :'), dial_string) + fs_manager.api('originate', dial_string.encode("ascii")) + except Exception, e: + _logger.error( + "Error in the Originate request to FreeSWITCH server %s" + % fs_server.ip_address) + _logger.error( + "Here are the details of the error: '%s'" % unicode(e)) + raise orm.except_orm( + _('Error:'), + _("Click to dial with FreeSWITCH failed.\nHere is the error: " + "'%s'") + % unicode(e)) + finally: + fs_manager.disconnect() + + res['dialed_number'] = fs_number + return res diff --git a/freeswitch_click2dial/freeswitch_click2dial_demo.xml b/freeswitch_click2dial/freeswitch_click2dial_demo.xml new file mode 100644 index 0000000..225439a --- /dev/null +++ b/freeswitch_click2dial/freeswitch_click2dial_demo.xml @@ -0,0 +1,38 @@ + + + + + + + Akretion FreeSWITCH IPBX + freeswitch.akretion.com + click2dial + mypassword + from-internal + info=<Bellcore-dr5> + + + + 11 + 11 + Administrator <0141981242> + + + + 12 + 12 + Demo user <0141984212> + + + + + + + + diff --git a/freeswitch_click2dial/freeswitch_server_view.xml b/freeswitch_click2dial/freeswitch_server_view.xml new file mode 100644 index 0000000..efcd99c --- /dev/null +++ b/freeswitch_click2dial/freeswitch_server_view.xml @@ -0,0 +1,86 @@ + + + + + + + + + freeswitch.server.search + freeswitch.server + + + + + + + + + + freeswitch.server.form + freeswitch.server + +
+ +
+
+ + + + + + + + +