You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

906 lines
34 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Asterisk Click2dial module for OpenERP
  5. # Copyright (C) 2010-2012 Alexis de Lattre <alexis@via.ecp.fr>
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. from osv import osv, fields
  22. # Lib required to print logs
  23. import logging
  24. # Lib to translate error messages
  25. from tools.translate import _
  26. from tools import ustr
  27. # Lib for phone number reformating -> pip install phonenumbers
  28. import phonenumbers
  29. # Lib py-asterisk from http://code.google.com/p/py-asterisk/
  30. # We need a version which has this commit :
  31. # 8d0e1c941cce727c702582f3c9fcd49beb4eeaa4
  32. # so a version after Nov 20th, 2012
  33. from Asterisk import Manager
  34. _logger = logging.getLogger(__name__)
  35. class asterisk_server(osv.osv):
  36. """Asterisk server object, to store all the parameters of the Asterisk
  37. IPBXs
  38. """
  39. _name = "asterisk.server"
  40. _description = "Asterisk Servers"
  41. _columns = {
  42. 'name': fields.char(
  43. 'Asterisk server name',
  44. size=50,
  45. required=True,
  46. help="Asterisk server name."
  47. ),
  48. 'active': fields.boolean(
  49. 'Active',
  50. help="The active field allows you to hide the Asterisk server "
  51. "without deleting it."
  52. ),
  53. 'ip_address': fields.char(
  54. 'Asterisk IP addr. or DNS',
  55. size=50,
  56. required=True,
  57. help="IP address or DNS name of the Asterisk server."
  58. ),
  59. 'port': fields.integer(
  60. 'Port',
  61. required=True,
  62. help="TCP port on which the Asterisk Manager Interface listens. "
  63. "Defined in /etc/asterisk/manager.conf on Asterisk."
  64. ),
  65. 'out_prefix': fields.char(
  66. 'Out prefix',
  67. size=4,
  68. help="Prefix to dial to place outgoing calls. "
  69. "If you don't use a prefix to place outgoing calls, "
  70. "leave empty."
  71. ),
  72. 'company_id': fields.many2one(
  73. 'res.company',
  74. 'Company',
  75. help="Company who uses the Asterisk server."
  76. ),
  77. 'national_prefix': fields.char(
  78. 'National prefix',
  79. size=4,
  80. help="Prefix for national phone calls "
  81. "(don't include the 'out prefix'). "
  82. "For e.g., in France, the phone numbers look like "
  83. "'01 41 98 12 42' : the National prefix is '0'."
  84. ),
  85. 'international_prefix': fields.char(
  86. 'International prefix',
  87. required=True,
  88. size=4,
  89. help="Prefix to add to make international phone calls "
  90. "(don't include the 'out prefix'). "
  91. "For e.g., in France, the International prefix is '00'."
  92. ),
  93. 'country_prefix': fields.char(
  94. 'My country prefix',
  95. required=True,
  96. size=4,
  97. help="Phone prefix of the country where the Asterisk server is "
  98. "located. For e.g. the phone prefix for France is '33'. "
  99. "If the phone number to dial starts with the "
  100. "'My country prefix', OpenERP will remove the country prefix "
  101. "from the phone number and add the 'out prefix' followed by "
  102. "the 'national prefix'. If the phone number to dial doesn't "
  103. "start with the 'My country prefix', OpenERP will add the "
  104. "'out prefix' followed by the 'international prefix'."
  105. ),
  106. 'login': fields.char(
  107. 'AMI login',
  108. size=30,
  109. required=True,
  110. help="Login that OpenERP will use to communicate with the "
  111. "Asterisk Manager Interface. "
  112. "Refer to /etc/asterisk/manager.conf on your Asterisk server."
  113. ),
  114. 'password': fields.char(
  115. 'AMI password',
  116. size=30,
  117. required=True,
  118. help="Password that Asterisk will use to communicate with the "
  119. "Asterisk Manager Interface. "
  120. "Refer to /etc/asterisk/manager.conf on your Asterisk server."
  121. ),
  122. 'context': fields.char(
  123. 'Dialplan context',
  124. size=50,
  125. required=True,
  126. help="Asterisk dialplan context from which the calls will be "
  127. "made. Refer to /etc/asterisk/extensions.conf on your "
  128. "Asterisk server."
  129. ),
  130. 'wait_time': fields.integer(
  131. 'Wait time (sec)',
  132. required=True,
  133. help="Amount of time (in seconds) Asterisk will try to reach the "
  134. "user's phone before hanging up."
  135. ),
  136. 'extension_priority': fields.integer(
  137. 'Extension priority',
  138. required=True,
  139. help="Priority of the extension in the Asterisk dialplan. "
  140. "Refer to /etc/asterisk/extensions.conf on your Asterisk "
  141. "server."
  142. ),
  143. 'alert_info': fields.char(
  144. 'Alert-Info SIP header',
  145. size=255,
  146. help="Set Alert-Info header in SIP request to user's IP Phone for "
  147. "the click2dial feature. If empty, the Alert-Info header "
  148. "will not be added. You can use it to have a special ring "
  149. "tone for click2dial (a silent one !) or to activate "
  150. "auto-answer for example."
  151. ),
  152. }
  153. _defaults = {
  154. 'active': True,
  155. 'port': 5038, # Default AMI port
  156. 'out_prefix': '0',
  157. 'national_prefix': '0',
  158. 'international_prefix': '00',
  159. 'extension_priority': 1,
  160. 'wait_time': 15,
  161. }
  162. def _check_validity(self, cr, uid, ids):
  163. for server in self.browse(cr, uid, ids):
  164. country_prefix = ('Country prefix', server.country_prefix)
  165. international_prefix = (
  166. 'International prefix',
  167. server.international_prefix
  168. )
  169. out_prefix = ('Out prefix', server.out_prefix)
  170. national_prefix = ('National prefix', server.national_prefix)
  171. dialplan_context = ('Dialplan context', server.context)
  172. alert_info = ('Alert-Info SIP header', server.alert_info)
  173. login = ('AMI login', server.login)
  174. password = ('AMI password', server.password)
  175. for digit_prefix in [
  176. country_prefix,
  177. international_prefix,
  178. out_prefix,
  179. national_prefix
  180. ]:
  181. if digit_prefix[1] and not digit_prefix[1].isdigit():
  182. raise osv.except_osv(
  183. _('Error :'),
  184. _("Only use digits for the '%s' on the Asterisk "
  185. "server '%s'") % (digit_prefix[0], server.name)
  186. )
  187. if server.wait_time < 1 or server.wait_time > 120:
  188. raise osv.except_osv(
  189. _('Error :'),
  190. _("You should set a 'Wait time' value between 1 and 120 "
  191. "seconds for the Asterisk server '%s'") % server.name
  192. )
  193. if server.extension_priority < 1:
  194. raise osv.except_osv(
  195. _('Error :'),
  196. _("The 'extension priority' must be a positive value for "
  197. "the Asterisk server '%s'") % server.name
  198. )
  199. if server.port > 65535 or server.port < 1:
  200. raise osv.except_osv(
  201. _('Error :'),
  202. _("You should set a TCP port between 1 and 65535 for the "
  203. "Asterik server '%s'") % server.name
  204. )
  205. for check_string in [
  206. dialplan_context,
  207. alert_info,
  208. login,
  209. password]:
  210. if check_string[1]:
  211. try:
  212. check_string[1].encode('ascii')
  213. except UnicodeEncodeError:
  214. raise osv.except_osv(
  215. _('Error :'),
  216. _("The '%s' should only have ASCII caracters for "
  217. "the Asterisk server '%s'")
  218. % (check_string[0], server.name)
  219. )
  220. return True
  221. _constraints = [
  222. (_check_validity, "Error message in raise", [
  223. 'out_prefix',
  224. 'country_prefix',
  225. 'national_prefix',
  226. 'international_prefix',
  227. 'wait_time',
  228. 'extension_priority',
  229. 'port',
  230. 'context',
  231. 'alert_info',
  232. 'login',
  233. 'password',
  234. ]),
  235. ]
  236. def _reformat_number(self, cr, uid, erp_number, ast_server, context=None):
  237. '''
  238. This function is dedicated to the transformation of the number
  239. available in OpenERP to the number that Asterisk should dial.
  240. You may have to inherit this function in another module specific
  241. for your company if you are not happy with the way I reformat
  242. the OpenERP numbers.
  243. '''
  244. error_title_msg = _("Invalid phone number")
  245. invalid_format_msg = _("The phone number is not written in valid "
  246. "format.")
  247. # Let's call the variable tmp_number now
  248. tmp_number = erp_number
  249. _logger.debug('Number before reformat = %s', tmp_number)
  250. # Check if empty
  251. if not tmp_number:
  252. raise osv.except_osv(error_title_msg, invalid_format_msg)
  253. # Before starting to use prefix, we convert empty prefix whose value
  254. # is False to an empty string
  255. country_prefix = ast_server.country_prefix or ''
  256. national_prefix = ast_server.national_prefix or ''
  257. international_prefix = ast_server.international_prefix or ''
  258. out_prefix = ast_server.out_prefix or ''
  259. # Maybe one day we will use
  260. # phonenumbers.format_out_of_country_calling_number(
  261. # phonenumbers.parse('<phone_number_e164', None), 'FR'
  262. # )
  263. # The country code seems to be OK with the ones of OpenERP
  264. # But it returns sometimes numbers with '-'...
  265. # we have to investigate this first
  266. # International format
  267. if tmp_number[0] != '+':
  268. raise # This should never happen
  269. # Remove the starting '+' of the number
  270. tmp_number = tmp_number.replace('+', '')
  271. _logger.debug('Number after removal of special char = %s', tmp_number)
  272. # At this stage, 'tmp_number' should only contain digits
  273. if not tmp_number.isdigit():
  274. raise osv.except_osv(error_title_msg, invalid_format_msg)
  275. _logger.debug('Country prefix = %s', country_prefix)
  276. if country_prefix == tmp_number[0:len(country_prefix)]:
  277. # If the number is a national number,
  278. # remove 'my country prefix' and add 'national prefix'
  279. tmp_number = (national_prefix
  280. + tmp_number[len(country_prefix):len(tmp_number)])
  281. _logger.debug(
  282. 'National prefix = %s - Number with national prefix = %s',
  283. national_prefix, tmp_number,
  284. )
  285. else:
  286. # If the number is an international number,
  287. # add 'international prefix'
  288. tmp_number = international_prefix + tmp_number
  289. _logger.debug(
  290. 'International prefix = %s - Number with international prefix '
  291. '= %s', international_prefix, tmp_number,
  292. )
  293. # Add 'out prefix' to all numbers
  294. tmp_number = out_prefix + tmp_number
  295. _logger.debug(
  296. 'Out prefix = %s - Number to be sent to Asterisk = %s',
  297. out_prefix, tmp_number,
  298. )
  299. return tmp_number
  300. def _convert_number_to_international_format(self, cr, uid, number,
  301. ast_server, context=None):
  302. """Convert the number presented by the phone network to a number
  303. in international format e.g. +33141981242
  304. """
  305. if number and number.isdigit() and len(number) > 5:
  306. if (ast_server.international_prefix
  307. and number[0:len(ast_server.international_prefix)]
  308. == ast_server.international_prefix):
  309. number = number[len(ast_server.international_prefix):]
  310. number = '+' + number
  311. elif (ast_server.national_prefix
  312. and number[0:len(ast_server.national_prefix)]
  313. == ast_server.national_prefix):
  314. number = number[len(ast_server.national_prefix):]
  315. number = '+' + ast_server.country_prefix + number
  316. return number
  317. def _get_asterisk_server_from_user(self, cr, uid, user, context=None):
  318. '''Returns an asterisk.server browse object'''
  319. # We check if the user has an Asterisk server configured
  320. if user.asterisk_server_id.id:
  321. ast_server = user.asterisk_server_id
  322. else:
  323. asterisk_server_ids = self.search(
  324. cr,
  325. uid,
  326. [('company_id', '=', user.company_id.id)],
  327. context=context
  328. )
  329. # If no asterisk server is configured on the user,
  330. # we take the first one
  331. if not asterisk_server_ids:
  332. raise osv.except_osv(
  333. _('Error :'),
  334. _("No Asterisk server configured for the company '%s'.")
  335. % user.company_id.name
  336. )
  337. else:
  338. ast_server = self.browse(
  339. cr, uid, asterisk_server_ids[0], context=context
  340. )
  341. return ast_server
  342. def _connect_to_asterisk(self, cr, uid, context=None):
  343. '''
  344. Open the connection to the asterisk manager
  345. Returns an instance of the Asterisk Manager
  346. '''
  347. user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
  348. # Note : if I write 'Error' without ' :', it won't get translated...
  349. # I don't understand why !
  350. ast_server = self._get_asterisk_server_from_user(
  351. cr, uid, user, context=context
  352. )
  353. # We check if the current user has a chan type
  354. if not user.asterisk_chan_type:
  355. raise osv.except_osv(
  356. _('Error :'),
  357. _('No channel type configured for the current user.')
  358. )
  359. # We check if the current user has an internal number
  360. if not user.resource:
  361. raise osv.except_osv(
  362. _('Error :'),
  363. _('No resource name configured for the current user')
  364. )
  365. _logger.debug(
  366. "User's phone : %s/%s", user.asterisk_chan_type, user.resource,
  367. )
  368. _logger.debug(
  369. "Asterisk server = %s:%d", ast_server.ip_address, ast_server.port,
  370. )
  371. # Connect to the Asterisk Manager Interface
  372. try:
  373. ast_manager = Manager.Manager(
  374. (ast_server.ip_address, ast_server.port),
  375. ast_server.login,
  376. ast_server.password
  377. )
  378. except Exception, e:
  379. _logger.error(
  380. "Error in the Originate request to Asterisk server %s",
  381. ast_server.ip_address,
  382. )
  383. _logger.error(
  384. "Here is the detail of the error : '%s'", ustr(e)
  385. )
  386. raise osv.except_osv(
  387. _('Error:'),
  388. _("Problem in the request from OpenERP to Asterisk. Here is "
  389. "the detail of the error: '%s'") % ustr(e)
  390. )
  391. return (user, ast_server, ast_manager)
  392. def _dial_with_asterisk(self, cr, uid, erp_number, context=None):
  393. print "_dial_with_asterisk erp_number=", erp_number
  394. if not erp_number:
  395. raise osv.except_osv(
  396. _('Error :'),
  397. "Hara kiri : you must call the function with erp_number"
  398. )
  399. user, ast_server, ast_manager = self._connect_to_asterisk(
  400. cr, uid, context=context
  401. )
  402. ast_number = self._reformat_number(
  403. cr, uid, erp_number, ast_server, context=context
  404. )
  405. # The user should have a CallerID
  406. if not user.callerid:
  407. raise osv.except_osv(
  408. _('Error :'),
  409. _('No callerID configured for the current user')
  410. )
  411. variable = []
  412. if user.asterisk_chan_type == 'SIP':
  413. # We can only have one alert-info header in a SIP request
  414. if user.alert_info:
  415. variable.append('SIPAddHeader=Alert-Info: ' + user.alert_info)
  416. elif ast_server.alert_info:
  417. variable.append(
  418. 'SIPAddHeader=Alert-Info: ' + ast_server.alert_info
  419. )
  420. if user.variable:
  421. for user_variable in user.variable.split('|'):
  422. variable.append(user_variable.strip())
  423. try:
  424. ast_manager.Originate(
  425. user.asterisk_chan_type + '/' + user.resource +
  426. (('/' + user.dial_suffix) if user.dial_suffix else ''),
  427. context=ast_server.context,
  428. extension=ast_number,
  429. priority=str(ast_server.extension_priority),
  430. timeout=str(ast_server.wait_time * 1000),
  431. caller_id=user.callerid,
  432. variable=variable)
  433. except Exception as e:
  434. _logger.error(
  435. "Error in the Originate request to Asterisk server %s",
  436. ast_server.ip_address,
  437. )
  438. _logger.error(
  439. "Here is the detail of the error : '%s'" , ustr(e)
  440. )
  441. raise osv.except_osv(
  442. _('Error :'),
  443. _("Click to dial with Asterisk failed.\nHere is the error: "
  444. "'%s'") % ustr(e)
  445. )
  446. finally:
  447. ast_manager.Logoff()
  448. return True
  449. def _get_calling_number(self, cr, uid, context=None):
  450. user, ast_server, ast_manager = self._connect_to_asterisk(
  451. cr, uid, context=context
  452. )
  453. calling_party_number = False
  454. try:
  455. list_chan = ast_manager.Status()
  456. # from pprint import pprint
  457. # pprint(list_chan)
  458. _logger.debug("Result of Status AMI request: %s", list_chan)
  459. for chan in list_chan.values():
  460. sip_account = user.asterisk_chan_type + '/' + user.resource
  461. if (chan.get('ChannelState') == '4' # 4 = Ring
  462. and chan.get('ConnectedLineNum')
  463. == user.internal_number):
  464. _logger.debug("Found a matching Event in 'Ring' state")
  465. calling_party_number = chan.get('CallerIDNum')
  466. break
  467. if (chan.get('ChannelState') == '6' # 6 = Up
  468. and sip_account in chan.get('BridgedChannel')):
  469. _logger.debug("Found a matching Event in 'Up' state")
  470. calling_party_number = chan.get('CallerIDNum')
  471. break
  472. except Exception, e:
  473. _logger.error(
  474. "Error in the Status request to Asterisk server %s",
  475. ast_server.ip_address,
  476. )
  477. _logger.error(
  478. "Here is the detail of the error : '%s'", ustr(e)
  479. )
  480. raise osv.except_osv(
  481. _('Error :'),
  482. _("Can't get calling number from Asterisk.\n"
  483. "Here is the error: '%s'") % ustr(e)
  484. )
  485. finally:
  486. ast_manager.Logoff()
  487. _logger.debug(
  488. "The calling party number is '%s'" % calling_party_number
  489. )
  490. return calling_party_number
  491. asterisk_server()
  492. # Parameters specific for each user
  493. class res_users(osv.osv):
  494. _inherit = "res.users"
  495. _columns = {
  496. 'internal_number': fields.char(
  497. 'Internal number',
  498. size=15,
  499. help="User's internal phone number."
  500. ),
  501. 'dial_suffix': fields.char(
  502. 'User-specific dial suffix',
  503. size=15,
  504. help="User-specific dial suffix such as aa=2wb for SCCP auto "
  505. "answer."
  506. ),
  507. 'callerid': fields.char(
  508. 'Caller ID',
  509. size=50,
  510. help="Caller ID used for the calls initiated by this user."
  511. ),
  512. # You'd probably think: Asterisk should reuse the callerID of sip.conf!
  513. # But it cannot, cf
  514. # lists.digium.com/pipermail/asterisk-users/2012-January/269787.html
  515. 'asterisk_chan_type': fields.selection(
  516. [
  517. ('SIP', 'SIP'),
  518. ('Local', 'Local'),
  519. ('IAX2', 'IAX2'),
  520. ('DAHDI', 'DAHDI'),
  521. ('Zap', 'Zap'),
  522. ('Skinny', 'Skinny'),
  523. ('MGCP', 'MGCP'),
  524. ('mISDN', 'mISDN'),
  525. ('H323', 'H323'),
  526. ('SCCP', 'SCCP'),
  527. ],
  528. 'Asterisk channel type',
  529. help="Asterisk channel type, as used in the Asterisk dialplan. If "
  530. "the user has a regular IP phone, the channel type is 'SIP'."
  531. ),
  532. 'resource': fields.char(
  533. 'Resource name',
  534. size=64,
  535. help="Resource name for the channel type selected. For example, "
  536. "if you use 'Dial(SIP/phone1)' in your Asterisk dialplan to "
  537. "ring the SIP phone of this user, then the resource name for "
  538. "this user is 'phone1'. For a SIP phone, the phone number "
  539. "is often used as resource name, but not always."
  540. ),
  541. 'alert_info': fields.char(
  542. 'User-specific Alert-Info SIP header',
  543. size=255,
  544. help="Set a user-specific Alert-Info header in SIP request to "
  545. "user's IP Phone for the click2dial feature. If empty, the "
  546. "Alert-Info header will not be added. You can use it to have "
  547. "a special ring tone for click2dial (a silent one !) or to "
  548. "activate auto-answer for example."
  549. ),
  550. 'variable': fields.char(
  551. 'User-specific Variable',
  552. size=255,
  553. help="Set a user-specific 'Variable' field in the Asterisk "
  554. "Manager Interface 'originate' request for the click2dial "
  555. "feature. If you want to have several variable headers, "
  556. "separate them with '|'."
  557. ),
  558. 'asterisk_server_id': fields.many2one(
  559. 'asterisk.server',
  560. 'Asterisk server',
  561. help="Asterisk server on which the user's phone is connected. If "
  562. "you leave this field empty, it will use the first Asterisk "
  563. "server of the user's company."
  564. ),
  565. }
  566. _defaults = {
  567. 'asterisk_chan_type': 'SIP',
  568. }
  569. def _check_validity(self, cr, uid, ids):
  570. for user in self.browse(cr, uid, ids):
  571. for check_string in [
  572. ('Resource name', user.resource),
  573. ('Internal number', user.internal_number),
  574. ('Caller ID', user.callerid)
  575. ]:
  576. if check_string[1]:
  577. try:
  578. check_string[1].encode('ascii')
  579. except UnicodeEncodeError:
  580. raise osv.except_osv(
  581. _('Error :'),
  582. _("The '%s' for the user '%s' should only have "
  583. "ASCII caracters") % (check_string[0], user.name)
  584. )
  585. return True
  586. _constraints = [
  587. (_check_validity, "Error message in raise", [
  588. 'resource',
  589. 'internal_number',
  590. 'callerid'
  591. ]),
  592. ]
  593. res_users()
  594. class res_partner_address(osv.osv):
  595. _inherit = "res.partner.address"
  596. def _format_phonenumber_to_e164(self, cr, uid, ids, name, arg,
  597. context=None):
  598. result = {}
  599. for addr in self.read(cr, uid, ids, ['phone', 'mobile', 'fax'],
  600. context=context):
  601. result[addr['id']] = {}
  602. for fromfield, tofield in [
  603. ('phone', 'phone_e164'),
  604. ('mobile', 'mobile_e164'),
  605. ('fax', 'fax_e164')
  606. ]:
  607. if not addr.get(fromfield):
  608. res = False
  609. else:
  610. try:
  611. res = phonenumbers.format_number(
  612. phonenumbers.parse(addr.get(fromfield), None),
  613. phonenumbers.PhoneNumberFormat.E164
  614. )
  615. except Exception, e:
  616. _logger.error(
  617. "Cannot reformat the phone number '%s' to E.164 "
  618. "format. Error message: %s",
  619. addr.get(fromfield), e,
  620. )
  621. _logger.error(
  622. "You should fix this number and run the wizard "
  623. "'Reformat all phone numbers' from the menu "
  624. "Settings > Configuration > Asterisk"
  625. )
  626. # If I raise an exception here, it won't be possible to
  627. # install the module on a DB with bad phone numbers
  628. # raise osv.except_osv(
  629. # _('Error :'),
  630. # _("Cannot reformat the phone number '%s' to "
  631. # "E.164 format. Error message: %s"
  632. # % (addr.get(fromfield), e)))
  633. res = False
  634. result[addr['id']][tofield] = res
  635. return result
  636. _columns = {
  637. 'phone_e164': fields.function(
  638. _format_phonenumber_to_e164,
  639. type='char',
  640. size=64,
  641. string='Phone in E.164 format',
  642. readonly=True,
  643. multi="e164",
  644. store={
  645. 'res.partner.address': (lambda self, cr, uid, ids, c={}:
  646. ids, ['phone'], 10),
  647. }
  648. ),
  649. 'mobile_e164': fields.function(
  650. _format_phonenumber_to_e164,
  651. type='char',
  652. size=64,
  653. string='Mobile in E.164 format',
  654. readonly=True,
  655. multi="e164",
  656. store={
  657. 'res.partner.address': (lambda self, cr, uid, ids, c={}:
  658. ids, ['mobile'], 10),
  659. }
  660. ),
  661. 'fax_e164': fields.function(
  662. _format_phonenumber_to_e164,
  663. type='char',
  664. size=64,
  665. string='Fax in E.164 format',
  666. readonly=True,
  667. multi="e164",
  668. store={
  669. 'res.partner.address': (lambda self, cr, uid, ids, c={}:
  670. ids, ['fax'], 10),
  671. }
  672. ),
  673. }
  674. def _reformat_phonenumbers(self, cr, uid, vals, context=None):
  675. """Reformat phone numbers in international format i.e. +33141981242"""
  676. phonefields = ['phone', 'fax', 'mobile']
  677. if any([vals.get(field) for field in phonefields]):
  678. user = self.pool.get('res.users').browse(
  679. cr, uid, uid, context=context
  680. )
  681. # country_id on res.company is a fields.function that looks at
  682. # company_id.partner_id.addres(default).country_id
  683. if user.company_id.country_id:
  684. user_countrycode = user.company_id.country_id.code
  685. else:
  686. # We need to raise an exception here because, if we pass None
  687. # as second arg of phonenumbers.parse(), it will raise an
  688. # exception when you try to enter a phone number in national
  689. # format... so it's better to raise the exception here
  690. raise osv.except_osv(
  691. _('Error :'),
  692. _("You should set a country on the company '%s'")
  693. % user.company_id.name
  694. )
  695. for field in phonefields:
  696. if vals.get(field):
  697. try:
  698. res_parse = phonenumbers.parse(
  699. vals.get(field),
  700. user_countrycode
  701. )
  702. except Exception, e:
  703. raise osv.except_osv(
  704. _('Error :'),
  705. _("Cannot reformat the phone number '%s' to "
  706. "international format. Error message: %s")
  707. (vals.get(field), e)
  708. )
  709. vals[field] = phonenumbers.format_number(
  710. res_parse,
  711. phonenumbers.PhoneNumberFormat.INTERNATIONAL
  712. )
  713. return vals
  714. def create(self, cr, uid, vals, context=None):
  715. vals_reformated = self._reformat_phonenumbers(
  716. cr, uid, vals, context=context
  717. )
  718. return super(res_partner_address, self).create(
  719. cr, uid, vals_reformated, context=context
  720. )
  721. def write(self, cr, uid, ids, vals, context=None):
  722. vals_reformated = self._reformat_phonenumbers(
  723. cr, uid, vals, context=context
  724. )
  725. return super(res_partner_address, self).write(
  726. cr, uid, ids, vals_reformated, context=context
  727. )
  728. def dial(self, cr, uid, ids, phone_field=None, context=None):
  729. """Read the number to dial and call _connect_to_asterisk the
  730. right way
  731. """
  732. if phone_field is None:
  733. phone_field = ['phone', 'phone_e164']
  734. erp_number_read = self.read(
  735. cr, uid, ids[0], phone_field, context=context
  736. )
  737. erp_number_e164 = erp_number_read[phone_field[1]]
  738. erp_number_display = erp_number_read[phone_field[0]]
  739. # Check if the number to dial is not empty
  740. if not erp_number_display:
  741. raise osv.except_osv(_('Error :'), _('There is no phone number !'))
  742. elif erp_number_display and not erp_number_e164:
  743. raise osv.except_osv(
  744. _('Error :'),
  745. _("The phone number isn't stored in the standard E.164 "
  746. "format. Try to run the wizard 'Reformat all phone numbers' "
  747. "from the menu Settings > Configuration > Asterisk.")
  748. )
  749. return self.pool.get('asterisk.server')._dial_with_asterisk(
  750. cr, uid, erp_number_e164, context=context
  751. )
  752. def action_dial_phone(self, cr, uid, ids, context=None):
  753. """Function called by the button 'Dial' next to the 'phone' field
  754. in the partner address view
  755. """
  756. return self.dial(
  757. cr, uid, ids, phone_field=['phone', 'phone_e164'], context=context
  758. )
  759. def action_dial_mobile(self, cr, uid, ids, context=None):
  760. """Function called by the button 'Dial' next to the 'mobile' field
  761. in the partner address view
  762. """
  763. return self.dial(
  764. cr, uid, ids, phone_field=['mobile', 'mobile_e164'],
  765. context=context
  766. )
  767. def get_name_from_phone_number(self, cr, uid, number, context=None):
  768. """Function to get name from phone number. Useful for use from Asterisk
  769. to add CallerID name to incoming calls.
  770. The "scripts/" subdirectory of this module has an AGI script that you
  771. can install on your Asterisk IPBX : the script will be called from the
  772. Asterisk dialplan via the AGI() function and it will use this function
  773. via an XML-RPC request.
  774. """
  775. res = self.get_partner_from_phone_number(
  776. cr, uid, number, context=context
  777. )
  778. if res:
  779. return res[2]
  780. else:
  781. return False
  782. def get_partner_from_phone_number(self, cr, uid, number, context=None):
  783. # We check that "number" is really a number
  784. _logger.debug(
  785. u"Call get_name_from_phone_number with number = %s", number,
  786. )
  787. if not isinstance(number, (str, unicode)):
  788. _logger.warning(
  789. u"Number should be a 'str' or 'unicode' but it is a '%s'",
  790. type(number)
  791. )
  792. return False
  793. if not number.isdigit():
  794. _logger.warning(u"Number should only contain digits.")
  795. return False
  796. # We try to match a phone or mobile number with the same end
  797. pg_seach_number = str('%' + number)
  798. res_ids = self.search(
  799. cr, uid, [
  800. '|',
  801. ('phone_e164', 'ilike', pg_seach_number),
  802. ('mobile_e164', 'ilike', pg_seach_number)
  803. ], context=context
  804. )
  805. # TODO : use is_number_match() of the phonenumber lib ?
  806. if len(res_ids) > 1:
  807. _logger.warning(
  808. u"There are several partners addresses (IDS = %s) with the "
  809. "same phone number %s", ustr(res_ids), number,
  810. )
  811. if res_ids:
  812. entry = self.read(
  813. cr, uid, res_ids[0], ['name', 'partner_id'], context=context
  814. )
  815. _logger.debug(
  816. u"Answer get_partner_from_phone_number with name = %s",
  817. entry['name']
  818. )
  819. return (
  820. entry['id'],
  821. entry['partner_id'] and entry['partner_id'][0] or False,
  822. entry['name']
  823. )
  824. else:
  825. _logger.debug(u"No match for phone number %s", number)
  826. return False
  827. res_partner_address()
  828. # This module supports multi-company
  829. class res_company(osv.osv):
  830. _inherit = "res.company"
  831. _columns = {
  832. 'asterisk_server_ids': fields.one2many(
  833. 'asterisk.server',
  834. 'company_id',
  835. 'Asterisk servers',
  836. help="List of Asterisk servers."
  837. )
  838. }
  839. res_company()