From 1159a8ea2b477b76b4c30bb2e88aaa885d60ddaa Mon Sep 17 00:00:00 2001 From: "Trever L. Adams" Date: Fri, 5 Aug 2016 15:08:53 -0600 Subject: [PATCH] Initial port of FreeSWITCH functionality to Odoo 9. --- .travis.yml | 2 +- README.md | 3 +- base_phone_popup/popup.py | 24 + freeswitch_click2dial/README.rst | 138 ++++ freeswitch_click2dial/__init__.py | 4 + freeswitch_click2dial/__openerp__.py | 28 + freeswitch_click2dial/controller.py | 18 + .../freeswitch_click2dial.py | 401 ++++++++++++ .../freeswitch_click2dial_demo.xml | 39 ++ .../freeswitch_server_view.xml | 85 +++ freeswitch_click2dial/i18n/bg.po | 411 ++++++++++++ freeswitch_click2dial/i18n/de.po | 615 ++++++++++++++++++ freeswitch_click2dial/i18n/es.po | 449 +++++++++++++ freeswitch_click2dial/i18n/fr.po | 530 +++++++++++++++ .../i18n/freeswitch_click2dial.pot | 528 +++++++++++++++ freeswitch_click2dial/i18n/hr.po | 410 ++++++++++++ freeswitch_click2dial/i18n/pt_BR.po | 421 ++++++++++++ freeswitch_click2dial/requirements.txt | 1 + freeswitch_click2dial/res_users_view.xml | 51 ++ .../scripts/get_caller_name.py | 275 ++++++++ .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 5314 bytes .../static/src/css/freeswitch_click2dial.css | 26 + .../static/src/js/freeswitch_click2dial.js | 96 +++ .../static/src/xml/freeswitch_click2dial.xml | 24 + .../web_freeswitch_click2dial.xml | 19 + .../odoo_addons/__init__.py | 1 + .../odoo_addons/freeswitch_click2dial | 1 + setup/freeswitch_click2dial/setup.py | 6 + 29 files changed, 4607 insertions(+), 2 deletions(-) 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 setup/freeswitch_click2dial/odoo_addons/__init__.py create mode 120000 setup/freeswitch_click2dial/odoo_addons/freeswitch_click2dial create mode 100644 setup/freeswitch_click2dial/setup.py diff --git a/.travis.yml b/.travis.yml index 3238793..9a0213c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ install: - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - travis_install_nightly - - pip install phonenumbers py-Asterisk SOAPpy + - pip install phonenumbers py-Asterisk SOAPpy FreeSWITCH-ESL-Python - hg clone http://bitbucket.org/anybox/web_action_request -b ${VERSION} ${HOME}/web_action_request - if [ -d /home/travis/${ODOO_REPO##*/}-${VERSION}/addons ]; then ln -s ${HOME}/web_action_request/web_action_request /home/travis/${ODOO_REPO##*/}-${VERSION}/addons; fi - hg clone http://bitbucket.org/anybox/bus_enhanced -b ${VERSION} ${HOME}/bus_enhanced diff --git a/README.md b/README.md index 8e421a2..835357c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Odoo telephony connector -This projets aims at connecting Odoo to different phone systems. Phone systems currently supported are Asterisk (an OpenSource IPBX, cf [asterisk.org](http://www.asterisk.org/) and OVH (the centrex offer of OVH, cf the [OVH website](http://www.ovhtelecom.fr/telephonie/)). +This projets aims at connecting Odoo to different phone systems. Phone systems currently supported are Asterisk (an OpenSource IPBX, cf [asterisk.org](http://www.asterisk.org/), OVH (the centrex offer of OVH, cf the [OVH website](http://www.ovhtelecom.fr/telephonie/)) and FreeSWITCH (cross-platform multi-protocol soft switch, cf [FreeSWITCH.org](http://freeswitch.org)). This project provides: * a serie of modules (base\_phone, base\_phone\_popup, @@ -30,6 +30,7 @@ addon | version | summary [base_phone_popup](base_phone_popup/) | 9.0.1.0.0 | Pop-up the related form view to the user on incoming calls [crm_phone](crm_phone/) | 9.0.1.0.0 | Validate phone numbers in CRM [event_phone](event_phone/) | 9.0.1.0.0 | Validate phone numbers in Events +[freeswitch_click2dial](freeswitch_click2dial/) | 9.0.1.0.0 | FreeSWITCH-Odoo telephony connector (click2dial) [hr_phone](hr_phone/) | 9.0.1.0.0 | Validate phone numbers in HR [hr_recruitment_phone](hr_recruitment_phone/) | 9.0.1.0.0 | Validate phone numbers in HR Recruitment diff --git a/base_phone_popup/popup.py b/base_phone_popup/popup.py index b0fc561..0543723 100644 --- a/base_phone_popup/popup.py +++ b/base_phone_popup/popup.py @@ -61,6 +61,30 @@ class PhoneCommon(models.AbstractModel): callerid = False return callerid + @api.model + def incall_notify_by_extension(self, number, extension_list): + assert isinstance(extension_list, list), \ + 'extension_list must be a list' + res = self.get_record_from_phone_number(number) + users = self.env['res.users'].search( + [('internal_number', 'in', extension_list)]) + logger.debug( + 'Notify incoming call from number %s to users %s' + % (number, users.ids)) + action = self._prepare_incall_pop_action(res, number) + if action: + for user in users: + if user.context_incall_popup: + self.sudo(user.id).env['action.request'].notify(action) + logger.debug( + 'This action has been sent to user ID %d: %s' + % (user.id, action)) + if res: + callerid = res[2] + else: + callerid = False + return callerid + class ResUsers(models.Model): _inherit = 'res.users' diff --git a/freeswitch_click2dial/README.rst b/freeswitch_click2dial/README.rst new file mode 100644 index 0000000..69fe260 --- /dev/null +++ b/freeswitch_click2dial/README.rst @@ -0,0 +1,138 @@ +.. 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 Odoo, the user clicks on the *Dial* button next to a phone number + field in the partner view. + + * Odoo 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 Odoo 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 Odoo. To understand how to + use this, please see get_caller_name.py, which should be installed per the + instructions in the script on the Odoo/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, Odoo 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 Odoo. 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 + +Additionally, you will need the FreeSWITCH ESL python module. The easiest way is +pip install FreeSWITCH-ESL-Python. Otherwise, you will find it under +${FREESWITCH_SRC_TOP_DIR}/libs/esl/python. Go to +${FREESWITCH_SRC_TOP_DIR}/libs/esl. Type make. Then make pymod. You will then +need to install ${FREESWITCH_SRC_TOP_DIR}/libs/esl/python/ESL.py and +${FREESWITCH_SRC_TOP_DIR}/libs/esl/python/_ESL.so into the appropriate places +on your Odoo/Odoo server. +(https://wiki.freeswitch.org/wiki/Event_Socket_Library#Installation for more +information.) An alternative method would involve +https://github.com/gurteshwar/freeswitch-esl-python. + +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..01fb186 --- /dev/null +++ b/freeswitch_click2dial/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +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..94186bf --- /dev/null +++ b/freeswitch_click2dial/__openerp__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# © 2010-2016 Akretion (Alexis de Lattre ) +# © 2014-2016 Trever L. Adams +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +{ + 'name': 'FreeSWITCH Click2dial', + 'version': '9.0.1.0.0', + 'category': 'Phone', + 'license': 'AGPL-3', + 'summary': 'FreeSWITCH-Odoo connector', + 'author': "Trever L. Adams, Akretion,Odoo Community Association (OCA)", + 'website': 'https://github.com/treveradams/connector-telephony', + 'depends': ['base_phone'], + 'external_dependencies': {'python': ['freeswitchESL']}, + '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, +} diff --git a/freeswitch_click2dial/controller.py b/freeswitch_click2dial/controller.py new file mode 100644 index 0000000..c377f6e --- /dev/null +++ b/freeswitch_click2dial/controller.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# © 2014-2016 Akretion (Alexis de Lattre ) +# © 2015-2016 Juris Malinens (port to v9) +# © 2014-2016 Trever Adams +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import http + + +class FreeSWITCHClick2dialController(http.Controller): + _cp_path = '/freeswitch_click2dial' + + @http.route( + '/freeswitch_click2dial/get_record_from_my_channel/', + type='json', auth='public') + def get_record_from_my_channel(self, **kw): + res = http.request.env['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..d3ab48a --- /dev/null +++ b/freeswitch_click2dial/freeswitch_click2dial.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- +# © 2010-2016 Akretion (Alexis de Lattre ) +# © 2014-2016 Trever L. Adams +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from openerp import models, fields, api, _ +from openerp.exceptions import UserError, ValidationError +import logging +# from pprint import pformat +try: + from freeswitchESL import ESL +except ImportError: + import ESL +# import sys +import StringIO +import re +import json + +_logger = logging.getLogger(__name__) + + +class FreeSWITCHServer(models.Model): + '''FreeSWITCH server object, stores the parameters of the FreeSWITCH Servers''' + _name = "freeswitch.server" + _description = "FreeSWITCH Servers" + name = fields.Char(string='FreeSWITCH Server Name', required=True) + active = fields.Boolean( + string='Active', help="The active field allows you to hide the " + "FreeSWITCH server without deleting it.", default=True) + ip_address = fields.Char( + string='FreeSWITCH IP address or DNS', required=True, + help="IP address or DNS name of the FreeSWITCH server.") + port = fields.Integer( + string='Port', required=True, default=8021, + 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( + string='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( + string='Event Socket Password', 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( + string='Dialplan Context', required=True, default="XML default", + 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( + string='Wait Time (sec)', required=True, + help="Amount of time (in seconds) FreeSWITCH will try to reach " + "the user's phone before hanging up.", default=60) + alert_info = fields.Char( + string='Alert-Info SIP Header', + 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', string='Company', + default=lambda self: self.env['res.company']._company_default_get( + 'freeswitch.server'), + help="Company who uses the FreeSWITCH server.") + + @api.multi + @api.constrains( + 'out_prefix', 'wait_time', 'port', 'context', 'alert_info', 'password') + def _check_validity(self): + for server in self: + 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 ValidationError( + _("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 ValidationError( + _("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 ValidationError( + _("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 ValidationError( + _("The '%s' should only have ASCII caracters for " + "the FreeSWITCH server '%s'" + % (check_str[0], server.name))) + + @api.model + def _connect_to_freeswitch(self): + ''' + Open the connection to the FreeSWITCH Event Socket + Returns an instance of the FreeSWITCH Event Socket + + ''' + user = self.env.user + + fs_server = user.get_freeswitch_server_from_user() + # We check if the current user has a chan type + if not user.freeswitch_chan_type: + raise UserError( + _('No channel type configured for the current user.')) + + # We check if the current user has an internal number + if not user.resource: + raise UserError( + _('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 UserError( + _("Problem in the request from Odoo to FreeSWITCH. " + "Here is the error message: %s" % e)) + # return (False, False, False) + + return (user, fs_server, fs_manager) + + @api.multi + def test_es_connection(self): + self.ensure_one() + fs_manager = False + try: + fs_manager = ESL.ESLconnection( + str(self.ip_address), + str(self.port), str(self.password)) + except Exception, e: + raise UserError( + _("Connection Test Failed! The error message is: %s" % e)) + finally: + try: + if fs_manager.connected() is not 1: + raise UserError( + _("Connection Test Failed! Check Host, Port and Password")) + else: + fs_manager.disconnect() + except Exception, e: + pass + raise UserError(_( + "Connection Test Successfull! Odoo can successfully login to " + "the FreeSWITCH Event Socket.")) + + @api.model + def _get_calling_number(self): + user, fs_server, fs_manager = self._connect_to_freeswitch() + calling_party_number = False + try: + request = "channels like /" + re.sub(r'/', r':', user.resource) + \ + ("/" if user.freeswitch_chan_type == "FreeTDM" else "@") + \ + " as json" + ret = fs_manager.api('show', str(request)) + f = json.load(StringIO.StringIO(ret.getBody())) + if int(f['row_count']) > 0: + if (f['rows'][0]['initial_cid_name'] == 'Odoo Connector' or + f['rows'][0]['direction'] == 'inbound'): + calling_party_number = f['rows'][0]['dest'] + else: + calling_party_number = f['rows'][0]['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 UserError( + _("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 + + @api.model + def get_record_from_my_channel(self): + calling_number = self.env['freeswitch.server']._get_calling_number() + # calling_number = "0641981246" + if calling_number: + record = self.env['phone.common'].get_record_from_phone_number( + calling_number) + if record: + return record + else: + return calling_number + else: + return False + + +class ResUsers(models.Model): + _inherit = "res.users" + + internal_number = fields.Char( + string='Internal Number', + help="User's internal phone number.") + dial_suffix = fields.Char( + string='User-specific Dial Suffix', + help="User-specific dial suffix such as aa=2wb for SCCP " + "auto answer.") + callerid = fields.Char( + string='Caller ID', copy=False, + help="Caller ID used for the calls initiated by this user.") + cdraccount = fields.Char( + string='CDR Account', + help="Call Detail Record (CDR) account used for billing this " + "user.") + freeswitch_chan_type = fields.Selection([ + ('user', 'SIP'), + ('FreeTDM', 'FreeTDM'), + ('verto.rtc', 'Verto'), + ('skinny', 'Skinny'), + ('h323', 'H323'), + ('dingaling', 'XMPP/JINGLE'), + ('gsmopen', 'GSM SMS/Voice'), + ('skypeopen', 'SkypeOpen'), + ('Khomp', 'Khomp'), + ('opal', 'Opal Multi-protocol'), + ('portaudio', 'Portaudio'), + ], string='FreeSWITCH Channel Type', default='user', + help="FreeSWITCH channel type, as used in the FreeSWITCH " + "dialplan. If the user has a regular IP phone, the channel type " + "is 'SIP'. Use Verto for verto.rtc connections only if you " + "haven't added '${verto_contact ${dialed_user}@${dialed_domain}}' " + "to the default dial string. Otherwise, use SIP. (This better " + "allows for changes to the user directory and changes in type of " + "phone without the need for further changes in OpenERP/Odoo.)") + resource = fields.Char( + string='Resource Name', + help="Resource name for the channel type selected. For example, " + "if you use 'user/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. FreeTDM will be the span followed " + "by the port (i.e. 1/5).") + alert_info = fields.Char( + string='User-specific Alert-Info SIP Header', + 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( + string='User-specific Variable', + 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', string='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.") + + @api.multi + @api.constrains('resource', 'internal_number', 'callerid') + def _check_validity(self): + for user in self: + 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 ValidationError(_( + "The '%s' for the user '%s' should only have " + "ASCII caracters"), + check_string[0], user.name) + + @api.multi + def get_freeswitch_server_from_user(self): + '''Returns an freeswitch.server recordset''' + self.ensure_one() + # We check if the user has an FreeSWITCH server configured + if self.freeswitch_server_id.id: + fs_server = self.freeswitch_server_id + else: + freeswitch_servers = self.env['freeswitch.server'].search( + [('company_id', '=', self.company_id.id)]) + # If the user doesn't have an FreeSWITCH server, + # we take the first one of the user's company + if not freeswitch_servers: + raise UserError( + _("No FreeSWITCH server configured for the company '%s'.") + % self.company_id.name) + else: + fs_server = freeswitch_servers[0] + return fs_server + + +class PhoneCommon(models.AbstractModel): + _inherit = 'phone.common' + + @api.model + def click2dial(self, erp_number): + res = super(PhoneCommon, self).click2dial(erp_number) + if not erp_number: + raise UserError(_('Missing phone number')) + + user, fs_server, fs_manager = \ + self.env['freeswitch.server']._connect_to_freeswitch() + fs_number = self.convert_to_dial_number(erp_number) + # 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 UserError(_('No callerID configured for the current user')) + + variable = "" + if user.freeswitch_chan_type == 'user': + # 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 user/2005 1005 DP_TYPE DP_NAME + # 'Caller ID name showed to aleg' 90125 + dial_string = (('<' + variable + '>') if variable else '') + \ + channel + ' ' + fs_number + ' ' + fs_server.context + ' ' + \ + '\'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 UserError( + _("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..c40d26d --- /dev/null +++ b/freeswitch_click2dial/freeswitch_click2dial_demo.xml @@ -0,0 +1,39 @@ + + + + + + + + Akretion FreeSWITCH Server + freeswitch.akretion.com + 8021 + 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..b7f7364 --- /dev/null +++ b/freeswitch_click2dial/freeswitch_server_view.xml @@ -0,0 +1,85 @@ + + + + + + + + freeswitch.server.search + freeswitch.server + + + + + + + + + freeswitch.server.form + freeswitch.server + +
+ +
+
+ + + + + + + + +