Browse Source

Fix code style

pull/3/head
Sandy Carter 10 years ago
parent
commit
3ebf804911
  1. 50
      asterisk_click2dial/__openerp__.py
  2. 731
      asterisk_click2dial/asterisk_click2dial.py
  3. 202
      asterisk_click2dial/scripts/get_cid_name.py
  4. 219
      asterisk_click2dial/wizard/open_calling_partner.py
  5. 48
      asterisk_click2dial/wizard/reformat_all_phonenumbers.py
  6. 7
      asterisk_click2dial_crm/__init__.py
  7. 20
      asterisk_click2dial_crm/__openerp__.py
  8. 21
      asterisk_click2dial_crm/asterisk_click2dial_crm.py
  9. 4
      asterisk_click2dial_crm/wizard/__init__.py
  10. 51
      asterisk_click2dial_crm/wizard/create_crm_phonecall.py

50
asterisk_click2dial/__openerp__.py

@ -27,26 +27,47 @@
'license': 'AGPL-3',
'description': """This module adds 3 functionnalities :
1) It adds a 'dial' button in the partner address form view so that users can directly dial a phone number through Asterisk. 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 address view.
. OpenERP connects to the Asterisk Manager Interface and Asterisk makes the user's phone ring.
1) It adds a 'dial' button in the partner address form view so that users can
directly dial a phone number through Asterisk. 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 address view.
. OpenERP connects to the Asterisk Manager Interface and Asterisk makes the
user's phone ring.
. The user answers his own phone (if he doesn't, the process stops here).
. Asterisk 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 addresses of OpenERP. Here is how it works :
. On incoming phone calls, the Asterisk dialplan executes an AGI script "get_cid_name_timeout.sh".
. The "get_cid_name_timeout.sh" script calls the "get_cid_name.py" script with a short timeout.
. The "get_cid_name.py" script will make an XML-RPC request on the OpenERP server to try to find the name of the person corresponding to the phone number presented by the calling party.
. If it finds the name, it is set as the CallerID name of the call, so as to be presented on the IP phone of the user.
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 addresses of OpenERP. Here is how it works :
. On incoming phone calls, the Asterisk dialplan executes an AGI script
"get_cid_name_timeout.sh".
. The "get_cid_name_timeout.sh" script calls the "get_cid_name.py" script with
a short timeout.
. The "get_cid_name.py" script will make an XML-RPC request on the OpenERP
server to try to find the name of the person corresponding to the phone
number presented by the calling party.
. If it finds the name, it is set as the CallerID name of the call, so as to be
presented on the IP phone of the user.
3) It adds a button "Open calling partner" in the menu "Sales > Address book" to get the partner corresponding to the calling party in one click. Here is how it works :
. When the user clicks on the "Open calling partner" button, OpenERP sends a query to the Asterisk 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 Partner addresses of OpenERP. If a record matches, it shows the name of the related Partner and proposes to open it, or open its related sale orders or invoices. If no record matches, it proposes to create a new Contact with the presented phone number as 'Phone' or 'Mobile' number or update an existing Contact.
3) It adds a button "Open calling partner" in the menu "Sales > Address book"
to get the partner corresponding to the calling party in one click.
Here is how it works :
. When the user clicks on the "Open calling partner" button, OpenERP sends a
query to the Asterisk 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 Partner addresses of
OpenERP. If a record matches, it shows the name of the related Partner and
proposes to open it, or open its related sale orders or invoices. If no
record matches, it proposes to create a new Contact with the presented phone
number as 'Phone' or 'Mobile' number or update an existing Contact.
A detailed documentation for this module is available on the Akretion Web site : http://www.akretion.com/en/products-and-services/openerp-asterisk-voip-connector """,
A detailed documentation for this module is available on the Akretion Web site:
http://www.akretion.com/products-and-services/openerp-asterisk-voip-connector
""",
'author': "Akretion,Odoo Community Association (OCA)",
'website': 'http://www.akretion.com/',
'depends': ['base'],
@ -67,4 +88,3 @@ A detailed documentation for this module is available on the Akretion Web site :
'installable': True,
'active': False,
}

731
asterisk_click2dial/asterisk_click2dial.py

@ -27,32 +27,131 @@ from tools.translate import _
# Lib for phone number reformating -> pip install phonenumbers
import phonenumbers
# Lib py-asterisk from http://code.google.com/p/py-asterisk/
# We need a version which has this commit : http://code.google.com/p/py-asterisk/source/detail?r=8d0e1c941cce727c702582f3c9fcd49beb4eeaa4
# We need a version which has this commit :
# 8d0e1c941cce727c702582f3c9fcd49beb4eeaa4
# so a version after Nov 20th, 2012
from Asterisk import Manager
_logger = logging.getLogger(__name__)
class asterisk_server(osv.osv):
'''Asterisk server object, to store all the parameters of the Asterisk IPBXs'''
"""Asterisk server object, to store all the parameters of the Asterisk
IPBXs
"""
_name = "asterisk.server"
_description = "Asterisk Servers"
_columns = {
'name': fields.char('Asterisk server name', size=50, required=True, help="Asterisk server name."),
'active': fields.boolean('Active', help="The active field allows you to hide the Asterisk server without deleting it."),
'ip_address': fields.char('Asterisk IP addr. 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 place outgoing calls. If you don't use a prefix to place outgoing calls, leave empty."),
'national_prefix': fields.char('National prefix', size=4, help="Prefix for national phone calls (don't include the 'out prefix'). For e.g., in France, the phone numbers look like '01 41 98 12 42' : the National prefix is '0'."),
'international_prefix': fields.char('International prefix', required=True, size=4, help="Prefix to add to make international phone calls (don't include the 'out prefix'). For e.g., in France, the International prefix is '00'."),
'country_prefix': fields.char('My country prefix', required=True, size=4, help="Phone prefix of the country where the Asterisk server is located. For e.g. the phone prefix for France is '33'. If the phone number to dial starts with the 'My country prefix', OpenERP will remove the country prefix from the phone number and add the 'out prefix' followed by the 'national prefix'. If the phone number to dial doesn't start with the 'My country prefix', OpenERP will add the 'out prefix' followed by the 'international prefix'."),
'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 Asterisk 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."),
'name': fields.char(
'Asterisk server name',
size=50,
required=True,
help="Asterisk server name."
),
'active': fields.boolean(
'Active',
help="The active field allows you to hide the Asterisk server "
"without deleting it."
),
'ip_address': fields.char(
'Asterisk IP addr. 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 place outgoing calls. "
"If you don't use a prefix to place outgoing calls, "
"leave empty."
),
'company_id': fields.many2one(
'res.company',
'Company',
help="Company who uses the Asterisk server."
),
'national_prefix': fields.char(
'National prefix',
size=4,
help="Prefix for national phone calls "
"(don't include the 'out prefix'). "
"For e.g., in France, the phone numbers look like "
"'01 41 98 12 42' : the National prefix is '0'."
),
'international_prefix': fields.char(
'International prefix',
required=True,
size=4,
help="Prefix to add to make international phone calls "
"(don't include the 'out prefix'). "
"For e.g., in France, the International prefix is '00'."
),
'country_prefix': fields.char(
'My country prefix',
required=True,
size=4,
help="Phone prefix of the country where the Asterisk server is "
"located. For e.g. the phone prefix for France is '33'. "
"If the phone number to dial starts with the "
"'My country prefix', OpenERP will remove the country prefix "
"from the phone number and add the 'out prefix' followed by "
"the 'national prefix'. If the phone number to dial doesn't "
"start with the 'My country prefix', OpenERP will add the "
"'out prefix' followed by the 'international prefix'."
),
'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 Asterisk 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."
),
}
_defaults = {
@ -68,7 +167,10 @@ class asterisk_server(osv.osv):
def _check_validity(self, cr, uid, ids):
for server in self.browse(cr, uid, ids):
country_prefix = ('Country prefix', server.country_prefix)
international_prefix = ('International prefix', server.international_prefix)
international_prefix = (
'International prefix',
server.international_prefix
)
out_prefix = ('Out prefix', server.out_prefix)
national_prefix = ('National prefix', server.national_prefix)
dialplan_context = ('Dialplan context', server.context)
@ -76,29 +178,69 @@ class asterisk_server(osv.osv):
login = ('AMI login', server.login)
password = ('AMI password', server.password)
for digit_prefix in [country_prefix, international_prefix, out_prefix, national_prefix]:
for digit_prefix in [
country_prefix,
international_prefix,
out_prefix,
national_prefix
]:
if digit_prefix[1] and not digit_prefix[1].isdigit():
raise osv.except_osv(_('Error :'), _("Only use digits for the '%s' on the Asterisk server '%s'" % (digit_prefix[0], server.name)))
raise osv.except_osv(
_('Error :'),
_("Only use digits for the '%s' on the Asterisk "
"server '%s'" % (digit_prefix[0], server.name))
)
if server.wait_time < 1 or server.wait_time > 120:
raise osv.except_osv(_('Error :'), _("You should set a 'Wait time' value between 1 and 120 seconds for the Asterisk server '%s'" % server.name))
raise osv.except_osv(
_('Error :'),
_("You should set a 'Wait time' value between 1 and 120 "
"seconds for the Asterisk server '%s'" % server.name)
)
if server.extension_priority < 1:
raise osv.except_osv(_('Error :'), _("The 'extension priority' must be a positive value for the Asterisk server '%s'" % server.name))
raise osv.except_osv(
_('Error :'),
_("The 'extension priority' must be a positive value for "
"the Asterisk server '%s'" % server.name)
)
if server.port > 65535 or server.port < 1:
raise osv.except_osv(_('Error :'), _("You should set a TCP port between 1 and 65535 for the Asterik server '%s'" % server.name))
for check_string in [dialplan_context, alert_info, login, password]:
raise osv.except_osv(
_('Error :'),
_("You should set a TCP port between 1 and 65535 for the "
"Asterik server '%s'" % server.name)
)
for check_string in [
dialplan_context,
alert_info,
login,
password]:
if check_string[1]:
try:
string = check_string[1].encode('ascii')
check_string[1].encode('ascii')
except UnicodeEncodeError:
raise osv.except_osv(_('Error :'), _("The '%s' should only have ASCII caracters for the Asterisk server '%s'" % (check_string[0], server.name)))
raise osv.except_osv(
_('Error :'),
_("The '%s' should only have ASCII caracters for "
"the Asterisk server '%s'"
% (check_string[0], server.name))
)
return True
_constraints = [
(_check_validity, "Error message in raise", ['out_prefix', 'country_prefix', 'national_prefix', 'international_prefix', 'wait_time', 'extension_priority', 'port', 'context', 'alert_info', 'login', 'password']),
(_check_validity, "Error message in raise", [
'out_prefix',
'country_prefix',
'national_prefix',
'international_prefix',
'wait_time',
'extension_priority',
'port',
'context',
'alert_info',
'login',
'password',
]),
]
def _reformat_number(self, cr, uid, erp_number, ast_server, context=None):
'''
This function is dedicated to the transformation of the number
@ -109,9 +251,8 @@ class asterisk_server(osv.osv):
'''
error_title_msg = _("Invalid phone number")
invalid_international_format_msg = _("The phone number is not written in valid international format. Example of valid international format : +33 1 41 98 12 42")
invalid_national_format_msg = _("The phone number is not written in valid national format.")
invalid_format_msg = _("The phone number is not written in valid format.")
invalid_format_msg = _("The phone number is not written in valid "
"format.")
# Let's call the variable tmp_number now
tmp_number = erp_number
@ -129,9 +270,12 @@ class asterisk_server(osv.osv):
out_prefix = ast_server.out_prefix or ''
# Maybe one day we will use
# phonenumbers.format_out_of_country_calling_number(phonenumbers.parse('<phone_number_e164', None), 'FR')
# phonenumbers.format_out_of_country_calling_number(
# phonenumbers.parse('<phone_number_e164', None), 'FR'
# )
# The country code seems to be OK with the ones of OpenERP
# But it returns sometimes numbers with '-'... we have to investigate this first
# But it returns sometimes numbers with '-'...
# we have to investigate this first
# International format
if tmp_number[0] != '+':
raise # This should never happen
@ -147,49 +291,74 @@ class asterisk_server(osv.osv):
if country_prefix == tmp_number[0:len(country_prefix)]:
# If the number is a national number,
# remove 'my country prefix' and add 'national prefix'
tmp_number = (national_prefix) + tmp_number[len(country_prefix):len(tmp_number)]
_logger.debug('National prefix = %s - Number with national prefix = %s' % (national_prefix, tmp_number))
tmp_number = (national_prefix
+ tmp_number[len(country_prefix):len(tmp_number)])
_logger.debug(
'National prefix = %s - Number with national prefix = %s'
% (national_prefix, tmp_number)
)
else:
# If the number is an international number,
# add 'international prefix'
tmp_number = international_prefix + tmp_number
_logger.debug('International prefix = %s - Number with international prefix = %s' % (international_prefix, tmp_number))
_logger.debug(
'International prefix = %s - Number with international prefix '
'= %s' % (international_prefix, tmp_number)
)
# Add 'out prefix' to all numbers
tmp_number = out_prefix + tmp_number
_logger.debug('Out prefix = %s - Number to be sent to Asterisk = %s' % (out_prefix, tmp_number))
_logger.debug(
'Out prefix = %s - Number to be sent to Asterisk = %s'
% (out_prefix, tmp_number)
)
return tmp_number
def _convert_number_to_international_format(self, cr, uid, number, ast_server, context=None):
'''Convert the number presented by the phone network to a number
in international format e.g. +33141981242'''
def _convert_number_to_international_format(self, cr, uid, number,
ast_server, context=None):
"""Convert the number presented by the phone network to a number
in international format e.g. +33141981242
"""
if number and number.isdigit() and len(number) > 5:
if ast_server.international_prefix and number[0:len(ast_server.international_prefix)] == ast_server.international_prefix:
if (ast_server.international_prefix
and number[0:len(ast_server.international_prefix)]
== ast_server.international_prefix):
number = number[len(ast_server.international_prefix):]
number = '+' + number
elif ast_server.national_prefix and number[0:len(ast_server.national_prefix)] == ast_server.national_prefix:
elif (ast_server.national_prefix
and number[0:len(ast_server.national_prefix)]
== ast_server.national_prefix):
number = number[len(ast_server.national_prefix):]
number = '+' + ast_server.country_prefix + number
return number
def _get_asterisk_server_from_user(self, cr, uid, user, context=None):
'''Returns an asterisk.server browse object'''
# We check if the user has an Asterisk server configured
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 no asterisk server is configured on the user, we take the first one
asterisk_server_ids = self.search(
cr,
uid,
[('company_id', '=', user.company_id.id)],
context=context
)
# If no asterisk server is configured on the user,
# we take the first one
if not asterisk_server_ids:
raise osv.except_osv(_('Error :'), _("No Asterisk server configured for the company '%s'.") % user.company_id.name)
raise osv.except_osv(
_('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)
ast_server = self.browse(
cr, uid, asterisk_server_ids[0], context=context
)
return ast_server
def _connect_to_asterisk(self, cr, uid, context=None):
'''
Open the connection to the asterisk manager
@ -201,41 +370,75 @@ class asterisk_server(osv.osv):
# Note : if I write 'Error' without ' :', it won't get translated...
# I don't understand why !
ast_server = self._get_asterisk_server_from_user(cr, uid, user, context=context)
ast_server = self._get_asterisk_server_from_user(
cr, uid, user, context=context
)
# We check if the current user has a chan type
if not user.asterisk_chan_type:
raise osv.except_osv(_('Error :'), _('No channel type configured for the current user.'))
raise osv.except_osv(
_('Error :'),
_('No channel type configured for the current user.')
)
# We check if the current user has an internal number
if not user.resource:
raise osv.except_osv(_('Error :'), _('No resource name configured for the current user'))
_logger.debug("User's phone : %s/%s" % (user.asterisk_chan_type, user.resource))
_logger.debug("Asterisk server = %s:%d" % (ast_server.ip_address, ast_server.port))
raise osv.except_osv(
_('Error :'),
_('No resource name configured for the current user')
)
_logger.debug(
"User's phone : %s/%s" % (user.asterisk_chan_type, user.resource)
)
_logger.debug(
"Asterisk server = %s:%d"
% (ast_server.ip_address, ast_server.port)
)
# Connect to the Asterisk Manager Interface
try:
ast_manager = Manager.Manager((ast_server.ip_address, ast_server.port), ast_server.login, ast_server.password)
ast_manager = Manager.Manager(
(ast_server.ip_address, ast_server.port),
ast_server.login,
ast_server.password
)
except Exception, e:
_logger.error("Error in the Originate request to Asterisk server %s" % ast_server.ip_address)
_logger.error("Here is the detail of the error : '%s'" % unicode(e))
raise osv.except_osv(_('Error :'), _("Problem in the request from OpenERP to Asterisk. Here is the detail of the error: '%s'" % unicode(e)))
return False
_logger.error(
"Error in the Originate request to Asterisk server %s"
% ast_server.ip_address
)
_logger.error(
"Here is the detail of the error : '%s'" % unicode(e)
)
raise osv.except_osv(
_('Error :'),
_("Problem in the request from OpenERP to Asterisk. Here is "
"the detail of the error: '%s'" % unicode(e))
)
return (user, ast_server, ast_manager)
def _dial_with_asterisk(self, cr, uid, erp_number, context=None):
print "_dial_with_asterisk erp_number=", erp_number
if not erp_number:
raise osv.except_osv(_('Error :'), "Hara kiri : you must call the function with erp_number")
user, ast_server, ast_manager = self._connect_to_asterisk(cr, uid, context=context)
ast_number = self._reformat_number(cr, uid, erp_number, ast_server, context=context)
raise osv.except_osv(
_('Error :'),
"Hara kiri : you must call the function with erp_number"
)
user, ast_server, ast_manager = self._connect_to_asterisk(
cr, uid, context=context
)
ast_number = self._reformat_number(
cr, uid, erp_number, ast_server, context=context
)
# The user should have a CallerID
if not user.callerid:
raise osv.except_osv(_('Error :'), _('No callerID configured for the current user'))
raise osv.except_osv(
_('Error :'),
_('No callerID configured for the current user')
)
variable = []
if user.asterisk_chan_type == 'SIP':
@ -243,14 +446,17 @@ class asterisk_server(osv.osv):
if user.alert_info:
variable.append('SIPAddHeader=Alert-Info: ' + user.alert_info)
elif ast_server.alert_info:
variable.append('SIPAddHeader=Alert-Info: ' + ast_server.alert_info)
variable.append(
'SIPAddHeader=Alert-Info: ' + ast_server.alert_info
)
if user.variable:
for user_variable in user.variable.split('|'):
variable.append(user_variable.strip())
try:
ast_manager.Originate(
user.asterisk_chan_type + '/' + user.resource + ( ('/' + user.dial_suffix) if user.dial_suffix else ''),
user.asterisk_chan_type + '/' + user.resource +
(('/' + user.dial_suffix) if user.dial_suffix else ''),
context=ast_server.context,
extension=ast_number,
priority=str(ast_server.extension_priority),
@ -258,9 +464,18 @@ class asterisk_server(osv.osv):
caller_id=user.callerid,
variable=variable)
except Exception, e:
_logger.error("Error in the Originate request to Asterisk server %s" % ast_server.ip_address)
_logger.error("Here is the detail of the error : '%s'" % unicode(e))
raise osv.except_osv(_('Error :'), _("Click to dial with Asterisk failed.\nHere is the error: '%s'" % unicode(e)))
_logger.error(
"Error in the Originate request to Asterisk server %s"
% ast_server.ip_address
)
_logger.error(
"Here is the detail of the error : '%s'" % unicode(e)
)
raise osv.except_osv(
_('Error :'),
_("Click to dial with Asterisk failed.\nHere is the error: "
"'%s'" % unicode(e))
)
finally:
ast_manager.Logoff()
@ -269,7 +484,9 @@ class asterisk_server(osv.osv):
def _get_calling_number(self, cr, uid, context=None):
user, ast_server, ast_manager = self._connect_to_asterisk(cr, uid, context=context)
user, ast_server, ast_manager = self._connect_to_asterisk(
cr, uid, context=context
)
calling_party_number = False
try:
list_chan = ast_manager.Status()
@ -278,23 +495,37 @@ class asterisk_server(osv.osv):
_logger.debug("Result of Status AMI request: %s", list_chan)
for chan in list_chan.values():
sip_account = user.asterisk_chan_type + '/' + user.resource
if chan.get('ChannelState') == '4' and chan.get('ConnectedLineNum') == user.internal_number: # 4 = Ring
if (chan.get('ChannelState') == '4' # 4 = Ring
and chan.get('ConnectedLineNum')
== user.internal_number):
_logger.debug("Found a matching Event in 'Ring' state")
calling_party_number = chan.get('CallerIDNum')
break
if chan.get('ChannelState') == '6' and sip_account in chan.get('BridgedChannel'): # 6 = Up
if (chan.get('ChannelState') == '6' # 6 = Up
and sip_account in chan.get('BridgedChannel')):
_logger.debug("Found a matching Event in 'Up' state")
calling_party_number = chan.get('CallerIDNum')
break
except Exception, e:
_logger.error("Error in the Status request to Asterisk server %s" % ast_server.ip_address)
_logger.error("Here is the detail of the error : '%s'" % unicode(e))
raise osv.except_osv(_('Error :'), _("Can't get calling number from Asterisk.\nHere is the error: '%s'" % unicode(e)))
_logger.error(
"Error in the Status request to Asterisk server %s"
% ast_server.ip_address
)
_logger.error(
"Here is the detail of the error : '%s'" % unicode(e)
)
raise osv.except_osv(
_('Error :'),
_("Can't get calling number from Asterisk.\n"
"Here is the error: '%s'" % unicode(e))
)
finally:
ast_manager.Logoff()
_logger.debug("The calling party number is '%s'" % calling_party_number)
_logger.debug(
"The calling party number is '%s'" % calling_party_number
)
return calling_party_number
@ -307,15 +538,27 @@ class res_users(osv.osv):
_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."),
'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
'asterisk_chan_type': fields.selection([
# But it cannot, cf
# lists.digium.com/pipermail/asterisk-users/2012-January/269787.html
'asterisk_chan_type': fields.selection(
[
('SIP', 'SIP'),
('Local', 'Local'),
('IAX2', 'IAX2'),
@ -326,14 +569,44 @@ class res_users(osv.osv):
('mISDN', 'mISDN'),
('H323', 'H323'),
('SCCP', 'SCCP'),
], '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."),
],
'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 = {
@ -342,16 +615,28 @@ class res_users(osv.osv):
def _check_validity(self, cr, uid, ids):
for user in self.browse(cr, uid, ids):
for check_string in [('Resource name', user.resource), ('Internal number', user.internal_number), ('Caller ID', user.callerid)]:
for check_string in [
('Resource name', user.resource),
('Internal number', user.internal_number),
('Caller ID', user.callerid)
]:
if check_string[1]:
try:
plom = check_string[1].encode('ascii')
check_string[1].encode('ascii')
except UnicodeEncodeError:
raise osv.except_osv(_('Error :'), _("The '%s' for the user '%s' should only have ASCII caracters" % (check_string[0], user.name)))
raise osv.except_osv(
_('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']),
(_check_validity, "Error message in raise", [
'resource',
'internal_number',
'callerid'
]),
]
res_users()
@ -360,120 +645,211 @@ res_users()
class res_partner_address(osv.osv):
_inherit = "res.partner.address"
def _format_phonenumber_to_e164(self, cr, uid, ids, name, arg, context=None):
def _format_phonenumber_to_e164(self, cr, uid, ids, name, arg,
context=None):
result = {}
for addr in self.read(cr, uid, ids, ['phone', 'mobile', 'fax'], context=context):
for addr in self.read(cr, uid, ids, ['phone', 'mobile', 'fax'],
context=context):
result[addr['id']] = {}
for fromfield, tofield in [('phone', 'phone_e164'), ('mobile', 'mobile_e164'), ('fax', 'fax_e164')]:
for fromfield, tofield in [
('phone', 'phone_e164'),
('mobile', 'mobile_e164'),
('fax', 'fax_e164')
]:
if not addr.get(fromfield):
res = False
else:
try:
res = phonenumbers.format_number(phonenumbers.parse(addr.get(fromfield), None), phonenumbers.PhoneNumberFormat.E164)
res = phonenumbers.format_number(
phonenumbers.parse(addr.get(fromfield), None),
phonenumbers.PhoneNumberFormat.E164
)
except Exception, e:
_logger.error("Cannot reformat the phone number '%s' to E.164 format. Error message: %s" % (addr.get(fromfield), e))
_logger.error("You should fix this number and run the wizard 'Reformat all phone numbers' from the menu Settings > Configuration > Asterisk")
# If I raise an exception here, it won't be possible to install
# the module on a DB with bad phone numbers
#raise osv.except_osv(_('Error :'), _("Cannot reformat the phone number '%s' to E.164 format. Error message: %s" % (addr.get(fromfield), e)))
_logger.error(
"Cannot reformat the phone number '%s' to E.164 "
"format. Error message: %s"
% (addr.get(fromfield), e)
)
_logger.error(
"You should fix this number and run the wizard "
"'Reformat all phone numbers' from the menu "
"Settings > Configuration > Asterisk"
)
# If I raise an exception here, it won't be possible to
# install the module on a DB with bad phone numbers
# raise osv.except_osv(
# _('Error :'),
# _("Cannot reformat the phone number '%s' to "
# "E.164 format. Error message: %s"
# % (addr.get(fromfield), e)))
res = False
result[addr['id']][tofield] = res
#print "RESULT _format_phonenumber_to_e164", result
return result
_columns = {
'phone_e164': fields.function(_format_phonenumber_to_e164, type='char', size=64, string='Phone in E.164 format', readonly=True, multi="e164", store={
'res.partner.address': (lambda self, cr, uid, ids, c={}: ids, ['phone'], 10),
}),
'mobile_e164': fields.function(_format_phonenumber_to_e164, type='char', size=64, string='Mobile in E.164 format', readonly=True, multi="e164", store={
'res.partner.address': (lambda self, cr, uid, ids, c={}: ids, ['mobile'], 10),
}),
'fax_e164': fields.function(_format_phonenumber_to_e164, type='char', size=64, string='Fax in E.164 format', readonly=True, multi="e164", store={
'res.partner.address': (lambda self, cr, uid, ids, c={}: ids, ['fax'], 10),
}),
'phone_e164': fields.function(
_format_phonenumber_to_e164,
type='char',
size=64,
string='Phone in E.164 format',
readonly=True,
multi="e164",
store={
'res.partner.address': (lambda self, cr, uid, ids, c={}:
ids, ['phone'], 10),
}
),
'mobile_e164': fields.function(
_format_phonenumber_to_e164,
type='char',
size=64,
string='Mobile in E.164 format',
readonly=True,
multi="e164",
store={
'res.partner.address': (lambda self, cr, uid, ids, c={}:
ids, ['mobile'], 10),
}
),
'fax_e164': fields.function(
_format_phonenumber_to_e164,
type='char',
size=64,
string='Fax in E.164 format',
readonly=True,
multi="e164",
store={
'res.partner.address': (lambda self, cr, uid, ids, c={}:
ids, ['fax'], 10),
}
),
}
def _reformat_phonenumbers(self, cr, uid, vals, context=None):
"""Reformat phone numbers in international format i.e. +33141981242"""
phonefields = ['phone', 'fax', 'mobile']
if any([vals.get(field) for field in phonefields]):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
user = self.pool.get('res.users').browse(
cr, uid, uid, context=context
)
# country_id on res.company is a fields.function that looks at
# company_id.partner_id.addres(default).country_id
if user.company_id.country_id:
user_countrycode = user.company_id.country_id.code
else:
# We need to raise an exception here because, if we pass None as second arg of phonenumbers.parse(), it will raise an exception when you try to enter a phone number in national format... so it's better to raise the exception here
raise osv.except_osv(_('Error :'), _("You should set a country on the company '%s'" % user.company_id.name))
#print "user_countrycode=", user_countrycode
# We need to raise an exception here because, if we pass None
# as second arg of phonenumbers.parse(), it will raise an
# exception when you try to enter a phone number in national
# format... so it's better to raise the exception here
raise osv.except_osv(
_('Error :'),
_("You should set a country on the company '%s'"
% user.company_id.name)
)
for field in phonefields:
if vals.get(field):
try:
res_parse = phonenumbers.parse(vals.get(field), user_countrycode)
res_parse = phonenumbers.parse(
vals.get(field),
user_countrycode
)
except Exception, e:
raise osv.except_osv(_('Error :'), _("Cannot reformat the phone number '%s' to international format. Error message: %s" % (vals.get(field), e)))
#print "res_parse=", res_parse
vals[field] = phonenumbers.format_number(res_parse, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
raise osv.except_osv(
_('Error :'),
_("Cannot reformat the phone number '%s' to "
"international format. Error message: %s"
% (vals.get(field), e))
)
vals[field] = phonenumbers.format_number(
res_parse,
phonenumbers.PhoneNumberFormat.INTERNATIONAL
)
return vals
def create(self, cr, uid, vals, context=None):
vals_reformated = self._reformat_phonenumbers(cr, uid, vals, context=context)
return super(res_partner_address, self).create(cr, uid, vals_reformated, context=context)
vals_reformated = self._reformat_phonenumbers(
cr, uid, vals, context=context
)
return super(res_partner_address, self).create(
cr, uid, vals_reformated, context=context
)
def write(self, cr, uid, ids, vals, context=None):
vals_reformated = self._reformat_phonenumbers(cr, uid, vals, context=context)
return super(res_partner_address, self).write(cr, uid, ids, vals_reformated, context=context)
def dial(self, cr, uid, ids, phone_field=['phone', 'phone_e164'], context=None):
'''Read the number to dial and call _connect_to_asterisk the right way'''
erp_number_read = self.read(cr, uid, ids[0], phone_field, context=context)
vals_reformated = self._reformat_phonenumbers(
cr, uid, vals, context=context
)
return super(res_partner_address, self).write(
cr, uid, ids, vals_reformated, context=context
)
def dial(self, cr, uid, ids, phone_field=None, context=None):
"""Read the number to dial and call _connect_to_asterisk the
right way
"""
if phone_field is None:
phone_field = ['phone', 'phone_e164']
erp_number_read = self.read(
cr, uid, ids[0], phone_field, context=context
)
erp_number_e164 = erp_number_read[phone_field[1]]
erp_number_display = erp_number_read[phone_field[0]]
# Check if the number to dial is not empty
if not erp_number_display:
raise osv.except_osv(_('Error :'), _('There is no phone number !'))
elif erp_number_display and not erp_number_e164:
raise osv.except_osv(_('Error :'), _("The phone number isn't stored in the standard E.164 format. Try to run the wizard 'Reformat all phone numbers' from the menu Settings > Configuration > Asterisk."))
return self.pool.get('asterisk.server')._dial_with_asterisk(cr, uid, erp_number_e164, context=context)
raise osv.except_osv(
_('Error :'),
_("The phone number isn't stored in the standard E.164 "
"format. Try to run the wizard 'Reformat all phone numbers' "
"from the menu Settings > Configuration > Asterisk.")
)
return self.pool.get('asterisk.server')._dial_with_asterisk(
cr, uid, erp_number_e164, context=context
)
def action_dial_phone(self, cr, uid, ids, context=None):
'''Function called by the button 'Dial' next to the 'phone' field
in the partner address view'''
return self.dial(cr, uid, ids, phone_field=['phone', 'phone_e164'], context=context)
"""Function called by the button 'Dial' next to the 'phone' field
in the partner address view
"""
return self.dial(
cr, uid, ids, phone_field=['phone', 'phone_e164'], context=context
)
def action_dial_mobile(self, cr, uid, ids, context=None):
'''Function called by the button 'Dial' next to the 'mobile' field
in the partner address view'''
return self.dial(cr, uid, ids, phone_field=['mobile', 'mobile_e164'], context=context)
"""Function called by the button 'Dial' next to the 'mobile' field
in the partner address view
"""
return self.dial(
cr, uid, ids, phone_field=['mobile', 'mobile_e164'],
context=context
)
def get_name_from_phone_number(self, cr, uid, number, context=None):
'''Function to get name from phone number. Usefull for use from Asterisk
"""Function to get name from phone number. Useful for use from Asterisk
to add CallerID name to incoming calls.
The "scripts/" subdirectory of this module has an AGI script that you can
install on your Asterisk IPBX : the script will be called from the Asterisk
dialplan via the AGI() function and it will use this function via an XML-RPC
request.
'''
res = self.get_partner_from_phone_number(cr, uid, number, context=context)
The "scripts/" subdirectory of this module has an AGI script that you
can install on your Asterisk IPBX : the script will be called from the
Asterisk dialplan via the AGI() function and it will use this function
via an XML-RPC request.
"""
res = self.get_partner_from_phone_number(
cr, uid, number, context=context
)
if res:
return res[2]
else:
return False
def get_partner_from_phone_number(self, cr, uid, number, context=None):
# We check that "number" is really a number
_logger.debug(u"Call get_name_from_phone_number with number = %s" % number)
_logger.debug(
u"Call get_name_from_phone_number with number = %s" % number
)
if not isinstance(number, (str, unicode)):
_logger.warning(u"Number should be a 'str' or 'unicode' but it is a '%s'" % type(number))
_logger.warning(
u"Number should be a 'str' or 'unicode' but it is a '%s'"
% type(number)
)
return False
if not number.isdigit():
_logger.warning(u"Number should only contain digits.")
@ -481,14 +857,32 @@ class res_partner_address(osv.osv):
# We try to match a phone or mobile number with the same end
pg_seach_number = str('%' + number)
res_ids = self.search(cr, uid, ['|', ('phone_e164', 'ilike', pg_seach_number), ('mobile_e164', 'ilike', pg_seach_number)], context=context)
res_ids = self.search(
cr, uid, [
'|',
('phone_e164', 'ilike', pg_seach_number),
('mobile_e164', 'ilike', pg_seach_number)
], context=context
)
# TODO : use is_number_match() of the phonenumber lib ?
if len(res_ids) > 1:
_logger.warning(u"There are several partners addresses (IDS = %s) with the same phone number %s" % (str(res_ids), number))
_logger.warning(
u"There are several partners addresses (IDS = %s) with the "
"same phone number %s" % (str(res_ids), number)
)
if res_ids:
entry = self.read(cr, uid, res_ids[0], ['name', 'partner_id'], context=context)
_logger.debug(u"Answer get_partner_from_phone_number with name = %s" % entry['name'])
return (entry['id'], entry['partner_id'] and entry['partner_id'][0] or False, entry['name'])
entry = self.read(
cr, uid, res_ids[0], ['name', 'partner_id'], context=context
)
_logger.debug(
u"Answer get_partner_from_phone_number with name = %s"
% entry['name']
)
return (
entry['id'],
entry['partner_id'] and entry['partner_id'][0] or False,
entry['name']
)
else:
_logger.debug(u"No match for phone number %s" % number)
return False
@ -501,7 +895,12 @@ class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'asterisk_server_ids': fields.one2many('asterisk.server', 'company_id', 'Asterisk servers', help="List of Asterisk servers.")
'asterisk_server_ids': fields.one2many(
'asterisk.server',
'company_id',
'Asterisk servers',
help="List of Asterisk servers."
)
}
res_company()

202
asterisk_click2dial/scripts/get_cid_name.py

@ -4,8 +4,9 @@
CallerID name lookup in OpenERP for Asterisk IPBX
When executed from the dialplan on an incoming phone call, it will lookup in
OpenERP's partner addresses, and, if it finds the phone number, it will get the
corresponding name of the person and use this name as CallerID name for the incoming call.
OpenERP's partner addresses, and, if it finds the phone number, it will get
the corresponding name of the person and use this name as CallerID name for
the incoming call.
Requires the "asterisk_click2dial" module
available from https://code.launchpad.net/openerp-asterisk-connector
@ -36,8 +37,9 @@
This user only needs to be part of the group "Asterisk CallerID", which has
read access on the 'res.partner.address' object, nothing more.
Note that this script can be used without OpenERP, with just the geolocalisation
feature : for that, don't use option --server ; only use --geoloc
Note that this script can be used without OpenERP, with just the
geolocalisation feature : for that, don't use option --server ; only use
--geoloc
"""
__author__ = "Alexis de Lattre <alexis.delattre@akretion.com>"
@ -69,18 +71,103 @@ from optparse import OptionParser
default_cid_name = "Not in OpenERP"
# Define command line options
option_server = {'names': ('-s', '--server'), 'dest': 'server', 'type': 'string', 'help': 'DNS or IP address of the OpenERP server. Default = none (will not try to connect to OpenERP)', 'action': 'store', 'default': False}
option_port = {'names': ('-p', '--port'), 'dest': 'port', 'type': 'int', 'help': "Port of OpenERP's XML-RPC interface. Default = 8069", 'action': 'store', 'default': 8069}
option_ssl = {'names': ('-e', '--ssl'), 'dest': 'ssl', 'help': "Use XML-RPC secure i.e. with SSL instead of clear XML-RPC. Default = no, use clear XML-RPC", 'action': 'store_true', 'default': False}
option_database = {'names': ('-d', '--database'), 'dest': 'database', 'type': 'string', 'help': "OpenERP database name. Default = 'openerp'", 'action': 'store', 'default': 'openerp'}
option_user = {'names': ('-u', '--user-id'), 'dest': 'user', 'type': 'int', 'help': "OpenERP user ID to use when connecting to OpenERP. Default = 2", 'action': 'store', 'default': 2}
option_password = {'names': ('-w', '--password'), 'dest': 'password', 'type': 'string', 'help': "Password of the OpenERP user. Default = 'demo'", 'action': 'store', 'default': 'demo'}
option_ascii = {'names': ('-a', '--ascii'), 'dest': 'ascii', 'help': "Convert name from UTF-8 to ASCII. Default = no, keep UTF-8", 'action': 'store_true', 'default': False}
option_geoloc = {'names': ('-g', '--geoloc'), 'dest': 'geoloc', 'help': "Try to geolocate phone numbers unknown to OpenERP. This features requires the 'phonenumbers' Python lib. To install it, run 'sudo pip install phonenumbers' Default = no", 'action': 'store_true', 'default': False}
option_geoloc_lang = {'names': ('-l', '--geoloc-lang'), 'dest': 'lang', 'help': "Language in which the name of the country and city name will be displayed by the geolocalisation database. Use the 2 letters ISO code of the language. Default = 'en'", 'action': 'store', 'default': "en"}
option_geoloc_country = {'names': ('-c', '--geoloc-country'), 'dest': 'country', 'help': "2 letters ISO code for your country e.g. 'FR' for France. This will be used by the geolocalisation system to parse the phone number of the calling party. Default = 'FR'", 'action': 'store', 'default': "FR"}
options = [option_server, option_port, option_ssl, option_database, option_user, option_password, option_ascii, option_geoloc, option_geoloc_lang, option_geoloc_country]
option_server = {
'names': ('-s', '--server'),
'dest': 'server',
'type': 'string',
'help': 'DNS or IP address of the OpenERP server. Default = none '
'(will not try to connect to OpenERP)',
'action': 'store',
'default': False,
}
option_port = {
'names': ('-p', '--port'),
'dest': 'port',
'type': 'int',
'help': "Port of OpenERP's XML-RPC interface. Default = 8069",
'action': 'store',
'default': 8069,
}
option_ssl = {
'names': ('-e', '--ssl'),
'dest': 'ssl',
'help': "Use XML-RPC secure i.e. with SSL instead of clear XML-RPC. "
"Default = no, use clear XML-RPC",
'action': 'store_true',
'default': False,
}
option_database = {
'names': ('-d', '--database'),
'dest': 'database',
'type': 'string',
'help': "OpenERP database name. Default = 'openerp'",
'action': 'store',
'default': 'openerp',
}
option_user = {
'names': ('-u', '--user-id'),
'dest': 'user',
'type': 'int',
'help': "OpenERP user ID to use when connecting to OpenERP. Default = 2",
'action': 'store',
'default': 2,
}
option_password = {
'names': ('-w', '--password'),
'dest': 'password',
'type': 'string',
'help': "Password of the OpenERP user. Default = 'demo'",
'action': 'store',
'default': 'demo',
}
option_ascii = {
'names': ('-a', '--ascii'),
'dest': 'ascii',
'help': "Convert name from UTF-8 to ASCII. Default = no, keep UTF-8",
'action': 'store_true',
'default': False,
}
option_geoloc = {
'names': ('-g', '--geoloc'),
'dest': 'geoloc',
'help': "Try to geolocate phone numbers unknown to OpenERP. This features "
"requires the 'phonenumbers' Python lib. To install it, run "
"'sudo pip install phonenumbers' Default = no",
'action': 'store_true',
'default': False,
}
option_geoloc_lang = {
'names': ('-l', '--geoloc-lang'),
'dest': 'lang',
'help': "Language in which the name of the country and city name will be "
"displayed by the geolocalisation database. Use the 2 letters ISO "
"code of the language. Default = 'en'",
'action': 'store',
'default': "en",
}
option_geoloc_country = {
'names': ('-c', '--geoloc-country'),
'dest': 'country',
'help': "2 letters ISO code for your country e.g. 'FR' for France. This "
"will be used by the geolocalisation system to parse the phone "
"number of the calling party. Default = 'FR'",
'action': 'store',
'default': "FR",
}
options = [
option_server,
option_port,
option_ssl,
option_database,
option_user,
option_password,
option_ascii,
option_geoloc,
option_geoloc_lang,
option_geoloc_country,
]
def stdout_write(string):
'''Wrapper on sys.stdout.write'''
@ -89,15 +176,17 @@ def stdout_write(string):
# When we output a command, we get an answer "200 result=1" on stdin
# Purge stdin to avoid these Asterisk error messages :
# utils.c ast_carefulwrite: write() returned error: Broken pipe
input_line = sys.stdin.readline()
sys.stdin.readline()
return True
def stderr_write(string):
'''Wrapper on sys.stderr.write'''
sys.stderr.write(string.encode(sys.stdout.encoding or 'utf-8', 'replace'))
sys.stdout.flush()
return True
def geolocate_phone_number(number, my_country_code, lang):
import phonenumbers
import phonenumbers.geocoder
@ -112,13 +201,16 @@ def geolocate_phone_number(number, my_country_code, lang):
res = city
else:
# Convert country code to country name
country = phonenumbers.geocoder._region_display_name(country_code, lang.lower())
country = phonenumbers.geocoder._region_display_name(
country_code, lang.lower()
)
if country and city:
res = country + ' ' + city
elif country and not city:
res = country
return res
def reformat_phone_number_before_query_openerp(number):
'''We match only on the end of the phone number'''
if len(number) >= 9:
@ -126,18 +218,25 @@ def reformat_phone_number_before_query_openerp(number):
else:
return number
def convert_to_ascii(my_unicode):
'''Convert to ascii, with clever management of accents (é -> e, è -> e)'''
"""Convert to ascii, with clever management of accents (é -> e, è -> e)"""
import unicodedata
if isinstance(my_unicode, unicode):
my_unicode_with_ascii_chars_only = ''.join((char for char in unicodedata.normalize('NFD', my_unicode) if unicodedata.category(char) != 'Mn'))
my_unicode_with_ascii_chars_only = ''.join(
(char
for char in unicodedata.normalize('NFD', my_unicode)
if unicodedata.category(char) != 'Mn')
)
return str(my_unicode_with_ascii_chars_only)
# If the argument is already of string type, we return it with the same value
# If the argument is already of string type, we return it with the same
# value
elif isinstance(my_unicode, str):
return my_unicode
else:
return False
def main(options, arguments):
# print 'options = %s' % options
# print 'arguments = %s' % arguments
@ -168,8 +267,12 @@ def main(options, arguments):
# If we already have a "True" caller ID name
# i.e. not just digits, but a real name, then we don't try to
# connect to OpenERP or geoloc, we just keep it
if stdinput.get('agi_calleridname') and not stdinput.get('agi_calleridname').isdigit() and stdinput.get('agi_calleridname').lower() not in ['asterisk', 'unknown', 'anonymous']:
stdout_write('VERBOSE "Incoming CallerID name is %s"\n' % stdinput.get('agi_calleridname'))
if (stdinput.get('agi_calleridname')
and not stdinput.get('agi_calleridname').isdigit()
and stdinput.get('agi_calleridname').lower()
not in ['asterisk', 'unknown', 'anonymous']):
stdout_write('VERBOSE "Incoming CallerID name is %s"\n'
% stdinput.get('agi_calleridname'))
stdout_write('VERBOSE "As it is a real name, we do not change it"\n')
return True
@ -182,26 +285,46 @@ def main(options, arguments):
# Match for particular cases and anonymous phone calls
# To test anonymous call in France, dial 3651 + number
if not input_cid_number.isdigit():
stdout_write('VERBOSE "CallerID number (%s) is not a digit"\n' % input_cid_number)
stdout_write('VERBOSE "CallerID number (%s) is not a digit"\n'
% input_cid_number)
exit(0)
stdout_write('VERBOSE "CallerID number = %s"\n' % input_cid_number)
res = False
if options.server: # Yes, this script can be used without "-s openerp_server" !
query_number = reformat_phone_number_before_query_openerp(input_cid_number)
# Yes, this script can be used without "-s openerp_server" !
if options.server:
query_number = reformat_phone_number_before_query_openerp(
input_cid_number
)
stderr_write("phone number sent to OpenERP = %s\n" % query_number)
if options.ssl:
stdout_write('VERBOSE "Starting XML-RPC secure request on OpenERP %s:%s"\n' % (options.server, str(options.port)))
stdout_write(
'VERBOSE "Starting XML-RPC secure request on OpenERP %s:%s"\n'
% (options.server, str(options.port))
)
protocol = 'https'
else:
stdout_write('VERBOSE "Starting clear XML-RPC request on OpenERP %s:%s"\n' % (options.server, str(options.port)))
stdout_write(
'VERBOSE "Starting clear XML-RPC request on OpenERP %s:%s"\n'
% (options.server, str(options.port))
)
protocol = 'http'
sock = xmlrpclib.ServerProxy('%s://%s:%s/xmlrpc/object' % (protocol, options.server, str(options.port)))
sock = xmlrpclib.ServerProxy(
'%s://%s:%s/xmlrpc/object'
% (protocol, options.server, str(options.port))
)
try:
res = sock.execute(options.database, options.user, options.password, 'res.partner.address', 'get_name_from_phone_number', query_number)
res = sock.execute(
options.database,
options.user,
options.password,
'res.partner.address',
'get_name_from_phone_number',
query_number
)
stdout_write('VERBOSE "End of XML-RPC request on OpenERP"\n')
if not res:
stdout_write('VERBOSE "Phone number not found in OpenERP"\n')
@ -218,14 +341,23 @@ def main(options, arguments):
res = res[0:40]
elif options.geoloc:
# if the number is not found in OpenERP, we try to geolocate
stdout_write('VERBOSE "Trying to geolocate with country %s and lang %s"\n' % (options.country, options.lang))
res = geolocate_phone_number(input_cid_number, options.country, options.lang)
stdout_write(
'VERBOSE "Trying to geolocate with country %s and lang %s"\n'
% (options.country, options.lang)
)
res = geolocate_phone_number(
input_cid_number,
options.country,
options.lang
)
else:
# if the number is not found in OpenERP and geoloc is off, we put 'default_cid_name' as CID Name
# if the number is not found in OpenERP and geoloc is off,
# we put 'default_cid_name' as CID Name
res = default_cid_name
# All SIP phones should support UTF-8... but in case you have analog phones over TDM
# or buggy phones, you should use the command line option --ascii
# All SIP phones should support UTF-8... but in case you have analog
# phones over TDM or buggy phones, you should use the command line option
# --ascii
if options.ascii:
res = convert_to_ascii(res)

219
asterisk_click2dial/wizard/open_calling_partner.py

@ -33,22 +33,54 @@ class wizard_open_calling_partner(osv.osv_memory):
_columns = {
# I can't set any field to readonly, because otherwize it would call
# default_get (and thus connect to Asterisk) a second time when the user
# clicks on one of the buttons
'calling_number': fields.char('Calling number', size=30, help="Phone number of calling party that has been obtained from Asterisk."),
'partner_address_id': fields.many2one('res.partner.address', 'Contact name', help="Partner contact related to the calling number. If there is none and you want to update an existing partner"),
'partner_id': fields.many2one('res.partner', 'Partner', help="Partner related to the calling number."),
'to_update_partner_address_id': fields.many2one('res.partner.address', 'Contact to update', help="Partner contact on which the phone or mobile number will be written"),
'current_phone': fields.related('to_update_partner_address_id', 'phone', type='char', relation='res.partner.address', string='Current phone'),
'current_mobile': fields.related('to_update_partner_address_id', 'mobile', type='char', relation='res.partner.address', string='Current mobile'),
# default_get (and thus connect to Asterisk) a second time when the
# user clicks on one of the buttons
'calling_number': fields.char(
'Calling number',
size=30,
help="Phone number of calling party that has been obtained from "
"Asterisk."
),
'partner_address_id': fields.many2one(
'res.partner.address',
'Contact name',
help="Partner contact related to the calling number. If there is "
"none and you want to update an existing partner"
),
'partner_id': fields.many2one(
'res.partner',
'Partner',
help="Partner related to the calling number."
),
'to_update_partner_address_id': fields.many2one(
'res.partner.address',
'Contact to update',
help="Partner contact on which the phone or mobile number will be "
"written"
),
'current_phone': fields.related(
'to_update_partner_address_id',
'phone',
type='char',
relation='res.partner.address',
string='Current phone'
),
'current_mobile': fields.related(
'to_update_partner_address_id',
'mobile',
type='char',
relation='res.partner.address',
string='Current mobile'
),
}
def default_get(self, cr, uid, fields, context=None):
'''Thanks to the default_get method, we are able to query Asterisk and
get the corresponding partner when we launch the wizard'''
"""Thanks to the default_get method, we are able to query Asterisk and
get the corresponding partner when we launch the wizard"""
res = {}
calling_number = self.pool.get('asterisk.server')._get_calling_number(cr, uid, context=context)
calling_number = self.pool.get('asterisk.server')._get_calling_number(
cr, uid, context=context
)
# To test the code without Asterisk server
# calling_number = "0141981242"
if calling_number:
@ -58,7 +90,10 @@ class wizard_open_calling_partner(osv.osv_memory):
number_to_search = calling_number[-9:len(calling_number)]
else:
number_to_search = calling_number
partner = self.pool.get('res.partner.address').get_partner_from_phone_number(cr, uid, number_to_search, context=context)
address_obj = self.pool.get('res.partner.address')
partner = address_obj.get_partner_from_phone_number(
cr, uid, number_to_search, context=context
)
if partner:
res['partner_address_id'] = partner[0]
res['partner_id'] = partner[1]
@ -67,22 +102,35 @@ class wizard_open_calling_partner(osv.osv_memory):
res['partner_address_id'] = False
res['to_update_partner_address_id'] = False
else:
_logger.debug("Could not get the calling number from Asterisk.")
raise osv.except_osv(_('Error :'), _("Could not get the calling number from Asterisk. Are you currently on the phone ? If yes, check your setup and look at the OpenERP debug logs."))
_logger.debug(
"Could not get the calling number from Asterisk."
)
raise osv.except_osv(
_('Error :'),
_("Could not get the calling number from Asterisk. Are you "
"currently on the phone ? If yes, check your setup and "
"look at the OpenERP debug logs.")
)
return res
def open_filtered_object(self, cr, uid, ids, oerp_object, context=None):
'''Returns the action that opens the list view of the 'oerp_object'
given as argument filtered on the partner'''
"""Returns the action that opens the list view of the 'oerp_object'
given as argument filtered on the partner"""
# This module only depends on "base"
# and I don't want to add a dependancy on "sale" or "account"
# So I just check here that the model exists, to avoid a crash
if not self.pool.get('ir.model').search(cr, uid, [('model', '=', oerp_object._name)], context=context):
raise osv.except_osv(_('Error :'), _("The object '%s' is not found in your OpenERP database, probably because the related module is not installed." % oerp_object._description))
partner = self.read(cr, uid, ids[0], ['partner_id'], context=context)['partner_id']
if not self.pool.get('ir.model').search(
cr, uid, [('model', '=', oerp_object._name)], context=context):
raise osv.except_osv(
_('Error :'),
_("The object '%s' is not found in your OpenERP database, "
"probably because the related module is not installed."
% oerp_object._description)
)
partner = self.read(
cr, uid, ids[0], ['partner_id'], context=context)['partner_id']
if partner:
action = {
'name': oerp_object._description,
@ -98,18 +146,20 @@ class wizard_open_calling_partner(osv.osv_memory):
else:
return False
def open_sale_orders(self, cr, uid, ids, context=None):
'''Function called by the related button of the wizard'''
return self.open_filtered_object(cr, uid, ids, self.pool.get('sale.order'), context=context)
"""Function called by the related button of the wizard"""
return self.open_filtered_object(
cr, uid, ids, self.pool.get('sale.order'), context=context
)
def open_invoices(self, cr, uid, ids, context=None):
'''Function called by the related button of the wizard'''
return self.open_filtered_object(cr, uid, ids, self.pool.get('account.invoice'), context=context)
"""Function called by the related button of the wizard"""
return self.open_filtered_object(
cr, uid, ids, self.pool.get('account.invoice'), context=context
)
def simple_open(self, cr, uid, ids, object_name='res.partner', context=None):
def simple_open(self, cr, uid, ids, object_name='res.partner',
context=None):
if object_name == 'res.partner':
field = 'partner_id'
label = 'Partner'
@ -117,8 +167,13 @@ class wizard_open_calling_partner(osv.osv_memory):
field = 'partner_address_id'
label = 'Contact'
else:
raise osv.except_osv(_('Error :'), "This object '%s' is not supported" % object_name)
record_to_open = self.read(cr, uid, ids[0], [field], context=context)[field]
raise osv.except_osv(
_('Error :'),
"This object '%s' is not supported"
% object_name
)
record_to_open = self.read(
cr, uid, ids[0], [field], context=context)[field]
if record_to_open:
return {
'name': label,
@ -133,24 +188,33 @@ class wizard_open_calling_partner(osv.osv_memory):
else:
return False
def open_partner(self, cr, uid, ids, context=None):
'''Function called by the related button of the wizard'''
return self.simple_open(cr, uid, ids, object_name='res.partner', context=context)
"""Function called by the related button of the wizard"""
return self.simple_open(
cr, uid, ids, object_name='res.partner', context=context
)
def open_partner_address(self, cr, uid, ids, context=None):
'''Function called by the related button of the wizard'''
return self.simple_open(cr, uid, ids, object_name='res.partner.address', context=context)
def create_partner_address(self, cr, uid, ids, phone_type='phone', context=None):
'''Function called by the related button of the wizard'''
calling_number = self.read(cr, uid, ids[0], ['calling_number'], context=context)['calling_number']
"""Function called by the related button of the wizard"""
return self.simple_open(
cr, uid, ids, object_name='res.partner.address', context=context
)
def create_partner_address(self, cr, uid, ids, phone_type='phone',
context=None):
"""Function called by the related button of the wizard"""
calling_number = self.read(
cr, uid, ids[0], ['calling_number'], context=context
)['calling_number']
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
ast_server = self.pool.get('asterisk.server')._get_asterisk_server_from_user(cr, uid, user, context=context)
ast_srv_obj = self.pool.get('asterisk.server')
ast_server = ast_srv_obj._get_asterisk_server_from_user(
cr, uid, user, context=context
)
# Convert the number to the international format
number_to_write = self.pool.get('asterisk.server')._convert_number_to_international_format(cr, uid, calling_number, ast_server, context=context)
number_to_write = ast_srv_obj._convert_number_to_international_format(
cr, uid, calling_number, ast_server, context=context
)
context['default_' + phone_type] = number_to_write
action = {
@ -165,23 +229,37 @@ class wizard_open_calling_partner(osv.osv_memory):
}
return action
def create_partner_address_phone(self, cr, uid, ids, context=None):
return self.create_partner_address(cr, uid, ids, phone_type='phone', context=context)
return self.create_partner_address(
cr, uid, ids, phone_type='phone', context=context
)
def create_partner_address_mobile(self, cr, uid, ids, context=None):
return self.create_partner_address(cr, uid, ids, phone_type='mobile', context=context)
return self.create_partner_address(
cr, uid, ids, phone_type='mobile', context=context
)
def update_partner_address(self, cr, uid, ids, phone_type='mobile', context=None):
def update_partner_address(self, cr, uid, ids, phone_type='mobile',
context=None):
cur_wizard = self.browse(cr, uid, ids[0], context=context)
if not cur_wizard.to_update_partner_address_id:
raise osv.except_osv(_('Error :'), _("Select the contact to update."))
raise osv.except_osv(
_('Error :'),
_("Select the contact to update.")
)
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
ast_server = self.pool.get('asterisk.server')._get_asterisk_server_from_user(cr, uid, user, context=context)
number_to_write = self.pool.get('asterisk.server')._convert_number_to_international_format(cr, uid, cur_wizard.calling_number, ast_server, context=context)
self.pool.get('res.partner.address').write(cr, uid, cur_wizard.to_update_partner_address_id.id, {phone_type: number_to_write}, context=context)
ast_srv_obj = self.pool.get('asterisk.server')
ast_server = ast_srv_obj._get_asterisk_server_from_user(
cr, uid, user, context=context
)
number_to_write = ast_srv_obj._convert_number_to_international_format(
cr, uid, cur_wizard.calling_number, ast_server, context=context
)
self.pool.get('res.partner.address').write(
cr, uid, cur_wizard.to_update_partner_address_id.id,
{phone_type: number_to_write},
context=context
)
action = {
'name': 'Contact: ' + cur_wizard.to_update_partner_address_id.name,
'view_type': 'form',
@ -194,24 +272,35 @@ class wizard_open_calling_partner(osv.osv_memory):
}
return action
def update_partner_address_phone(self, cr, uid, ids, context=None):
return self.update_partner_address(cr, uid, ids, phone_type='phone', context=context)
return self.update_partner_address(
cr, uid, ids, phone_type='phone', context=context
)
def update_partner_address_mobile(self, cr, uid, ids, context=None):
return self.update_partner_address(cr, uid, ids, phone_type='mobile', context=context)
return self.update_partner_address(
cr, uid, ids, phone_type='mobile', context=context
)
def onchange_to_update_partner_address(self, cr, uid, ids, to_update_partner_address_id, context=None):
def onchange_to_update_partner_address(self, cr, uid, ids,
to_update_partner_address_id,
context=None):
res = {}
res['value'] = {}
if to_update_partner_address_id:
to_update_partner_address = self.pool.get('res.partner.address').browse(cr, uid, to_update_partner_address_id, context=context)
res['value'].update({'current_phone': to_update_partner_address.phone,
'current_mobile': to_update_partner_address.mobile})
address_obj = self.pool.get('res.partner.address')
to_update_partner_address = address_obj.browse(
cr, uid, to_update_partner_address_id, context=context
)
res['value'].update({
'current_phone': to_update_partner_address.phone,
'current_mobile': to_update_partner_address.mobile
})
else:
res['value'].update({'current_phone': False, 'current_mobile': False})
res['value'].update({
'current_phone': False,
'current_mobile': False,
})
return res
wizard_open_calling_partner()

48
asterisk_click2dial/wizard/reformat_all_phonenumbers.py

@ -21,43 +21,65 @@
from osv import osv, fields
import logging
from tools.translate import _
_logger = logging.getLogger(__name__)
class reformat_all_phonenumbers(osv.osv_memory):
_name = "reformat.all.phonenumbers"
_description = "Reformat all phone numbers"
_columns = {
'phonenumbers_not_reformatted': fields.text("Phone numbers that couldn't be reformatted"),
'phonenumbers_not_reformatted': fields.text(
"Phone numbers that couldn't be reformatted"
),
}
def run_reformat_all_phonenumbers(self, cr, uid, ids, context=None):
addr_obj = self.pool.get('res.partner.address')
phonefields = ['phone', 'fax', 'mobile']
_logger.info('Starting to reformat all the phone numbers')
all_addr_ids = addr_obj.search(cr, uid, ['|', ('active', '=', True), ('active', '=', False)], context=context)
all_addr_ids = addr_obj.search(
cr,
uid,
['|', ('active', '=', True), ('active', '=', False)],
context=context
)
phonenumbers_not_reformatted = ''
for addr in addr_obj.read(cr, uid, all_addr_ids, ['name'] + phonefields, context=context):
for addr in addr_obj.read(
cr, uid, all_addr_ids, ['name'] + phonefields, context=context
):
init_addr = addr.copy()
# addr is _updated_ by the fonction _reformat_phonenumbers()
try:
addr_obj._reformat_phonenumbers(cr, uid, addr, context=context)
except Exception, e:
#raise osv.except_osv(_('Error :'), _("Problem on partner contact '%s'. Error message: %s" % (init_addr.get('name'), e[1])))
phonenumbers_not_reformatted += "Problem on partner contact '%s'. Error message: %s" % (init_addr.get('name'), e[1]) + "\n"
_logger.warning("Problem on partner contact '%s'. Error message: %s" % (init_addr.get('name'), e[1]))
phonenumbers_not_reformatted += (
"Problem on partner contact '%s'. "
"Error message: %s" % (init_addr.get('name'), e[1]) + "\n"
)
_logger.warning(
"Problem on partner contact '%s'. Error message: %s"
% (init_addr.get('name'), e[1])
)
continue
# Test if the phone numbers have been changed
if any([init_addr.get(field) != addr.get(field) for field in phonefields]):
if any([init_addr.get(field) != addr.get(field)
for field in phonefields]):
addr.pop('id')
addr.pop('name')
_logger.info('Reformating phone number: FROM %s TO %s' % (unicode(init_addr), unicode(addr)))
_logger.info(
'Reformating phone number: FROM %s TO %s'
% (unicode(init_addr), unicode(addr))
)
addr_obj.write(cr, uid, init_addr['id'], addr, context=context)
if not phonenumbers_not_reformatted:
phonenumbers_not_reformatted = 'All phone numbers have been reformatted successfully.'
self.write(cr, uid, ids[0], {'phonenumbers_not_reformatted': phonenumbers_not_reformatted}, context=context)
phonenumbers_not_reformatted = (
'All phone numbers have been reformatted successfully.'
)
self.write(
cr, uid, ids[0],
{'phonenumbers_not_reformatted': phonenumbers_not_reformatted},
context=context
)
_logger.info('End of the phone number reformatting wizard')
return True

7
asterisk_click2dial_crm/__init__.py

@ -2,14 +2,15 @@
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# All Rights Reserved.
# Copyright (c) 2012 Akretion (http://www.akretion.com)
# @author: Jesús Martín <jmartin@zikzakmedia.com>
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# 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,

20
asterisk_click2dial_crm/__openerp__.py

@ -2,14 +2,15 @@
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# All Rights Reserved.
# Copyright (c) 2012 Akretion (http://www.akretion.com)
# @author: Jesús Martín <jmartin@zikzakmedia.com>
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# 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,
@ -41,13 +42,16 @@
should disable the corresponding option in the 'Telephony' tab of the 'User'
form.
Second, when you receive a phone call and run the wizard "Open calling partner",
if the partner is found in OpenERP, you will see a button that proposes to create
an inbound phone call in the CRM.
Second, when you receive a phone call and run the wizard
"Open calling partner", if the partner is found in OpenERP, you will see a
button that proposes to create an inbound phone call in the CRM.
This module has been initially developped by Zikzakmedia and has been enhanced by Akretion.
This module has been initially developped by Zikzakmedia and has been enhanced
by Akretion.
A detailed documentation for the OpenERP-Asterisk connector is available on the Akretion Web site : http://www.akretion.com/en/products-and-services/openerp-asterisk-voip-connector
A detailed documentation for the OpenERP-Asterisk connector is available on the
Akretion Web site :
http://www.akretion.com/products-and-services/openerp-asterisk-voip-connector
""",
"depends": [
'asterisk_click2dial',

21
asterisk_click2dial_crm/asterisk_click2dial_crm.py

@ -2,14 +2,15 @@
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# All Rights Reserved.
# Copyright (c) 2012 Akretion (http://www.akretion.com)
# @author: Jesús Martín <jmartin@zikzakmedia.com>
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# 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,
@ -23,8 +24,7 @@
##############################################################################
from osv import osv, fields
# Lib to translate error messages
from tools.translate import _
class res_partner_address(osv.osv):
_inherit = "res.partner.address"
@ -37,7 +37,9 @@ class res_partner_address(osv.osv):
'''
if context is None:
context = {}
super(res_partner_address, self).dial(cr, uid, ids, phone_field=phone_field, context=context)
super(res_partner_address, self).dial(
cr, uid, ids, phone_field=phone_field, context=context
)
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
context['partner_address_id'] = ids[0]
action_start_wizard = {
@ -60,17 +62,16 @@ res_partner_address()
class res_users(osv.osv):
_inherit = "res.users"
_columns = {
# Field name starts with 'context_' to allow modification by the user
# in his preferences, cf server-61/openerp/addons/base/res/res_users.py
# line 377 in "def write" of "class users"
'context_propose_creation_crm_call': fields.boolean('Propose to create a call in CRM after a click2dial'),
'context_propose_creation_crm_call': fields.boolean(
'Propose to create a call in CRM after a click2dial'
),
}
_defaults = {
'context_propose_creation_crm_call': True,
}
res_users()

4
asterisk_click2dial_crm/wizard/__init__.py

@ -6,8 +6,8 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# 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,

51
asterisk_click2dial_crm/wizard/create_crm_phonecall.py

@ -2,14 +2,15 @@
##############################################################################
#
# Asterisk click2dial CRM module for OpenERP
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com) All Rights Reserved.
# Copyright (c) 2011 Zikzakmedia S.L. (http://zikzakmedia.com)
# All Rights Reserved.
# Copyright (c) 2012 Akretion (http://www.akretion.com)
# @author: Jesús Martín <jmartin@zikzakmedia.com>
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# 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,
@ -22,31 +23,44 @@
#
##############################################################################
from osv import osv, fields
# Lib to translate error messages
from tools.translate import _
from osv import osv
class wizard_create_crm_phonecall(osv.osv_memory):
_name = "wizard.create.crm.phonecall"
def button_create_outgoing_phonecall(self, cr, uid, ids, context=None):
partner_address = self.pool.get('res.partner.address').browse(cr, uid, context.get('partner_address_id'), context=context)
return self._create_open_crm_phonecall(cr, uid, partner_address, crm_categ='Outbound', context=context)
partner_address = self.pool.get('res.partner.address').browse(
cr, uid, context.get('partner_address_id'), context=context
)
return self._create_open_crm_phonecall(
cr, uid, partner_address, crm_categ='Outbound', context=context
)
def _create_open_crm_phonecall(self, cr, uid, partner_address, crm_categ, context=None):
def _create_open_crm_phonecall(self, cr, uid, partner_address, crm_categ,
context=None):
if context is None:
context = {}
categ_ids = self.pool.get('crm.case.categ').search(cr, uid, [('name','=',crm_categ)], context={'lang': 'en_US'})
case_section_ids = self.pool.get('crm.case.section').search(cr, uid, [('member_ids', 'in', uid)], context=context)
categ_ids = self.pool.get('crm.case.categ').search(
cr, uid, [('name', '=', crm_categ)], context={'lang': 'en_US'}
)
case_section_ids = self.pool.get('crm.case.section').search(
cr, uid, [('member_ids', 'in', uid)], context=context
)
context.update({
'default_partner_id': partner_address.partner_id and partner_address.partner_id.id or False,
'default_partner_id': (
partner_address.partner_id
and partner_address.partner_id.id
or False
),
'default_partner_address_id': partner_address.id,
'default_partner_contact': partner_address.name,
'default_partner_phone': partner_address.phone,
'default_partner_mobile': partner_address.mobile,
'default_categ_id': categ_ids and categ_ids[0] or False,
'default_section_id': case_section_ids and case_section_ids[0] or False,
'default_section_id': (
case_section_ids and case_section_ids[0] or False
),
})
return {
@ -68,9 +82,14 @@ class wizard_open_calling_partner(osv.osv_memory):
_inherit = "wizard.open.calling.partner"
def create_incoming_phonecall(self, cr, uid, ids, crm_categ, context=None):
'''Started by button on 'open calling partner wizard'''
partner_address = self.browse(cr, uid, ids[0], context=context).partner_address_id
action = self.pool.get('wizard.create.crm.phonecall')._create_open_crm_phonecall(cr, uid, partner_address, crm_categ='Inbound', context=context)
"""Started by button on 'open calling partner wizard"""
partner_address = (
self.browse(cr, uid, ids[0], context=context).partner_address_id
)
wizard_obj = self.pool.get('wizard.create.crm.phonecall')
action = wizard_obj._create_open_crm_phonecall(
cr, uid, partner_address, crm_categ='Inbound', context=context
)
return action
wizard_open_calling_partner()
Loading…
Cancel
Save