diff --git a/asterisk_click2dial/__openerp__.py b/asterisk_click2dial/__openerp__.py index 0877607..deef09d 100644 --- a/asterisk_click2dial/__openerp__.py +++ b/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, } - diff --git a/asterisk_click2dial/asterisk_click2dial.py b/asterisk_click2dial/asterisk_click2dial.py index cacd133..663a182 100644 --- a/asterisk_click2dial/asterisk_click2dial.py +++ b/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,14 +270,17 @@ 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(' 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,24 +446,36 @@ 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 ''), - context = ast_server.context, - extension = ast_number, - priority = str(ast_server.extension_priority), - timeout = str(ast_server.wait_time*1000), - caller_id = user.callerid, - variable = variable) + 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), + timeout=str(ast_server.wait_time * 1000), + 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,32 +484,48 @@ 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() - #from pprint import pprint - #pprint(list_chan) + # from pprint import pprint + # pprint(list_chan) _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,34 +538,76 @@ 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."), - # 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([ - ('SIP', 'SIP'), - ('Local', 'Local'), - ('IAX2', 'IAX2'), - ('DAHDI', 'DAHDI'), - ('Zap', 'Zap'), - ('Skinny', 'Skinny'), - ('MGCP', 'MGCP'), - ('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."), - } + '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 + # lists.digium.com/pipermail/asterisk-users/2012-January/269787.html + 'asterisk_chan_type': fields.selection( + [ + ('SIP', 'SIP'), + ('Local', 'Local'), + ('IAX2', 'IAX2'), + ('DAHDI', 'DAHDI'), + ('Zap', 'Zap'), + ('Skinny', 'Skinny'), + ('MGCP', 'MGCP'), + ('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." + ), + } _defaults = { 'asterisk_chan_type': 'SIP', @@ -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() diff --git a/asterisk_click2dial/scripts/get_cid_name.py b/asterisk_click2dial/scripts/get_cid_name.py index d850f68..afaf6e5 100755 --- a/asterisk_click2dial/scripts/get_cid_name.py +++ b/asterisk_click2dial/scripts/get_cid_name.py @@ -4,10 +4,11 @@ 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 + Requires the "asterisk_click2dial" module available from https://code.launchpad.net/openerp-asterisk-connector for OpenERP version >= 5.0 @@ -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 " @@ -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,58 +176,70 @@ 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 res = '' phonenum = phonenumbers.parse(number, my_country_code.upper()) city = phonenumbers.area_description_for_number(phonenum, lang.lower()) - #country = phonenumbers.country_name_for_number(phonenum, lang.lower()) + # country = phonenumbers.country_name_for_number(phonenum, lang.lower()) country_code = phonenumbers.region_code_for_number(phonenum) if country_code == my_country_code.upper(): - # We don't display the country name when it's my own country + # We don't display the country name when it's my own country if city: 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: - return number[-9:len(number)] # Take 9 last numbers + return number[-9:len(number)] # Take 9 last numbers 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 + # print 'options = %s' % options + # print 'arguments = %s' % arguments # AGI passes parameters to the script on standard input stdinput = {} @@ -152,8 +251,8 @@ def main(options, arguments): try: variable, value = line.split(':') except: - break - if variable[:4] != 'agi_': # All AGI parameters start with 'agi_' + break + if variable[:4] != 'agi_': # All AGI parameters start with 'agi_' stderr_write("bad stdin variable : %s\n" % variable) continue variable = variable.strip() @@ -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') @@ -209,8 +332,8 @@ def main(options, arguments): stdout_write('VERBOSE "Could not connect to OpenERP"\n') res = False # To simulate a long execution of the XML-RPC request - #import time - #time.sleep(5) + # import time + # time.sleep(5) # Function to limit the size of the CID name to 40 chars if res: @@ -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) diff --git a/asterisk_click2dial/wizard/open_calling_partner.py b/asterisk_click2dial/wizard/open_calling_partner.py index 00be35d..2169fb5 100644 --- a/asterisk_click2dial/wizard/open_calling_partner.py +++ b/asterisk_click2dial/wizard/open_calling_partner.py @@ -33,24 +33,56 @@ 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) - #To test the code without Asterisk server - #calling_number = "0141981242" + 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: res['calling_number'] = calling_number # We match only on the end of the phone 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, @@ -90,26 +138,28 @@ class wizard_open_calling_partner(osv.osv_memory): 'view_mode': 'tree,form', 'res_model': oerp_object._name, 'type': 'ir.actions.act_window', - 'nodestroy': False, # close the pop-up wizard after action + 'nodestroy': False, # close the pop-up wizard after action 'target': 'current', 'domain': [('partner_id', '=', partner[0])], - } + } return action 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, @@ -126,31 +181,40 @@ class wizard_open_calling_partner(osv.osv_memory): 'view_mode': 'form,tree', 'res_model': object_name, 'type': 'ir.actions.act_window', - 'nodestroy': False, # close the pop-up wizard after action + 'nodestroy': False, # close the pop-up wizard after action 'target': 'current', 'res_id': record_to_open[0], - } + } 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', @@ -191,27 +269,38 @@ class wizard_open_calling_partner(osv.osv_memory): 'nodestroy': False, 'target': 'current', 'res_id': cur_wizard.to_update_partner_address_id.id - } + } 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() diff --git a/asterisk_click2dial/wizard/reformat_all_phonenumbers.py b/asterisk_click2dial/wizard/reformat_all_phonenumbers.py index 4412305..8d6c56b 100644 --- a/asterisk_click2dial/wizard/reformat_all_phonenumbers.py +++ b/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 diff --git a/asterisk_click2dial_crm/__init__.py b/asterisk_click2dial_crm/__init__.py index be2809c..3b0499d 100644 --- a/asterisk_click2dial_crm/__init__.py +++ b/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 # @author: Alexis de Lattre # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, diff --git a/asterisk_click2dial_crm/__openerp__.py b/asterisk_click2dial_crm/__openerp__.py index 1034929..35b6169 100644 --- a/asterisk_click2dial_crm/__openerp__.py +++ b/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 # @author: Alexis de Lattre # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, @@ -27,27 +28,30 @@ "version": "0.1", "author": "Zikzakmedia SL, Akretion,Odoo Community Association (OCA)", "website": "http://www.zikzakmedia.com", - "license" : "AGPL-3", + "license": "AGPL-3", "category": "Customer Relationship Management", "description": """ - This module adds CRM-specific features to the asterisk_click2dial module. +This module adds CRM-specific features to the asterisk_click2dial module. - It adds 2 features : +It adds 2 features : - First, when you do a click2dial, OpenERP will propose you to create an - outbound phone call in the CRM ; if you answer 'Yes', it will create the phone - call in the CRM and open it in a new tab. If some users don't want to be asked - to create a phone call in the CRM each time they do a click2dial, you - should disable the corresponding option in the 'Telephony' tab of the 'User' - form. +First, when you do a click2dial, OpenERP will propose you to create an +outbound phone call in the CRM ; if you answer 'Yes', it will create the phone +call in the CRM and open it in a new tab. If some users don't want to be asked +to create a phone call in the CRM each time they do a click2dial, you +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', diff --git a/asterisk_click2dial_crm/asterisk_click2dial_crm.py b/asterisk_click2dial_crm/asterisk_click2dial_crm.py index a4f2755..809a98e 100644 --- a/asterisk_click2dial_crm/asterisk_click2dial_crm.py +++ b/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 # @author: Alexis de Lattre # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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() - diff --git a/asterisk_click2dial_crm/wizard/__init__.py b/asterisk_click2dial_crm/wizard/__init__.py index 9200a68..9f6310c 100644 --- a/asterisk_click2dial_crm/wizard/__init__.py +++ b/asterisk_click2dial_crm/wizard/__init__.py @@ -6,8 +6,8 @@ # @author: Alexis de Lattre # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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, diff --git a/asterisk_click2dial_crm/wizard/create_crm_phonecall.py b/asterisk_click2dial_crm/wizard/create_crm_phonecall.py index a29521b..26fe88e 100644 --- a/asterisk_click2dial_crm/wizard/create_crm_phonecall.py +++ b/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 # @author: Alexis de Lattre # # This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or +# 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 { @@ -56,7 +70,7 @@ class wizard_create_crm_phonecall(osv.osv_memory): 'view_type': 'form', 'view_mode': 'form,tree', 'type': 'ir.actions.act_window', - 'nodestroy': False, # close the pop-up wizard after action + 'nodestroy': False, # close the pop-up wizard after action 'target': 'current', 'context': context, } @@ -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()