Browse Source

- smaller "Dial" button on partner contact

- option to allow the use of phone numbers in National format,
  as discussed with Albert. This option is on the definition of the server.
  -> required a large change of the reformat number process
- added exact link to documentation in module description
- some clean-up
6.0
Alexis de Lattre 14 years ago
parent
commit
d6db0aa211
  1. 2
      asterisk_click2dial/__terp__.py
  2. 109
      asterisk_click2dial/asterisk_click2dial.py
  3. 2
      asterisk_click2dial/asterisk_click2dial_demo.xml
  4. 3
      asterisk_click2dial/asterisk_server_view.xml
  5. 24
      asterisk_click2dial/res_partner_view.xml

2
asterisk_click2dial/__terp__.py

@ -35,7 +35,7 @@ Here is how it works :
4) Asterisk dials the phone number found in OpenERP in place of the user. 4) Asterisk dials the phone number found in OpenERP in place of the user.
5) If the remote party answers, the user can talk to his correspondent. 5) If the remote party answers, the user can talk to his correspondent.
Documentation for this module is available on the Akretion Web site : http://www.akretion.com/""",
A detailed documentation for this module is available on the Akretion Web site : http://www.akretion.com/en/blog/2010/09/23/akretion-and-anevia-present-the-asterisk_click2dial-module-unleash-your-asterisk-phones-in-openerp/""",
'author': 'Alexis de Lattre', 'author': 'Alexis de Lattre',
'website': 'http://www.akretion.com/', 'website': 'http://www.akretion.com/',
'depends': ['base'], 'depends': ['base'],

109
asterisk_click2dial/asterisk_click2dial.py

@ -21,30 +21,34 @@
# TODO list : # TODO list :
# Wait time don't seem to be taken into account # Wait time don't seem to be taken into account
# In fact, maybe it the waittime for external correspondants !
# -> if yes, default waittime should be changed from 15 to 30
from osv import osv, fields from osv import osv, fields
# Lib required to open a socket towards Asterisk
# Lib required to open a socket (needed to communicate with Asterisk server)
import socket import socket
# Lib required to print logs # Lib required to print logs
import netsvc import netsvc
class asterisk_server(osv.osv): class asterisk_server(osv.osv):
_name = "asterisk.server" _name = "asterisk.server"
_description = "Asterisk Servers" _description = "Asterisk Servers"
_columns = { _columns = {
'name' : fields.char('Asterisk server name', size=50, required=True, help="Asterisk server name."),
'ip_address' : fields.char('Asterisk IP addr. or DNS', size=50, required=True, help="IPv4 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."),
'name' : fields.char('Asterisk server name', size=50, required=True, help="Asterisk server name."),
'ip_address' : fields.char('Asterisk IP addr. or DNS', size=50, required=True, help="IPv4 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'."), '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', 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'."), 'international_prefix' : fields.char('International prefix', 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', 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'."), 'country_prefix' : fields.char('My country prefix', 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'."),
'national_format_allowed' : fields.boolean('National format allowed ?', help="Do we allow to use click2dial on phone numbers written in national format, e.g. 01 41 98 12 42, or only in the international format, e.g. +33 1 41 98 12 42 ?"),
'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."), '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."), '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."),
'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."), '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."), '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=40, help="Set Alert-Info header in SIP request to user's IP Phone. If empty, the Alert-Info header will not be added. You can use it to have a special ring tone for click2dial, for example you could choose a silent ring tone."),
'alert_info' : fields.char('Alert-Info SIP header', size=40, help="Set Alert-Info header in SIP request to user's IP Phone. If empty, the Alert-Info header will not be added. You can use it to have a special ring tone for click2dial, for example you could choose a silent ring tone."),
'company_id' : fields.many2one('res.company', 'Company', help="Company who uses the Asterisk server."), 'company_id' : fields.many2one('res.company', 'Company', help="Company who uses the Asterisk server."),
} }
@ -103,9 +107,6 @@ class asterisk_server(osv.osv):
return False return False
return True return True
# TODO : is it possible to read the field name inside the constraint
# function in order to avoid using as many functions as fields to
# check prefix ?
_constraints = [ _constraints = [
(_only_digits_out_prefix, "Only use digits for the 'out prefix' or leave empty", ['out_prefix']), (_only_digits_out_prefix, "Only use digits for the 'out prefix' or leave empty", ['out_prefix']),
(_only_digits_country_prefix, "Only use digits for the 'country prefix'", ['country_prefix']), (_only_digits_country_prefix, "Only use digits for the 'country prefix'", ['country_prefix']),
@ -116,6 +117,7 @@ class asterisk_server(osv.osv):
(_check_port, 'TCP ports range from 1 to 65535', ['port']), (_check_port, 'TCP ports range from 1 to 65535', ['port']),
] ]
# This function is dedicated to the transformation of the number # This function is dedicated to the transformation of the number
# available in OpenERP to the number that Asterisk should dial. # available in OpenERP to the number that Asterisk should dial.
# You may have to inherit this function in another module specific # You may have to inherit this function in another module specific
@ -123,29 +125,22 @@ class asterisk_server(osv.osv):
# the OpenERP numbers. # the OpenERP numbers.
def reformat_number(self, cr, uid, ids, erp_number, ast_server, context): def reformat_number(self, cr, uid, ids, erp_number, ast_server, context):
logger = netsvc.Logger() logger = netsvc.Logger()
invalid_format_msg = "The phone number is not written in valid international format. Example of valid international format : +33 1 41 98 12 42"
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."
# Let's call the variable tmp_number now # Let's call the variable tmp_number now
tmp_number = erp_number tmp_number = erp_number
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Number before reformat = ' + tmp_number) logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Number before reformat = ' + tmp_number)
# First, we remove all stupid caracters and spaces
try:
for i in [' ', '.', '(', ')', '[', ']', '-', '/']:
tmp_number = tmp_number.replace(i, '')
except:
raise osv.except_osv('Invalid phone number', invalid_format_msg)
# Check if empty
if not tmp_number:
raise osv.except_osv(error_title_msg, invalid_format_msg)
# Remove the starting '+' of the number
if tmp_number[0]=='+':
tmp_number = tmp_number.replace('+','')
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Number after removal of special char = ' + tmp_number)
else:
raise osv.except_osv('Invalid phone number', invalid_format_msg)
# At this stage, 'tmp_number' should only contain digits
if not tmp_number.isdigit():
raise osv.except_osv('Invalid phone number', invalid_format_msg)
# First, we remove all stupid caracters and spaces
for i in [' ', '.', '(', ')', '[', ']', '-', '/']:
tmp_number = tmp_number.replace(i, '')
# Before starting to use prefix, we convert empty prefix whose value # Before starting to use prefix, we convert empty prefix whose value
# is False to an empty string # is False to an empty string
@ -154,23 +149,42 @@ class asterisk_server(osv.osv):
international_prefix = (ast_server.international_prefix or '') international_prefix = (ast_server.international_prefix or '')
out_prefix = (ast_server.out_prefix or '') out_prefix = (ast_server.out_prefix or '')
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Country prefix = ' + country_prefix)
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.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'National prefix = ' + national_prefix + ' - Number with national prefix = ' + tmp_number)
# International format
if tmp_number[0]=='+':
# Remove the starting '+' of the number
tmp_number = tmp_number.replace('+','')
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Number after removal of special char = ' + tmp_number)
# At this stage, 'tmp_number' should only contain digits
if not tmp_number.isdigit():
raise osv.except_osv(error_title_msg, invalid_format_msg)
else:
# If the number is an international number,
# add 'international prefix'
tmp_number = international_prefix + tmp_number
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'International prefix = ' + international_prefix + ' - Number with international prefix = ' + tmp_number)
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Country prefix = ' + country_prefix)
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.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'National prefix = ' + national_prefix + ' - Number with national prefix = ' + tmp_number)
# Add 'out prefix' to all numbers - Caution : out prefix can be False
else:
# If the number is an international number,
# add 'international prefix'
tmp_number = international_prefix + tmp_number
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'International prefix = ' + international_prefix + ' - Number with international prefix = ' + tmp_number)
# National format, allowed
elif ast_server.national_format_allowed:
# No treatment required
if not tmp_number.isdigit():
raise osv.except_osv(error_title_msg, invalid_national_format_msg)
# National format, disallowed
elif not ast_server.national_format_allowed:
raise osv.except_osv(error_title_msg, invalid_international_format_msg)
# Add 'out prefix' to all numbers
tmp_number = out_prefix + tmp_number tmp_number = out_prefix + tmp_number
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Out prefix = ' + out_prefix)
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Number that will be sent to Asterisk = ' + tmp_number)
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, 'Out prefix = ' + out_prefix + ' - Number to be sent to Asterisk = ' + tmp_number)
return tmp_number return tmp_number
# Open the socket to the Asterisk Manager Interface # Open the socket to the Asterisk Manager Interface
@ -206,6 +220,7 @@ class asterisk_server(osv.osv):
try: try:
ast_ip = socket.gethostbyname(str(ast_server.ip_address)) ast_ip = socket.gethostbyname(str(ast_server.ip_address))
except: except:
logger.notifyChannel('asterisk_click2dial', netsvc.LOG_DEBUG, "Can't resolve the Asterisk server's DNS : " + str(ast_server.ip_address))
raise osv.except_osv('Wrong DNS', "Can't resolve the DNS name of the Asterisk server.") raise osv.except_osv('Wrong DNS', "Can't resolve the DNS name of the Asterisk server.")
try: try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -220,8 +235,8 @@ class asterisk_server(osv.osv):
sock.send('CallerId: '+str(user.callerid)+'\r\n') sock.send('CallerId: '+str(user.callerid)+'\r\n')
sock.send('Exten: '+str(ast_number)+'\r\n') sock.send('Exten: '+str(ast_number)+'\r\n')
sock.send('Context: '+str(ast_server.context)+'\r\n') sock.send('Context: '+str(ast_server.context)+'\r\n')
if not ast_server.alert-info and user.asterisk_chan_type == 'SIP':
sock.send('Variable: SIPAddHeader=Alert-Info: '+str(ast_server.alert-info)+'\r\n')
if not ast_server.alert_info and user.asterisk_chan_type == 'SIP':
sock.send('Variable: SIPAddHeader=Alert_Info: '+str(ast_server.alert_info)+'\r\n')
sock.send('Priority: '+str(ast_server.extension_priority)+'\r\n\r\n') sock.send('Priority: '+str(ast_server.extension_priority)+'\r\n\r\n')
sock.send('Action: Logoff\r\n\r\n') sock.send('Action: Logoff\r\n\r\n')
sock.close() sock.close()
@ -233,14 +248,15 @@ class asterisk_server(osv.osv):
asterisk_server() asterisk_server()
# Parameters specific for each user
class res_users(osv.osv): class res_users(osv.osv):
_name = "res.users" _name = "res.users"
_inherit = "res.users" _inherit = "res.users"
_columns = { _columns = {
'internal_number' : fields.char('Internal number', size=15, help="User's internal phone number."),
'internal_number' : fields.char('Internal number', size=15, help="User's internal phone number."),
'callerid' : fields.char('Caller ID', size=50, help="Caller ID used for the calls initiated by this user."), 'callerid' : fields.char('Caller ID', size=50, help="Caller ID used for the calls initiated by this user."),
'asterisk_chan_type' : fields.selection([('SIP', 'SIP'), ('IAX2', 'IAX2'), ('DAHDI', 'DAHDI'), ('Zap', 'Zap'), ('Skinny', 'Skinny'), ('MGCP', 'MGCP'), ('mISDN', 'mISDN'), ('H323', 'H323')], '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'."),
'asterisk_server_id' : fields.many2one('asterisk.server', 'Asterisk server', help="Asterisk server on which the user's phone is connected."),
'asterisk_chan_type' : fields.selection([('SIP', 'SIP'), ('IAX2', 'IAX2'), ('DAHDI', 'DAHDI'), ('Zap', 'Zap'), ('Skinny', 'Skinny'), ('MGCP', 'MGCP'), ('mISDN', 'mISDN'), ('H323', 'H323')], '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'."),
'asterisk_server_id' : fields.many2one('asterisk.server', 'Asterisk server', help="Asterisk server on which the user's phone is connected."),
} }
_defaults = { _defaults = {
@ -266,6 +282,7 @@ class res_partner_address(osv.osv):
res_partner_address() res_partner_address()
# This module supports multi-company
class res_company(osv.osv): class res_company(osv.osv):
_name = "res.company" _name = "res.company"
_inherit = "res.company" _inherit = "res.company"

2
asterisk_click2dial/asterisk_click2dial_demo.xml

@ -8,7 +8,7 @@
<field name="login">click2dial</field> <field name="login">click2dial</field>
<field name="password">mypassword</field> <field name="password">mypassword</field>
<field name="context">from-internal</field> <field name="context">from-internal</field>
<field name="alert-info">info=&lt;Bellcore-dr5&gt;</field>
<field name="alert_info">info=&lt;Bellcore-dr5&gt;</field>
<field name="company_id" ref="base.main_company" /> <field name="company_id" ref="base.main_company" />
</record> </record>
<record id="base.user_root" model="res.users"> <record id="base.user_root" model="res.users">

3
asterisk_click2dial/asterisk_server_view.xml

@ -33,7 +33,8 @@
<field name="national_prefix" /> <field name="national_prefix" />
<field name="international_prefix" /> <field name="international_prefix" />
<field name="country_prefix" select="1" /> <field name="country_prefix" select="1" />
<field name="alert-info" />
<field name="national_format_allowed" />
<field name="alert_info" />
<field name="wait_time" /> <field name="wait_time" />
</form> </form>
</field> </field>

24
asterisk_click2dial/res_partner_view.xml

@ -11,15 +11,15 @@
<data> <data>
<field name="phone" position="replace"> <field name="phone" position="replace">
<label string="Phone : " align="1.0" /> <label string="Phone : " align="1.0" />
<group colspan="1" col="2">
<field name="phone" nolabel="1"/>
<group colspan="1" col="5">
<field name="phone" nolabel="1" colspan="4" />
<button name="action_dial_phone" string="Dial" type="object"/> <button name="action_dial_phone" string="Dial" type="object"/>
</group> </group>
</field> </field>
<field name="mobile" position="replace"> <field name="mobile" position="replace">
<label string="Mobile : " align="1.0" /> <label string="Mobile : " align="1.0" />
<group colspan="1" col="2">
<field name="mobile" nolabel="1"/>
<group colspan="1" col="5">
<field name="mobile" nolabel="1" colspan="4" />
<button name="action_dial_mobile" string="Dial" type="object"/> <button name="action_dial_mobile" string="Dial" type="object"/>
</group> </group>
</field> </field>
@ -36,15 +36,15 @@
<data> <data>
<field name="phone" position="replace"> <field name="phone" position="replace">
<label string="Phone : " align="1.0" /> <label string="Phone : " align="1.0" />
<group colspan="1" col="2">
<field name="phone" nolabel="1"/>
<group colspan="1" col="5">
<field name="phone" nolabel="1" colspan="4" />
<button name="action_dial_phone" string="Dial" type="object"/> <button name="action_dial_phone" string="Dial" type="object"/>
</group> </group>
</field> </field>
<field name="mobile" position="replace"> <field name="mobile" position="replace">
<label string="Mobile : " align="1.0" /> <label string="Mobile : " align="1.0" />
<group colspan="1" col="2">
<field name="mobile" nolabel="1"/>
<group colspan="1" col="5">
<field name="mobile" nolabel="1" colspan="4" />
<button name="action_dial_mobile" string="Dial" type="object"/> <button name="action_dial_mobile" string="Dial" type="object"/>
</group> </group>
</field> </field>
@ -61,15 +61,15 @@
<data> <data>
<field name="phone" position="replace"> <field name="phone" position="replace">
<label string="Phone : " align="1.0" /> <label string="Phone : " align="1.0" />
<group colspan="1" col="2">
<field name="phone" nolabel="1"/>
<group colspan="1" col="5">
<field name="phone" nolabel="1" colspan="4" />
<button name="action_dial_phone" string="Dial" type="object"/> <button name="action_dial_phone" string="Dial" type="object"/>
</group> </group>
</field> </field>
<field name="mobile" position="replace"> <field name="mobile" position="replace">
<label string="Mobile : " align="1.0" /> <label string="Mobile : " align="1.0" />
<group colspan="1" col="2">
<field name="mobile" nolabel="1"/>
<group colspan="1" col="5">
<field name="mobile" nolabel="1" colspan="4" />
<button name="action_dial_mobile" string="Dial" type="object"/> <button name="action_dial_mobile" string="Dial" type="object"/>
</group> </group>
</field> </field>

Loading…
Cancel
Save