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.

280 lines
12 KiB

10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Base Phone module for Odoo/OpenERP
  5. # Copyright (C) 2010-2014 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 openerp.osv import orm, fields
  22. from openerp.tools.translate import _
  23. import logging
  24. # Lib for phone number reformating -> pip install phonenumbers
  25. import phonenumbers
  26. _logger = logging.getLogger(__name__)
  27. class phone_common(orm.AbstractModel):
  28. _name = 'phone.common'
  29. def generic_phonenumber_to_e164(
  30. self, cr, uid, ids, field_from_to_seq, context=None):
  31. result = {}
  32. from_field_seq = [item[0] for item in field_from_to_seq]
  33. for record in self.read(cr, uid, ids, from_field_seq, context=context):
  34. result[record['id']] = {}
  35. for fromfield, tofield in field_from_to_seq:
  36. if not record.get(fromfield):
  37. res = False
  38. else:
  39. try:
  40. res = phonenumbers.format_number(
  41. phonenumbers.parse(record.get(fromfield), None),
  42. phonenumbers.PhoneNumberFormat.E164)
  43. except Exception, e:
  44. _logger.error(
  45. "Cannot reformat the phone number '%s' to E.164 "
  46. "format. Error message: %s"
  47. % (record.get(fromfield), e))
  48. _logger.error(
  49. "You should fix this number and run the wizard "
  50. "'Reformat all phone numbers' from the menu "
  51. "Settings > Configuration > Phones")
  52. # If I raise an exception here, it won't be possible to
  53. # install the module on a DB with bad phone numbers
  54. res = False
  55. result[record['id']][tofield] = res
  56. return result
  57. def _generic_reformat_phonenumbers(self, cr, uid, vals, phonefields=None,
  58. context=None):
  59. """Reformat phone numbers in E.164 format i.e. +33141981242"""
  60. if context is None:
  61. context = {}
  62. if phonefields is None:
  63. phonefields = [
  64. 'phone', 'partner_phone', 'work_phone', 'fax',
  65. 'mobile', 'partner_mobile', 'mobile_phone',
  66. ]
  67. if any([vals.get(field) for field in phonefields]):
  68. user = self.pool['res.users'].browse(cr, uid, uid, context=context)
  69. # country_id on res.company is a fields.function that looks at
  70. # company_id.partner_id.addres(default).country_id
  71. if user.company_id.country_id:
  72. user_countrycode = user.company_id.country_id.code
  73. else:
  74. # We need to raise an exception here because, if we pass None
  75. # as second arg of phonenumbers.parse(), it will raise an
  76. # exception when you try to enter a phone number in
  77. # national format... so it's better to raise the exception here
  78. raise orm.except_orm(
  79. _('Error:'),
  80. _("You should set a country on the company '%s'")
  81. % user.company_id.name)
  82. for field in phonefields:
  83. if vals.get(field):
  84. init_value = vals.get(field)
  85. try:
  86. res_parse = phonenumbers.parse(
  87. vals.get(field), user_countrycode)
  88. vals[field] = phonenumbers.format_number(
  89. res_parse, phonenumbers.PhoneNumberFormat.E164)
  90. if init_value != vals[field]:
  91. _logger.info(
  92. "%s initial value: '%s' updated value: '%s'"
  93. % (field, init_value, vals[field]))
  94. except Exception, e:
  95. # I do BOTH logger and raise, because:
  96. # raise is usefull when the record is created/written
  97. # by a user via the Web interface
  98. # logger is usefull when the record is created/written
  99. # via the webservices
  100. _logger.error(
  101. "Cannot reformat the phone number '%s' to "
  102. "international format" % vals.get(field))
  103. if context.get('raise_if_phone_parse_fails'):
  104. raise orm.except_orm(
  105. _('Error:'),
  106. _("Cannot reformat the phone number '%s' to "
  107. "international format. Error message: %s")
  108. % (vals.get(field), e))
  109. return vals
  110. def get_name_from_phone_number(
  111. self, cr, uid, presented_number, context=None):
  112. '''Function to get name from phone number. Usefull for use from IPBX
  113. to add CallerID name to incoming calls.'''
  114. res = self.get_record_from_phone_number(
  115. cr, uid, presented_number, context=context)
  116. if res:
  117. return res[2]
  118. else:
  119. return False
  120. def get_record_from_phone_number(
  121. self, cr, uid, presented_number, context=None):
  122. '''If it finds something, it returns (object name, ID, record name)
  123. For example : ('res.partner', 42, u'Alexis de Lattre (Akretion)')
  124. '''
  125. if context is None:
  126. context = {}
  127. ctx_phone = context.copy()
  128. ctx_phone['callerid'] = True
  129. _logger.debug(
  130. u"Call get_name_from_phone_number with number = %s"
  131. % presented_number)
  132. if not isinstance(presented_number, (str, unicode)):
  133. _logger.warning(
  134. u"Number '%s' should be a 'str' or 'unicode' but it is a '%s'"
  135. % (presented_number, type(presented_number)))
  136. return False
  137. if not presented_number.isdigit():
  138. _logger.warning(
  139. u"Number '%s' should only contain digits." % presented_number)
  140. user = self.pool['res.users'].browse(cr, uid, uid, context=context)
  141. nr_digits_to_match_from_end = \
  142. user.company_id.number_of_digits_to_match_from_end
  143. if len(presented_number) >= nr_digits_to_match_from_end:
  144. end_number_to_match = presented_number[
  145. -nr_digits_to_match_from_end:len(presented_number)]
  146. else:
  147. end_number_to_match = presented_number
  148. phonefieldsdict = self._get_phone_fields(cr, uid, context=context)
  149. phonefieldslist = []
  150. for objname, prop in phonefieldsdict.iteritems():
  151. if prop.get('get_name_sequence'):
  152. phonefieldslist.append({objname: prop})
  153. phonefieldslist_sorted = sorted(
  154. phonefieldslist,
  155. key=lambda element: element.values()[0]['get_name_sequence'])
  156. for phonedict in phonefieldslist_sorted:
  157. objname = phonedict.keys()[0]
  158. prop = phonedict.values()[0]
  159. phonefields = prop['phonefields']
  160. obj = self.pool[objname]
  161. pg_search_number = str('%' + end_number_to_match)
  162. _logger.debug(
  163. "Will search phone and mobile numbers in %s ending with '%s'"
  164. % (objname, end_number_to_match))
  165. domain = []
  166. for phonefield in phonefields:
  167. domain.append((phonefield, '=like', pg_search_number))
  168. if len(phonefields) > 1:
  169. domain = ['|'] * (len(phonefields) - 1) + domain
  170. res_ids = obj.search(cr, uid, domain, context=context)
  171. if len(res_ids) > 1:
  172. _logger.warning(
  173. u"There are several %s (IDS = %s) with a phone number "
  174. "ending with '%s'. Taking the first one."
  175. % (objname, res_ids, end_number_to_match))
  176. if res_ids:
  177. name = obj.name_get(
  178. cr, uid, res_ids[0], context=ctx_phone)[0][1]
  179. res = (objname, res_ids[0], name)
  180. _logger.debug(
  181. u"Answer get_record_from_phone_number: (%s, %d, %s)"
  182. % (res[0], res[1], res[2]))
  183. return res
  184. else:
  185. _logger.debug(
  186. u"No match on %s for end of phone number '%s'"
  187. % (objname, end_number_to_match))
  188. return False
  189. def _get_phone_fields(self, cr, uid, context=None):
  190. '''Returns a dict with key = object name
  191. and value = list of phone fields'''
  192. res = {
  193. 'res.partner': {
  194. 'phonefields': ['phone', 'mobile'],
  195. 'faxfields': ['fax'],
  196. 'get_name_sequence': 10,
  197. },
  198. }
  199. return res
  200. def click2dial(self, cr, uid, erp_number, context=None):
  201. '''This function is designed to be overridden in IPBX-specific
  202. modules, such as asterisk_click2dial'''
  203. return {'dialed_number': erp_number}
  204. class res_partner(orm.Model):
  205. _name = 'res.partner'
  206. _inherit = ['res.partner', 'phone.common']
  207. def create(self, cr, uid, vals, context=None):
  208. vals_reformated = self._generic_reformat_phonenumbers(
  209. cr, uid, vals, context=context)
  210. return super(res_partner, self).create(
  211. cr, uid, vals_reformated, context=context)
  212. def write(self, cr, uid, ids, vals, context=None):
  213. vals_reformated = self._generic_reformat_phonenumbers(
  214. cr, uid, vals, context=context)
  215. return super(res_partner, self).write(
  216. cr, uid, ids, vals_reformated, context=context)
  217. def name_get(self, cr, uid, ids, context=None):
  218. if context is None:
  219. context = {}
  220. if context.get('callerid'):
  221. res = []
  222. if isinstance(ids, (int, long)):
  223. ids = [ids]
  224. for partner in self.browse(cr, uid, ids, context=context):
  225. if partner.parent_id and partner.parent_id.is_company:
  226. name = u'%s (%s)' % (partner.name, partner.parent_id.name)
  227. else:
  228. name = partner.name
  229. res.append((partner.id, name))
  230. return res
  231. else:
  232. return super(res_partner, self).name_get(
  233. cr, uid, ids, context=context)
  234. class res_company(orm.Model):
  235. _inherit = 'res.company'
  236. _columns = {
  237. 'number_of_digits_to_match_from_end': fields.integer(
  238. 'Number of Digits To Match From End',
  239. help="In several situations, OpenERP will have to find a "
  240. "Partner/Lead/Employee/... from a phone number presented by the "
  241. "calling party. As the phone numbers presented by your phone "
  242. "operator may not always be displayed in a standard format, "
  243. "the best method to find the related Partner/Lead/Employee/... "
  244. "in OpenERP is to try to match the end of the phone number in "
  245. "OpenERP with the N last digits of the phone number presented "
  246. "by the calling party. N is the value you should enter in this "
  247. "field."),
  248. }
  249. _defaults = {
  250. 'number_of_digits_to_match_from_end': 8,
  251. }
  252. _sql_constraints = [(
  253. 'number_of_digits_to_match_from_end_positive',
  254. 'CHECK (number_of_digits_to_match_from_end > 0)',
  255. "The value of the field 'Number of Digits To Match From End' must "
  256. "be positive."),
  257. ]