From b1da5449bea01a6f2cbf651c7d5a0b42bb49da2f Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 7 May 2012 02:46:41 +0200 Subject: [PATCH] New feature : add wizard that opens the partner form of the calling party --- asterisk_click2dial/asterisk_click2dial.py | 170 ++++++++++++++++----- asterisk_click2dial/res_partner_view.xml | 24 +++ 2 files changed, 154 insertions(+), 40 deletions(-) diff --git a/asterisk_click2dial/asterisk_click2dial.py b/asterisk_click2dial/asterisk_click2dial.py index ba5cf6f..0cc922b 100644 --- a/asterisk_click2dial/asterisk_click2dial.py +++ b/asterisk_click2dial/asterisk_click2dial.py @@ -98,7 +98,7 @@ class asterisk_server(osv.osv): ] - def reformat_number(self, cr, uid, ids, erp_number, ast_server, context=None): + def _reformat_number(self, cr, uid, erp_number, ast_server, context=None): ''' This function is dedicated to the transformation of the number available in OpenERP to the number that Asterisk should dial. @@ -170,18 +170,19 @@ class asterisk_server(osv.osv): return tmp_number - def _parse_asterisk_answer(self, cr, uid, sock, context=None): + def _parse_asterisk_answer(self, cr, uid, sock, end_string='\r\n\r\n', context=None): '''Parse the answer of the Asterisk Manager Interface''' answer = '' data = '' - while '\r\n\r\n' not in data: + while end_string not in data: data = sock.recv(1024) if data: answer += data return answer - - def dial(self, cr, uid, ids, erp_number, context=None): + + + def _connect_to_asterisk(self, cr, uid, method='dial', options=None, context=None): ''' Open the socket to the Asterisk Manager Interface (AMI) and send instructions to Dial to Asterisk. That's the important function ! @@ -189,9 +190,6 @@ class asterisk_server(osv.osv): ''' user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - # Check if the number to dial is not empty - if not erp_number: - raise osv.except_osv(_('Error :'), _('There is no phone number !')) # Note : if I write 'Error' without ' :', it won't get translated... # I don't understand why ! @@ -214,13 +212,8 @@ class asterisk_server(osv.osv): if not user.internal_number: raise osv.except_osv(_('Error :'), _('No internal phone number configured for the current user')) - # The user should also have a CallerID - if not user.callerid: - raise osv.except_osv(_('Error :'), _('No callerID configured for the current user')) - # Convert the phone number in the format that will be sent to Asterisk - ast_number = self.reformat_number(cr, uid, ids, erp_number, ast_server, context=context) - _logger.debug("User dialing : channel = %s/%s - Callerid = %s" % (user.asterisk_chan_type, user.internal_number, user.callerid)) + _logger.debug("User's phone : %s/%s" % (user.asterisk_chan_type, user.internal_number)) _logger.debug("Asterisk server = %s:%d" % (ast_server.ip_address, ast_server.port)) # Connect to the Asterisk Manager Interface, using IPv6-ready code @@ -249,23 +242,60 @@ class asterisk_server(osv.osv): else: raise osv.except_osv(_('Error :'), _("Authentification to Asterisk failed :\n%s" % login_answer)) - # Dial with Asterisk - originate_act = 'Action: originate\r\n' + \ - 'Channel: ' + user.asterisk_chan_type + '/' + user.internal_number + '\r\n' + \ - 'Priority: ' + str(ast_server.extension_priority) + '\r\n' + \ - 'Timeout: ' + str(ast_server.wait_time*1000) + '\r\n' + \ - 'CallerId: ' + user.callerid + '\r\n' + \ - 'Exten: ' + ast_number + '\r\n' + \ - 'Context: ' + ast_server.context + '\r\n' - if ast_server.alert_info and user.asterisk_chan_type == 'SIP': - originate_act += 'Variable: SIPAddHeader=Alert-Info: ' + ast_server.alert_info + '\r\n' - originate_act += '\r\n' - sock.send(originate_act.encode('ascii')) - originate_answer = self._parse_asterisk_answer(cr, uid, sock, context=context) - if 'Response: Success' in originate_answer: - _logger.debug('Successfull originate command : %s' % originate_answer) - else: - raise osv.except_osv(_('Error :'), _("Click to dial with Asterisk failed :\n%s" % originate_answer)) + if method == 'dial': + # Convert the phone number in the format that will be sent to Asterisk + erp_number = options.get('erp_number') + if not erp_number: + raise osv.except_osv(_('Error :'), "Hara kiri : you must call the function with erp_number in the options") + 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')) + + # Dial with Asterisk + originate_act = 'Action: originate\r\n' + \ + 'Channel: ' + user.asterisk_chan_type + '/' + user.internal_number + '\r\n' + \ + 'Priority: ' + str(ast_server.extension_priority) + '\r\n' + \ + 'Timeout: ' + str(ast_server.wait_time*1000) + '\r\n' + \ + 'CallerId: ' + user.callerid + '\r\n' + \ + 'Exten: ' + ast_number + '\r\n' + \ + 'Context: ' + ast_server.context + '\r\n' + if ast_server.alert_info and user.asterisk_chan_type == 'SIP': + originate_act += 'Variable: SIPAddHeader=Alert-Info: ' + ast_server.alert_info + '\r\n' + originate_act += '\r\n' + sock.send(originate_act.encode('ascii')) + originate_answer = self._parse_asterisk_answer(cr, uid, sock, context=context) + if 'Response: Success' in originate_answer: + _logger.debug('Successfull originate command : %s' % originate_answer) + else: + raise osv.except_osv(_('Error :'), _("Click to dial with Asterisk failed :\n%s" % originate_answer)) + + elif method == "get_calling_number": + status_act = 'Action: Status\r\n\r\n' # TODO : add ActionID + sock.send(status_act.encode('ascii')) + status_answer = self._parse_asterisk_answer(cr, uid, sock, end_string='Event: StatusComplete', context=context) + + if 'Response: Success' in status_answer: + _logger.debug('Successfull Status command : %s' % status_answer) + else: + raise osv.except_osv(_('Error :'), _("Status command to Asterisk failed :\n%s" % status_answer)) + + # Parse answer + calling_party_number = False + status_answer_split = status_answer.split('\r\n\r\n') + for event in status_answer_split: + string_match = 'Channel: ' + user.asterisk_chan_type + '/' + user.internal_number + if not string_match in event: + continue + event_split = event.split('\r\n') + for event_line in event_split: + if not 'CallerIDNum' in event_line: + continue + line_detail = event_line.split(': ') + if len(line_detail) <> 2: + raise osv.except_osv('Error :', "Hara kiri... this is not possible") + calling_party_number = line_detail[1] # Logout of Asterisk sock.send(('Action: Logoff\r\n\r\n').encode('ascii')) @@ -276,13 +306,19 @@ class asterisk_server(osv.osv): _logger.warning('Logout from Asterisk failed : %s' % logout_answer) # we catch only network problems here except socket.error: - _logger.warning("Click2dial failed : unable to connect to Asterisk") + _logger.warning("Unable to connect to the Asterisk server '%s' IP '%s:%d'" % (ast_server.name, ast_server.ip_address, ast_server.port)) raise osv.except_osv(_('Error :'), _("The connection from OpenERP to the Asterisk server failed. Please check the configuration on OpenERP and on Asterisk.")) finally: sock.close() + if method == 'dial': _logger.info("Asterisk Click2Dial from %s/%s to %s" % (user.asterisk_chan_type, user.internal_number, ast_number)) + return True - return True + elif method == "get_calling_number": + return calling_party_number + + else: + return False asterisk_server() @@ -333,20 +369,30 @@ res_users() class res_partner_address(osv.osv): - _name = "res.partner.address" _inherit = "res.partner.address" + + def dial(self, cr, uid, ids, phone_field='phone', context=None): + '''Read the number to dial and call _connect_to_asterisk the right way''' + erp_number = self.read(cr, uid, ids, [phone_field], context=context)[0][phone_field] + # Check if the number to dial is not empty + if not erp_number: + raise osv.except_osv(_('Error :'), _('There is no phone number !')) + options = {'erp_number': erp_number} + return self.pool.get('asterisk.server')._connect_to_asterisk(cr, uid, method='dial', options=options, 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''' - erp_number = self.read(cr, uid, ids, ['phone'], context=context)[0]['phone'] - return self.pool.get('asterisk.server').dial(cr, uid, ids, erp_number, context=context) + return self.dial(cr, uid, ids, phone_field='phone', 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''' - erp_number = self.read(cr, uid, ids, ['mobile'], context=context)[0]['mobile'] - return self.pool.get('asterisk.server').dial(cr, uid, ids, erp_number, context=context) + return self.dial(cr, uid, ids, phone_field='mobile', 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 @@ -356,6 +402,13 @@ class res_partner_address(osv.osv): 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[1] + else: + return False + + def get_partner_from_phone_number(self, cr, uid, number, context=None): res = {} # We check that "number" is really a number if not isinstance(number, str): @@ -372,11 +425,11 @@ class res_partner_address(osv.osv): # We use a regexp on the phone field to remove non-digit caracters if re.sub(r'\D', '', entry.phone).endswith(number): _logger.debug(u"Answer get_name_from_phone_number with name = %s" % entry.name) - return entry.name + return (entry.partner_id.id, entry.name) if entry.mobile: if re.sub(r'\D', '', entry.mobile).endswith(number): _logger.debug(u"Answer get_name_from_phone_number with name = %s" % entry.name) - return entry.name + return (entry.partner_id.id, entry.name) _logger.debug(u"No match for phone number %s" % number) return False @@ -384,6 +437,43 @@ class res_partner_address(osv.osv): res_partner_address() +class wizard_open_calling_partner(osv.osv_memory): + _name = "wizard.open.calling.partner" + + + def open_calling_partner(self, cr, uid, ids, context=None): + _logger.debug(u"Start wizard 'open calling partner'") + calling_number = self.pool.get('asterisk.server')._connect_to_asterisk(cr, uid, method='get_calling_number', context=context) + if calling_number: + # We match only on the end of the phone number + if len(calling_number) >= 9: + 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) + if partner: + _logger.debug("Found a partner corresponding to the calling party : '%s'" % partner[1]) + action = { + 'name': 'Calling partner', + 'view_type': 'form', + 'view_mode': 'form,tree', + 'res_model': 'res.partner', + 'type': 'ir.actions.act_window', + 'nodestroy': False, # close the pop-up wizard after action + 'target': 'current', + 'res_id': [partner[0]], + } + return action + else: + _logger.debug("Could not find a partner corresponding to the calling number '%s'" % calling_number) # TODO : display an error message + raise osv.except_osv(_('Error :'), _("Could not find a partner corresponding to the calling number '%s'" % calling_number)) + else: + _logger.debug("Could not retrieve the calling number from Asterisk") # TODO : display an error message + raise osv.except_osv(_('Error :'), _("Could not retrieve the calling number from Asterisk")) + +wizard_open_calling_partner() + + # This module supports multi-company class res_company(osv.osv): _name = "res.company" diff --git a/asterisk_click2dial/res_partner_view.xml b/asterisk_click2dial/res_partner_view.xml index 41c5f32..7a7f313 100644 --- a/asterisk_click2dial/res_partner_view.xml +++ b/asterisk_click2dial/res_partner_view.xml @@ -89,7 +89,31 @@ + + + view_open_calling_partner + wizard.open.calling.partner + form + +
+