|
@ -1,31 +1,13 @@ |
|
|
# -*- encoding: utf-8 -*- |
|
|
|
|
|
############################################################################## |
|
|
|
|
|
# |
|
|
|
|
|
# Asterisk Click2dial module for OpenERP |
|
|
|
|
|
# Copyright (C) 2010-2013 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 <http://www.gnu.org/licenses/>. |
|
|
|
|
|
# |
|
|
|
|
|
############################################################################## |
|
|
|
|
|
|
|
|
|
|
|
from openerp.osv import fields, orm |
|
|
|
|
|
from openerp.tools.translate import _ |
|
|
|
|
|
|
|
|
# -*- coding: utf-8 -*- |
|
|
|
|
|
# © 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) |
|
|
|
|
|
# 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 |
|
|
import logging |
|
|
|
|
|
|
|
|
try: |
|
|
try: |
|
|
# Lib py-asterisk from http://code.google.com/p/py-asterisk/ |
|
|
|
|
|
# -> pip install py-Asterisk |
|
|
|
|
|
|
|
|
# pip install py-Asterisk |
|
|
from Asterisk import Manager |
|
|
from Asterisk import Manager |
|
|
except ImportError: |
|
|
except ImportError: |
|
|
Manager = None |
|
|
Manager = None |
|
@ -33,73 +15,66 @@ except ImportError: |
|
|
_logger = logging.getLogger(__name__) |
|
|
_logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class asterisk_server(orm.Model): |
|
|
|
|
|
|
|
|
class AsteriskServer(models.Model): |
|
|
'''Asterisk server object, stores the parameters of the Asterisk IPBXs''' |
|
|
'''Asterisk server object, stores the parameters of the Asterisk IPBXs''' |
|
|
_name = "asterisk.server" |
|
|
_name = "asterisk.server" |
|
|
_description = "Asterisk Servers" |
|
|
_description = "Asterisk Servers" |
|
|
_columns = { |
|
|
|
|
|
'name': fields.char('Asterisk Server Name', size=50, required=True), |
|
|
|
|
|
'active': fields.boolean( |
|
|
|
|
|
'Active', help="The active field allows you to hide the Asterisk " |
|
|
|
|
|
"server without deleting it."), |
|
|
|
|
|
'ip_address': fields.char( |
|
|
|
|
|
'Asterisk IP address or DNS', size=50, required=True, |
|
|
|
|
|
help="IP address or DNS name of the Asterisk server."), |
|
|
|
|
|
'port': fields.integer( |
|
|
|
|
|
'Port', required=True, |
|
|
|
|
|
help="TCP port on which the Asterisk Manager Interface listens. " |
|
|
|
|
|
"Defined in /etc/asterisk/manager.conf on Asterisk."), |
|
|
|
|
|
'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."), |
|
|
|
|
|
'login': fields.char( |
|
|
|
|
|
'AMI Login', size=30, required=True, |
|
|
|
|
|
help="Login that OpenERP will use to communicate with the " |
|
|
|
|
|
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf " |
|
|
|
|
|
"on your Asterisk server."), |
|
|
|
|
|
'password': fields.char( |
|
|
|
|
|
'AMI Password', size=30, required=True, |
|
|
|
|
|
help="Password that OpenERP will use to communicate with the " |
|
|
|
|
|
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf " |
|
|
|
|
|
"on your Asterisk server."), |
|
|
|
|
|
'context': fields.char( |
|
|
|
|
|
'Dialplan Context', size=50, required=True, |
|
|
|
|
|
help="Asterisk dialplan context from which the calls will be " |
|
|
|
|
|
"made. Refer to /etc/asterisk/extensions.conf on your Asterisk " |
|
|
|
|
|
"server."), |
|
|
|
|
|
'wait_time': fields.integer( |
|
|
|
|
|
'Wait Time (sec)', required=True, |
|
|
|
|
|
help="Amount of time (in seconds) Asterisk will try to reach " |
|
|
|
|
|
"the user's phone before hanging up."), |
|
|
|
|
|
'extension_priority': fields.integer( |
|
|
|
|
|
'Extension Priority', required=True, |
|
|
|
|
|
help="Priority of the extension in the Asterisk dialplan. Refer " |
|
|
|
|
|
"to /etc/asterisk/extensions.conf on your Asterisk server."), |
|
|
|
|
|
'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 Asterisk server."), |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_defaults = { |
|
|
|
|
|
'active': True, |
|
|
|
|
|
'port': 5038, # Default AMI port |
|
|
|
|
|
'extension_priority': 1, |
|
|
|
|
|
'wait_time': 15, |
|
|
|
|
|
'company_id': lambda self, cr, uid, context: |
|
|
|
|
|
self.pool['res.company']._company_default_get( |
|
|
|
|
|
cr, uid, 'asterisk.server', context=context), |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def _check_validity(self, cr, uid, ids): |
|
|
|
|
|
for server in self.browse(cr, uid, ids): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = fields.Char(string='Asterisk Server Name', required=True) |
|
|
|
|
|
active = fields.Boolean( |
|
|
|
|
|
string='Active', default=True) |
|
|
|
|
|
ip_address = fields.Char( |
|
|
|
|
|
string='Asterisk IP address or DNS', required=True) |
|
|
|
|
|
port = fields.Integer( |
|
|
|
|
|
string='Port', required=True, default=5038, |
|
|
|
|
|
help="TCP port on which the Asterisk Manager Interface listens. " |
|
|
|
|
|
"Defined in /etc/asterisk/manager.conf on Asterisk.") |
|
|
|
|
|
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.") |
|
|
|
|
|
login = fields.Char( |
|
|
|
|
|
string='AMI Login', required=True, |
|
|
|
|
|
help="Login that Odoo will use to communicate with the " |
|
|
|
|
|
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf " |
|
|
|
|
|
"on your Asterisk server.") |
|
|
|
|
|
password = fields.Char( |
|
|
|
|
|
string='AMI Password', required=True, |
|
|
|
|
|
help="Password that Odoo will use to communicate with the " |
|
|
|
|
|
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf " |
|
|
|
|
|
"on your Asterisk server.") |
|
|
|
|
|
context = fields.Char( |
|
|
|
|
|
string='Dialplan Context', required=True, |
|
|
|
|
|
help="Asterisk dialplan context from which the calls will be " |
|
|
|
|
|
"made. Refer to /etc/asterisk/extensions.conf on your Asterisk " |
|
|
|
|
|
"server.") |
|
|
|
|
|
wait_time = fields.Integer( |
|
|
|
|
|
string='Wait Time', required=True, default=15, |
|
|
|
|
|
help="Amount of time (in seconds) Asterisk will try to reach " |
|
|
|
|
|
"the user's phone before hanging up.") |
|
|
|
|
|
extension_priority = fields.Integer( |
|
|
|
|
|
string='Extension Priority', required=True, default=1, |
|
|
|
|
|
help="Priority of the extension in the Asterisk dialplan. Refer " |
|
|
|
|
|
"to /etc/asterisk/extensions.conf on your Asterisk server.") |
|
|
|
|
|
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( |
|
|
|
|
|
'asterisk.server'), |
|
|
|
|
|
help="Company who uses the Asterisk server.") |
|
|
|
|
|
|
|
|
|
|
|
@api.multi |
|
|
|
|
|
@api.constrains( |
|
|
|
|
|
'out_prefix', 'wait_time', 'extension_priority', 'port', |
|
|
|
|
|
'context', 'alert_info', 'login', 'password') |
|
|
|
|
|
def _check_validity(self): |
|
|
|
|
|
for server in self: |
|
|
out_prefix = ('Out prefix', server.out_prefix) |
|
|
out_prefix = ('Out prefix', server.out_prefix) |
|
|
dialplan_context = ('Dialplan context', server.context) |
|
|
dialplan_context = ('Dialplan context', server.context) |
|
|
alert_info = ('Alert-Info SIP header', server.alert_info) |
|
|
alert_info = ('Alert-Info SIP header', server.alert_info) |
|
@ -107,23 +82,19 @@ class asterisk_server(orm.Model): |
|
|
password = ('AMI password', server.password) |
|
|
password = ('AMI password', server.password) |
|
|
|
|
|
|
|
|
if out_prefix[1] and not out_prefix[1].isdigit(): |
|
|
if out_prefix[1] and not out_prefix[1].isdigit(): |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise ValidationError( |
|
|
_("Only use digits for the '%s' on the Asterisk server " |
|
|
_("Only use digits for the '%s' on the Asterisk server " |
|
|
"'%s'" % (out_prefix[0], server.name))) |
|
|
"'%s'" % (out_prefix[0], server.name))) |
|
|
if server.wait_time < 1 or server.wait_time > 120: |
|
|
if server.wait_time < 1 or server.wait_time > 120: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise ValidationError( |
|
|
_("You should set a 'Wait time' value between 1 and 120 " |
|
|
_("You should set a 'Wait time' value between 1 and 120 " |
|
|
"seconds for the Asterisk server '%s'" % server.name)) |
|
|
"seconds for the Asterisk server '%s'" % server.name)) |
|
|
if server.extension_priority < 1: |
|
|
if server.extension_priority < 1: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise ValidationError( |
|
|
_("The 'extension priority' must be a positive value for " |
|
|
_("The 'extension priority' must be a positive value for " |
|
|
"the Asterisk server '%s'" % server.name)) |
|
|
"the Asterisk server '%s'" % server.name)) |
|
|
if server.port > 65535 or server.port < 1: |
|
|
if server.port > 65535 or server.port < 1: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise ValidationError( |
|
|
_("You should set a TCP port between 1 and 65535 for the " |
|
|
_("You should set a TCP port between 1 and 65535 for the " |
|
|
"Asterisk server '%s'" % server.name)) |
|
|
"Asterisk server '%s'" % server.name)) |
|
|
for check_str in [dialplan_context, alert_info, login, password]: |
|
|
for check_str in [dialplan_context, alert_info, login, password]: |
|
@ -131,70 +102,34 @@ class asterisk_server(orm.Model): |
|
|
try: |
|
|
try: |
|
|
check_str[1].encode('ascii') |
|
|
check_str[1].encode('ascii') |
|
|
except UnicodeEncodeError: |
|
|
except UnicodeEncodeError: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise ValidationError( |
|
|
_("The '%s' should only have ASCII caracters for " |
|
|
_("The '%s' should only have ASCII caracters for " |
|
|
"the Asterisk server '%s'" |
|
|
"the Asterisk server '%s'" |
|
|
% (check_str[0], server.name))) |
|
|
% (check_str[0], server.name))) |
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
_constraints = [( |
|
|
|
|
|
_check_validity, |
|
|
|
|
|
"Error message in raise", |
|
|
|
|
|
[ |
|
|
|
|
|
'out_prefix', 'wait_time', 'extension_priority', 'port', |
|
|
|
|
|
'context', 'alert_info', 'login', 'password'] |
|
|
|
|
|
)] |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
user = self.pool['res.users'].browse(cr, uid, uid, context=context) |
|
|
|
|
|
if user.asterisk_server_id.id: |
|
|
|
|
|
ast_server = user.asterisk_server_id |
|
|
|
|
|
else: |
|
|
|
|
|
asterisk_server_ids = self.search( |
|
|
|
|
|
cr, uid, [('company_id', '=', user.company_id.id)], |
|
|
|
|
|
context=context) |
|
|
|
|
|
# If the user doesn't have an asterisk server, |
|
|
|
|
|
# we take the first one of the user's company |
|
|
|
|
|
if not asterisk_server_ids: |
|
|
|
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
_("No Asterisk server configured for the company '%s'.") |
|
|
|
|
|
% user.company_id.name) |
|
|
|
|
|
else: |
|
|
|
|
|
ast_server = self.browse( |
|
|
|
|
|
cr, uid, asterisk_server_ids[0], context=context) |
|
|
|
|
|
return ast_server |
|
|
|
|
|
|
|
|
|
|
|
def _connect_to_asterisk(self, cr, uid, context=None): |
|
|
|
|
|
|
|
|
@api.model |
|
|
|
|
|
def _connect_to_asterisk(self): |
|
|
''' |
|
|
''' |
|
|
Open the connection to the Asterisk Manager |
|
|
Open the connection to the Asterisk Manager |
|
|
Returns an instance of the Asterisk Manager |
|
|
Returns an instance of the Asterisk Manager |
|
|
|
|
|
|
|
|
''' |
|
|
''' |
|
|
user = self.pool['res.users'].browse(cr, uid, uid, context=context) |
|
|
|
|
|
|
|
|
|
|
|
ast_server = self._get_asterisk_server_from_user( |
|
|
|
|
|
cr, uid, context=context) |
|
|
|
|
|
|
|
|
user = self.env.user |
|
|
|
|
|
ast_server = user.get_asterisk_server_from_user() |
|
|
# We check if the current user has a chan type |
|
|
# We check if the current user has a chan type |
|
|
if not user.asterisk_chan_type: |
|
|
if not user.asterisk_chan_type: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise UserError( |
|
|
_('No channel type configured for the current user.')) |
|
|
_('No channel type configured for the current user.')) |
|
|
|
|
|
|
|
|
# We check if the current user has an internal number |
|
|
# We check if the current user has an internal number |
|
|
if not user.resource: |
|
|
if not user.resource: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
raise UserError( |
|
|
_('No resource name configured for the current user')) |
|
|
_('No resource name configured for the current user')) |
|
|
|
|
|
|
|
|
_logger.debug( |
|
|
_logger.debug( |
|
|
"User's phone: %s/%s" % (user.asterisk_chan_type, user.resource)) |
|
|
|
|
|
|
|
|
"User's phone: %s/%s", user.asterisk_chan_type, user.resource) |
|
|
_logger.debug( |
|
|
_logger.debug( |
|
|
"Asterisk server: %s:%d" |
|
|
|
|
|
% (ast_server.ip_address, ast_server.port)) |
|
|
|
|
|
|
|
|
"Asterisk server: %s:%d", ast_server.ip_address, ast_server.port) |
|
|
|
|
|
|
|
|
# Connect to the Asterisk Manager Interface |
|
|
# Connect to the Asterisk Manager Interface |
|
|
try: |
|
|
try: |
|
@ -203,41 +138,38 @@ class asterisk_server(orm.Model): |
|
|
ast_server.login, ast_server.password) |
|
|
ast_server.login, ast_server.password) |
|
|
except Exception, e: |
|
|
except Exception, e: |
|
|
_logger.error( |
|
|
_logger.error( |
|
|
"Error in the request to the Asterisk Manager Interface %s" |
|
|
|
|
|
% ast_server.ip_address) |
|
|
|
|
|
_logger.error("Here is the error message: %s" % e) |
|
|
|
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
_("Problem in the request from OpenERP to Asterisk. " |
|
|
|
|
|
|
|
|
"Error in the request to the Asterisk Manager Interface %s", |
|
|
|
|
|
ast_server.ip_address) |
|
|
|
|
|
_logger.error("Here is the error message: %s", e) |
|
|
|
|
|
raise UserError( |
|
|
|
|
|
_("Problem in the request from Odoo to Asterisk. " |
|
|
"Here is the error message: %s" % e)) |
|
|
"Here is the error message: %s" % e)) |
|
|
|
|
|
|
|
|
return (user, ast_server, ast_manager) |
|
|
return (user, ast_server, ast_manager) |
|
|
|
|
|
|
|
|
def test_ami_connection(self, cr, uid, ids, context=None): |
|
|
|
|
|
assert len(ids) == 1, 'Only 1 ID' |
|
|
|
|
|
ast_server = self.browse(cr, uid, ids[0], context=context) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
|
|
|
def test_ami_connection(self): |
|
|
|
|
|
self.ensure_one() |
|
|
ast_manager = False |
|
|
ast_manager = False |
|
|
try: |
|
|
try: |
|
|
ast_manager = Manager.Manager( |
|
|
ast_manager = Manager.Manager( |
|
|
(ast_server.ip_address, ast_server.port), |
|
|
|
|
|
ast_server.login, |
|
|
|
|
|
ast_server.password) |
|
|
|
|
|
|
|
|
(self.ip_address, self.port), |
|
|
|
|
|
self.login, |
|
|
|
|
|
self.password) |
|
|
except Exception, e: |
|
|
except Exception, e: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_("Connection Test Failed!"), |
|
|
|
|
|
_("Here is the error message: %s" % e)) |
|
|
|
|
|
|
|
|
raise UserError( |
|
|
|
|
|
_("Connection Test Failed! The error message is: %s" % e)) |
|
|
finally: |
|
|
finally: |
|
|
if ast_manager: |
|
|
if ast_manager: |
|
|
ast_manager.Logoff() |
|
|
ast_manager.Logoff() |
|
|
raise orm.except_orm( |
|
|
|
|
|
_("Connection Test Successfull!"), |
|
|
|
|
|
_("Odoo can successfully login to the Asterisk Manager " |
|
|
|
|
|
"Interface.")) |
|
|
|
|
|
|
|
|
raise UserError(_( |
|
|
|
|
|
"Connection Test Successfull! Odoo can successfully login to " |
|
|
|
|
|
"the Asterisk Manager Interface.")) |
|
|
|
|
|
|
|
|
def _get_calling_number(self, cr, uid, context=None): |
|
|
|
|
|
|
|
|
@api.model |
|
|
|
|
|
def _get_calling_number(self): |
|
|
|
|
|
|
|
|
user, ast_server, ast_manager = self._connect_to_asterisk( |
|
|
|
|
|
cr, uid, context=context) |
|
|
|
|
|
|
|
|
user, ast_server, ast_manager = self._connect_to_asterisk() |
|
|
calling_party_number = False |
|
|
calling_party_number = False |
|
|
try: |
|
|
try: |
|
|
list_chan = ast_manager.Status() |
|
|
list_chan = ast_manager.Status() |
|
@ -255,42 +187,41 @@ class asterisk_server(orm.Model): |
|
|
break |
|
|
break |
|
|
# 6 = Up |
|
|
# 6 = Up |
|
|
if ( |
|
|
if ( |
|
|
chan.get('ChannelState') == '6' |
|
|
|
|
|
and sip_account in chan.get('BridgedChannel', '')): |
|
|
|
|
|
|
|
|
chan.get('ChannelState') == '6' and |
|
|
|
|
|
sip_account in chan.get('BridgedChannel', '')): |
|
|
_logger.debug("Found a matching Event in 'Up' state") |
|
|
_logger.debug("Found a matching Event in 'Up' state") |
|
|
calling_party_number = chan.get('CallerIDNum') |
|
|
calling_party_number = chan.get('CallerIDNum') |
|
|
break |
|
|
break |
|
|
# Compatibility with Asterisk 1.4 |
|
|
# Compatibility with Asterisk 1.4 |
|
|
if ( |
|
|
if ( |
|
|
chan.get('State') == 'Up' |
|
|
|
|
|
and sip_account in chan.get('Link', '')): |
|
|
|
|
|
|
|
|
chan.get('State') == 'Up' and |
|
|
|
|
|
sip_account in chan.get('Link', '')): |
|
|
_logger.debug("Found a matching Event in 'Up' state") |
|
|
_logger.debug("Found a matching Event in 'Up' state") |
|
|
calling_party_number = chan.get('CallerIDNum') |
|
|
calling_party_number = chan.get('CallerIDNum') |
|
|
break |
|
|
break |
|
|
except Exception, e: |
|
|
except Exception, e: |
|
|
_logger.error( |
|
|
_logger.error( |
|
|
"Error in the Status request to Asterisk server %s" |
|
|
|
|
|
% ast_server.ip_address) |
|
|
|
|
|
|
|
|
"Error in the Status request to Asterisk server %s", |
|
|
|
|
|
ast_server.ip_address) |
|
|
_logger.error( |
|
|
_logger.error( |
|
|
"Here are the details of the error: '%s'" % unicode(e)) |
|
|
|
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
"Here are the details of the error: '%s'", unicode(e)) |
|
|
|
|
|
raise UserError( |
|
|
_("Can't get calling number from Asterisk.\nHere is the " |
|
|
_("Can't get calling number from Asterisk.\nHere is the " |
|
|
"error: '%s'" % unicode(e))) |
|
|
"error: '%s'" % unicode(e))) |
|
|
|
|
|
|
|
|
finally: |
|
|
finally: |
|
|
ast_manager.Logoff() |
|
|
ast_manager.Logoff() |
|
|
|
|
|
|
|
|
_logger.debug("Calling party number: '%s'" % calling_party_number) |
|
|
|
|
|
|
|
|
_logger.debug("Calling party number: '%s'", calling_party_number) |
|
|
return calling_party_number |
|
|
return calling_party_number |
|
|
|
|
|
|
|
|
def get_record_from_my_channel(self, cr, uid, context=None): |
|
|
|
|
|
calling_number = self.pool['asterisk.server']._get_calling_number( |
|
|
|
|
|
cr, uid, context=context) |
|
|
|
|
|
|
|
|
@api.model |
|
|
|
|
|
def get_record_from_my_channel(self): |
|
|
|
|
|
calling_number = self.env['asterisk.server']._get_calling_number() |
|
|
# calling_number = "0641981246" |
|
|
# calling_number = "0641981246" |
|
|
if calling_number: |
|
|
if calling_number: |
|
|
record = self.pool['phone.common'].get_record_from_phone_number( |
|
|
|
|
|
cr, uid, calling_number, context=context) |
|
|
|
|
|
|
|
|
record = self.env['phone.common'].get_record_from_phone_number( |
|
|
|
|
|
calling_number) |
|
|
if record: |
|
|
if record: |
|
|
return record |
|
|
return record |
|
|
else: |
|
|
else: |
|
@ -299,75 +230,72 @@ class asterisk_server(orm.Model): |
|
|
return False |
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class res_users(orm.Model): |
|
|
|
|
|
|
|
|
class ResUsers(models.Model): |
|
|
_inherit = "res.users" |
|
|
_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: Asterisk should reuse the callerID of sip.conf! |
|
|
|
|
|
# But it cannot, cf |
|
|
|
|
|
# http://lists.digium.com/pipermail/asterisk-users/ |
|
|
|
|
|
# 2012-January/269787.html |
|
|
|
|
|
'cdraccount': fields.char( |
|
|
|
|
|
'CDR Account', size=50, |
|
|
|
|
|
help="Call Detail Record (CDR) account used for billing this " |
|
|
|
|
|
"user."), |
|
|
|
|
|
'asterisk_chan_type': fields.selection([ |
|
|
|
|
|
('SIP', 'SIP'), |
|
|
|
|
|
('IAX2', 'IAX2'), |
|
|
|
|
|
('DAHDI', 'DAHDI'), |
|
|
|
|
|
('Zap', 'Zap'), |
|
|
|
|
|
('Skinny', 'Skinny'), |
|
|
|
|
|
('MGCP', 'MGCP'), |
|
|
|
|
|
('mISDN', 'mISDN'), |
|
|
|
|
|
('H323', 'H323'), |
|
|
|
|
|
('SCCP', 'SCCP'), |
|
|
|
|
|
('Local', 'Local'), |
|
|
|
|
|
], 'Asterisk Channel Type', |
|
|
|
|
|
help="Asterisk channel type, as used in the Asterisk 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 Asterisk 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 Asterisk " |
|
|
|
|
|
"Manager Interface 'originate' request for the click2dial " |
|
|
|
|
|
"feature. If you want to have several variable headers, separate " |
|
|
|
|
|
"them with '|'."), |
|
|
|
|
|
'asterisk_server_id': fields.many2one( |
|
|
|
|
|
'asterisk.server', 'Asterisk Server', |
|
|
|
|
|
help="Asterisk server on which the user's phone is connected. " |
|
|
|
|
|
"If you leave this field empty, it will use the first Asterisk " |
|
|
|
|
|
"server of the user's company."), |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_defaults = { |
|
|
|
|
|
'asterisk_chan_type': 'SIP', |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def _check_validity(self, cr, uid, ids): |
|
|
|
|
|
for user in self.browse(cr, uid, ids): |
|
|
|
|
|
|
|
|
internal_number = fields.Char( |
|
|
|
|
|
string='Internal Number', copy=False, |
|
|
|
|
|
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.") |
|
|
|
|
|
# You'd probably think: Asterisk should reuse the callerID of sip.conf! |
|
|
|
|
|
# But it cannot, cf |
|
|
|
|
|
# http://lists.digium.com/pipermail/asterisk-users/ |
|
|
|
|
|
# 2012-January/269787.html |
|
|
|
|
|
cdraccount = fields.Char( |
|
|
|
|
|
string='CDR Account', |
|
|
|
|
|
help="Call Detail Record (CDR) account used for billing this user.") |
|
|
|
|
|
asterisk_chan_type = fields.Selection([ |
|
|
|
|
|
('SIP', 'SIP'), |
|
|
|
|
|
('IAX2', 'IAX2'), |
|
|
|
|
|
('DAHDI', 'DAHDI'), |
|
|
|
|
|
('Zap', 'Zap'), |
|
|
|
|
|
('Skinny', 'Skinny'), |
|
|
|
|
|
('MGCP', 'MGCP'), |
|
|
|
|
|
('mISDN', 'mISDN'), |
|
|
|
|
|
('H323', 'H323'), |
|
|
|
|
|
('SCCP', 'SCCP'), |
|
|
|
|
|
# Local works for click2dial, but it won't work in |
|
|
|
|
|
# _get_calling_number() when trying to identify the |
|
|
|
|
|
# channel of the user, so it's better not to propose it |
|
|
|
|
|
# ('Local', 'Local'), |
|
|
|
|
|
], string='Asterisk Channel Type', default='SIP', |
|
|
|
|
|
help="Asterisk channel type, as used in the Asterisk dialplan. " |
|
|
|
|
|
"If the user has a regular IP phone, the channel type is 'SIP'.") |
|
|
|
|
|
resource = fields.Char( |
|
|
|
|
|
string='Resource Name', copy=False, |
|
|
|
|
|
help="Resource name for the channel type selected. For example, " |
|
|
|
|
|
"if you use 'Dial(SIP/phone1)' in your Asterisk 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( |
|
|
|
|
|
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 Asterisk " |
|
|
|
|
|
"Manager Interface 'originate' request for the click2dial " |
|
|
|
|
|
"feature. If you want to have several variable headers, separate " |
|
|
|
|
|
"them with '|'.") |
|
|
|
|
|
asterisk_server_id = fields.Many2one( |
|
|
|
|
|
'asterisk.server', string='Asterisk Server', |
|
|
|
|
|
help="Asterisk server on which the user's phone is connected. " |
|
|
|
|
|
"If you leave this field empty, it will use the first Asterisk " |
|
|
|
|
|
"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 = [ |
|
|
strings_to_check = [ |
|
|
(_('Resource Name'), user.resource), |
|
|
(_('Resource Name'), user.resource), |
|
|
(_('Internal Number'), user.internal_number), |
|
|
(_('Internal Number'), user.internal_number), |
|
@ -378,47 +306,53 @@ class res_users(orm.Model): |
|
|
try: |
|
|
try: |
|
|
check_string[1].encode('ascii') |
|
|
check_string[1].encode('ascii') |
|
|
except UnicodeEncodeError: |
|
|
except UnicodeEncodeError: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
_("The '%s' for the user '%s' should only have " |
|
|
|
|
|
"ASCII caracters") |
|
|
|
|
|
|
|
|
raise ValidationError(_( |
|
|
|
|
|
"The '%s' for the user '%s' should only have " |
|
|
|
|
|
"ASCII caracters") |
|
|
% (check_string[0], user.name)) |
|
|
% (check_string[0], user.name)) |
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
_constraints = [( |
|
|
|
|
|
_check_validity, |
|
|
|
|
|
"Error message in raise", |
|
|
|
|
|
['resource', 'internal_number', 'callerid'] |
|
|
|
|
|
)] |
|
|
|
|
|
|
|
|
@api.multi |
|
|
|
|
|
def get_asterisk_server_from_user(self): |
|
|
|
|
|
'''Returns an asterisk.server recordset''' |
|
|
|
|
|
self.ensure_one() |
|
|
|
|
|
# We check if the user has an Asterisk server configured |
|
|
|
|
|
if self.asterisk_server_id: |
|
|
|
|
|
ast_server = self.asterisk_server_id |
|
|
|
|
|
else: |
|
|
|
|
|
asterisk_servers = self.env['asterisk.server'].search( |
|
|
|
|
|
[('company_id', '=', self.company_id.id)]) |
|
|
|
|
|
# If the user doesn't have an asterisk server, |
|
|
|
|
|
# we take the first one of the user's company |
|
|
|
|
|
if not asterisk_servers: |
|
|
|
|
|
raise UserError( |
|
|
|
|
|
_("No Asterisk server configured for the company '%s'.") |
|
|
|
|
|
% self.company_id.name) |
|
|
|
|
|
else: |
|
|
|
|
|
ast_server = asterisk_servers[0] |
|
|
|
|
|
return ast_server |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PhoneCommon(orm.AbstractModel): |
|
|
|
|
|
|
|
|
class PhoneCommon(models.AbstractModel): |
|
|
_inherit = 'phone.common' |
|
|
_inherit = 'phone.common' |
|
|
|
|
|
|
|
|
def click2dial(self, cr, uid, erp_number, context=None): |
|
|
|
|
|
res = super(PhoneCommon, self).click2dial( |
|
|
|
|
|
cr, uid, erp_number, context=context) |
|
|
|
|
|
|
|
|
@api.model |
|
|
|
|
|
def click2dial(self, erp_number): |
|
|
|
|
|
res = super(PhoneCommon, self).click2dial(erp_number) |
|
|
if not erp_number: |
|
|
if not erp_number: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
_('Missing phone number')) |
|
|
|
|
|
|
|
|
raise UserError(_('Missing phone number')) |
|
|
|
|
|
|
|
|
user, ast_server, ast_manager = \ |
|
|
user, ast_server, ast_manager = \ |
|
|
self.pool['asterisk.server']._connect_to_asterisk( |
|
|
|
|
|
cr, uid, context=context) |
|
|
|
|
|
ast_number = self.convert_to_dial_number( |
|
|
|
|
|
cr, uid, erp_number, context=context) |
|
|
|
|
|
|
|
|
self.env['asterisk.server']._connect_to_asterisk() |
|
|
|
|
|
ast_number = self.convert_to_dial_number(erp_number) |
|
|
# Add 'out prefix' |
|
|
# Add 'out prefix' |
|
|
if ast_server.out_prefix: |
|
|
if ast_server.out_prefix: |
|
|
_logger.debug('Out prefix = %s' % ast_server.out_prefix) |
|
|
|
|
|
|
|
|
_logger.debug('Out prefix = %s', ast_server.out_prefix) |
|
|
ast_number = '%s%s' % (ast_server.out_prefix, ast_number) |
|
|
ast_number = '%s%s' % (ast_server.out_prefix, ast_number) |
|
|
_logger.debug('Number to be sent to Asterisk = %s' % ast_number) |
|
|
|
|
|
|
|
|
_logger.debug('Number to be sent to Asterisk = %s', ast_number) |
|
|
|
|
|
|
|
|
# The user should have a CallerID |
|
|
# The user should have a CallerID |
|
|
if not user.callerid: |
|
|
if not user.callerid: |
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
_('No callerID configured for the current user')) |
|
|
|
|
|
|
|
|
raise UserError(_('No callerID configured for the current user')) |
|
|
|
|
|
|
|
|
variable = [] |
|
|
variable = [] |
|
|
if user.asterisk_chan_type == 'SIP': |
|
|
if user.asterisk_chan_type == 'SIP': |
|
@ -441,19 +375,18 @@ class PhoneCommon(orm.AbstractModel): |
|
|
channel, |
|
|
channel, |
|
|
context=ast_server.context, |
|
|
context=ast_server.context, |
|
|
extension=ast_number, |
|
|
extension=ast_number, |
|
|
priority=str(ast_server.extension_priority), |
|
|
|
|
|
timeout=str(ast_server.wait_time * 1000), |
|
|
|
|
|
|
|
|
priority=unicode(ast_server.extension_priority), |
|
|
|
|
|
timeout=unicode(ast_server.wait_time * 1000), |
|
|
caller_id=user.callerid, |
|
|
caller_id=user.callerid, |
|
|
account=user.cdraccount, |
|
|
account=user.cdraccount, |
|
|
variable=variable) |
|
|
variable=variable) |
|
|
except Exception, e: |
|
|
except Exception, e: |
|
|
_logger.error( |
|
|
_logger.error( |
|
|
"Error in the Originate request to Asterisk server %s" |
|
|
|
|
|
% ast_server.ip_address) |
|
|
|
|
|
|
|
|
"Error in the Originate request to Asterisk server %s", |
|
|
|
|
|
ast_server.ip_address) |
|
|
_logger.error( |
|
|
_logger.error( |
|
|
"Here are the details of the error: '%s'" % unicode(e)) |
|
|
|
|
|
raise orm.except_orm( |
|
|
|
|
|
_('Error:'), |
|
|
|
|
|
|
|
|
"Here are the details of the error: '%s'", unicode(e)) |
|
|
|
|
|
raise UserError( |
|
|
_("Click to dial with Asterisk failed.\nHere is the error: " |
|
|
_("Click to dial with Asterisk failed.\nHere is the error: " |
|
|
"'%s'") |
|
|
"'%s'") |
|
|
% unicode(e)) |
|
|
% unicode(e)) |
|
|