Browse Source
Started to implement solution d) as discussed in https://github.com/OCA/connector-telephony/issues/90
Started to implement solution d) as discussed in https://github.com/OCA/connector-telephony/issues/90
The revolution in the connector-telephony project has started !pull/88/head
Alexis de Lattre
9 years ago
25 changed files with 398 additions and 1982 deletions
-
1base_phone/README.rst
-
5base_phone/__init__.py
-
6base_phone/__openerp__.py
-
311base_phone/base_phone.py
-
134base_phone/fields.py
-
6base_phone/models/__init__.py
-
0base_phone/models/controller.py
-
130base_phone/models/phone_common.py
-
28base_phone/models/res_company.py
-
30base_phone/models/res_partner.py
-
63base_phone/report_sxw_format.py
-
1397base_phone/static/lib/phonenumbers/PhoneFormat.js
-
38base_phone/static/src/js/phone_widget.js
-
44base_phone/static/src/xml/phone.xml
-
0base_phone/views/res_company_view.xml
-
0base_phone/views/res_partner_view.xml
-
0base_phone/views/res_users_view.xml
-
2base_phone/web_phone.xml
-
19crm_claim_phone/__init__.py
-
47crm_claim_phone/crm_claim_phone.py
-
9crm_claim_phone/crm_claim_view.xml
-
49crm_phone/crm_phone.py
-
19event_phone/event_phone.py
-
20hr_phone/hr_phone.py
-
18hr_recruitment_phone/hr_recruitment_phone.py
@ -1,6 +1,5 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
|
|
||||
from . import base_phone |
|
||||
|
from . import fields |
||||
|
from . import models |
||||
from . import wizard |
from . import wizard |
||||
from . import report_sxw_format |
|
||||
from . import controller |
|
@ -1,311 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Base Phone module for Odoo |
|
||||
# Copyright (C) 2010-2015 Alexis de Lattre <alexis@via.ecp.fr> |
|
||||
# |
|
||||
# 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 (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp import models, fields, api, _ |
|
||||
from openerp.tools.safe_eval import safe_eval |
|
||||
from openerp.exceptions import UserError |
|
||||
import logging |
|
||||
# Lib for phone number reformating -> pip install phonenumbers |
|
||||
import phonenumbers |
|
||||
|
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
|
|
||||
class PhoneCommon(models.AbstractModel): |
|
||||
_name = 'phone.common' |
|
||||
|
|
||||
@api.model |
|
||||
def _reformat_phonenumbers_create(self, vals): |
|
||||
assert isinstance(self._phone_fields, list),\ |
|
||||
'self._phone_fields must be a list' |
|
||||
if any([vals.get(field) for field in self._phone_fields]): |
|
||||
countrycode = self._get_countrycode_from_vals(vals) |
|
||||
countrycode = self._countrycode_fallback(countrycode) |
|
||||
vals = self._reformat_phonenumbers(vals, countrycode) |
|
||||
return vals |
|
||||
|
|
||||
@api.multi |
|
||||
def _reformat_phonenumbers_write(self, vals): |
|
||||
assert isinstance(self._phone_fields, list),\ |
|
||||
'self._phone_fields must be a list' |
|
||||
if any([vals.get(field) for field in self._phone_fields]): |
|
||||
countrycode = self._get_countrycode_from_vals(vals) |
|
||||
if not countrycode: |
|
||||
if self._country_field: |
|
||||
country = safe_eval( |
|
||||
'self.' + self._country_field, {'self': self}) |
|
||||
countrycode = country and country.code or None |
|
||||
elif self._partner_field: |
|
||||
partner = safe_eval( |
|
||||
'self.' + self._partner_field, {'self': self}) |
|
||||
if partner: |
|
||||
countrycode = partner.country_id and\ |
|
||||
partner.country_id.code or None |
|
||||
|
|
||||
countrycode = self._countrycode_fallback(countrycode) |
|
||||
vals = self._reformat_phonenumbers(vals, countrycode) |
|
||||
return vals |
|
||||
|
|
||||
@api.model |
|
||||
def _get_countrycode_from_vals(self, vals): |
|
||||
assert isinstance(self._country_field, (str, unicode, type(None))),\ |
|
||||
'Wrong self._country_field' |
|
||||
assert isinstance(self._partner_field, (str, unicode, type(None))),\ |
|
||||
'Wrong self._partner_field' |
|
||||
countrycode = None |
|
||||
if self._country_field and vals.get(self._country_field): |
|
||||
# When updating a partner in the frontend of the POS |
|
||||
# Odoo generate a write() with the ID of the country as unicode !!! |
|
||||
# example : vals = {u'country_id': u'9'} |
|
||||
# So we have to convert it to an integer before browsing |
|
||||
country = self.env['res.country'].browse( |
|
||||
int(vals[self._country_field])) |
|
||||
countrycode = country.code |
|
||||
elif self._partner_field and vals.get(self._partner_field): |
|
||||
partner = self.env['res.partner'].browse( |
|
||||
vals[self._partner_field]) |
|
||||
countrycode = partner.country_id.code or False |
|
||||
return countrycode |
|
||||
|
|
||||
@api.model |
|
||||
def _countrycode_fallback(self, countrycode): |
|
||||
if not countrycode: |
|
||||
if self.env.user.company_id.country_id: |
|
||||
countrycode = self.env.user.company_id.country_id.code |
|
||||
else: |
|
||||
_logger.error( |
|
||||
"You should set a country on the company '%s' " |
|
||||
"to allow the reformat of phone numbers", |
|
||||
self.env.user.company_id.name) |
|
||||
return countrycode |
|
||||
|
|
||||
@api.model |
|
||||
def _reformat_phonenumbers(self, vals, countrycode): |
|
||||
for field in self._phone_fields: |
|
||||
if vals.get(field): |
|
||||
init_value = vals.get(field) |
|
||||
try: |
|
||||
res_parse = phonenumbers.parse( |
|
||||
vals.get(field), countrycode.upper()) |
|
||||
vals[field] = phonenumbers.format_number( |
|
||||
res_parse, phonenumbers.PhoneNumberFormat.E164) |
|
||||
if init_value != vals[field]: |
|
||||
_logger.info( |
|
||||
"%s initial value: '%s' updated value: '%s'" |
|
||||
% (field, init_value, vals[field])) |
|
||||
except Exception, e: |
|
||||
# I do BOTH logger and raise, because: |
|
||||
# raise is usefull when the record is created/written |
|
||||
# by a user via the Web interface |
|
||||
# logger is usefull when the record is created/written |
|
||||
# via the webservices |
|
||||
_logger.error( |
|
||||
"Cannot reformat the phone number '%s' to " |
|
||||
"international format with region=%s", |
|
||||
vals.get(field), countrycode) |
|
||||
if self.env.context.get('raise_if_phone_parse_fails'): |
|
||||
raise UserError( |
|
||||
_("Cannot reformat the phone number '%s' to " |
|
||||
"international format. Error message: %s") |
|
||||
% (vals.get(field), e)) |
|
||||
return vals |
|
||||
|
|
||||
@api.model |
|
||||
def get_name_from_phone_number(self, presented_number): |
|
||||
'''Function to get name from phone number. Usefull for use from IPBX |
|
||||
to add CallerID name to incoming calls.''' |
|
||||
res = self.get_record_from_phone_number(presented_number) |
|
||||
if res: |
|
||||
return res[2] |
|
||||
else: |
|
||||
return False |
|
||||
|
|
||||
@api.model |
|
||||
def get_record_from_phone_number(self, presented_number): |
|
||||
'''If it finds something, it returns (object name, ID, record name) |
|
||||
For example : ('res.partner', 42, u'Alexis de Lattre (Akretion)') |
|
||||
''' |
|
||||
_logger.debug( |
|
||||
u"Call get_name_from_phone_number with number = %s" |
|
||||
% presented_number) |
|
||||
if not isinstance(presented_number, (str, unicode)): |
|
||||
_logger.warning( |
|
||||
u"Number '%s' should be a 'str' or 'unicode' but it is a '%s'" |
|
||||
% (presented_number, type(presented_number))) |
|
||||
return False |
|
||||
if not presented_number.isdigit(): |
|
||||
_logger.warning( |
|
||||
u"Number '%s' should only contain digits." % presented_number) |
|
||||
|
|
||||
nr_digits_to_match_from_end = \ |
|
||||
self.env.user.company_id.number_of_digits_to_match_from_end |
|
||||
if len(presented_number) >= nr_digits_to_match_from_end: |
|
||||
end_number_to_match = presented_number[ |
|
||||
-nr_digits_to_match_from_end:len(presented_number)] |
|
||||
else: |
|
||||
end_number_to_match = presented_number |
|
||||
|
|
||||
phoneobjects = self._get_phone_fields() |
|
||||
phonefieldslist = [] # [('res.parter', 10), ('crm.lead', 20)] |
|
||||
for objname in phoneobjects: |
|
||||
if ( |
|
||||
'_phone_name_sequence' in dir(self.env[objname]) and |
|
||||
self.env[objname]._phone_name_sequence): |
|
||||
phonefieldslist.append( |
|
||||
(objname, self.env[objname]._phone_name_sequence)) |
|
||||
phonefieldslist_sorted = sorted( |
|
||||
phonefieldslist, |
|
||||
key=lambda element: element[1]) |
|
||||
_logger.debug('phonefieldslist_sorted=%s' % phonefieldslist_sorted) |
|
||||
for (objname, prio) in phonefieldslist_sorted: |
|
||||
obj = self.with_context(callerid=True).env[objname] |
|
||||
pg_search_number = str('%' + end_number_to_match) |
|
||||
_logger.debug( |
|
||||
"Will search phone and mobile numbers in %s ending with '%s'" |
|
||||
% (objname, end_number_to_match)) |
|
||||
domain = [] |
|
||||
for phonefield in obj._phone_fields: |
|
||||
domain.append((phonefield, '=like', pg_search_number)) |
|
||||
if len(obj._phone_fields) > 1: |
|
||||
domain = ['|'] * (len(obj._phone_fields) - 1) + domain |
|
||||
res_obj = obj.search(domain) |
|
||||
if len(res_obj) > 1: |
|
||||
_logger.warning( |
|
||||
u"There are several %s (IDS = %s) with a phone number " |
|
||||
"ending with '%s'. Taking the first one." |
|
||||
% (objname, res_obj.ids, end_number_to_match)) |
|
||||
res_obj = res_obj[0] |
|
||||
if res_obj: |
|
||||
name = res_obj.name_get()[0][1] |
|
||||
res = (objname, res_obj.id, name) |
|
||||
_logger.debug( |
|
||||
u"Answer get_record_from_phone_number: (%s, %d, %s)" |
|
||||
% (res[0], res[1], res[2])) |
|
||||
return res |
|
||||
else: |
|
||||
_logger.debug( |
|
||||
u"No match on %s for end of phone number '%s'" |
|
||||
% (objname, end_number_to_match)) |
|
||||
return False |
|
||||
|
|
||||
@api.model |
|
||||
def _get_phone_fields(self): |
|
||||
'''Returns a dict with key = object name |
|
||||
and value = list of phone fields''' |
|
||||
models = self.env['ir.model'].search([('transient', '=', False)]) |
|
||||
res = [] |
|
||||
for model in models: |
|
||||
senv = False |
|
||||
try: |
|
||||
senv = self.env[model.model] |
|
||||
except: |
|
||||
continue |
|
||||
if ( |
|
||||
'_phone_fields' in dir(senv) and |
|
||||
isinstance(senv._phone_fields, list)): |
|
||||
res.append(model.model) |
|
||||
return res |
|
||||
|
|
||||
@api.model |
|
||||
def click2dial(self, erp_number): |
|
||||
'''This function is designed to be overridden in IPBX-specific |
|
||||
modules, such as asterisk_click2dial or ovh_telephony_connector''' |
|
||||
return {'dialed_number': erp_number} |
|
||||
|
|
||||
@api.model |
|
||||
def convert_to_dial_number(self, erp_number): |
|
||||
''' |
|
||||
This function is dedicated to the transformation of the number |
|
||||
available in Odoo to the number that can be dialed. |
|
||||
You may have to inherit this function in another module specific |
|
||||
for your company if you are not happy with the way I reformat |
|
||||
the numbers. |
|
||||
''' |
|
||||
assert(erp_number), 'Missing phone number' |
|
||||
_logger.debug('Number before reformat = %s' % erp_number) |
|
||||
# erp_number are supposed to be in E.164 format, so no need to |
|
||||
# give a country code here |
|
||||
parsed_num = phonenumbers.parse(erp_number, None) |
|
||||
country_code = self.env.user.company_id.country_id.code |
|
||||
assert(country_code), 'Missing country on company' |
|
||||
_logger.debug('Country code = %s' % country_code) |
|
||||
to_dial_number = phonenumbers.format_out_of_country_calling_number( |
|
||||
parsed_num, country_code.upper()) |
|
||||
to_dial_number = str(to_dial_number).translate(None, ' -.()/') |
|
||||
_logger.debug('Number to be sent to phone system: %s' % to_dial_number) |
|
||||
return to_dial_number |
|
||||
|
|
||||
|
|
||||
class ResPartner(models.Model): |
|
||||
_name = 'res.partner' |
|
||||
_inherit = ['res.partner', 'phone.common'] |
|
||||
_phone_fields = ['phone', 'mobile', 'fax'] |
|
||||
_phone_name_sequence = 10 |
|
||||
_country_field = 'country_id' |
|
||||
_partner_field = None |
|
||||
|
|
||||
@api.model |
|
||||
def create(self, vals): |
|
||||
vals_reformated = self._reformat_phonenumbers_create(vals) |
|
||||
return super(ResPartner, self).create(vals_reformated) |
|
||||
|
|
||||
@api.multi |
|
||||
def write(self, vals): |
|
||||
vals_reformated = self._reformat_phonenumbers_write(vals) |
|
||||
return super(ResPartner, self).write(vals_reformated) |
|
||||
|
|
||||
@api.multi |
|
||||
def name_get(self): |
|
||||
if self._context.get('callerid'): |
|
||||
res = [] |
|
||||
for partner in self: |
|
||||
if partner.parent_id and partner.parent_id.is_company: |
|
||||
name = u'%s (%s)' % (partner.name, partner.parent_id.name) |
|
||||
else: |
|
||||
name = partner.name |
|
||||
res.append((partner.id, name)) |
|
||||
return res |
|
||||
else: |
|
||||
return super(ResPartner, self).name_get() |
|
||||
|
|
||||
|
|
||||
class ResCompany(models.Model): |
|
||||
_inherit = 'res.company' |
|
||||
|
|
||||
number_of_digits_to_match_from_end = fields.Integer( |
|
||||
string='Number of Digits To Match From End', |
|
||||
default=8, |
|
||||
help="In several situations, OpenERP will have to find a " |
|
||||
"Partner/Lead/Employee/... from a phone number presented by the " |
|
||||
"calling party. As the phone numbers presented by your phone " |
|
||||
"operator may not always be displayed in a standard format, " |
|
||||
"the best method to find the related Partner/Lead/Employee/... " |
|
||||
"in OpenERP is to try to match the end of the phone number in " |
|
||||
"OpenERP with the N last digits of the phone number presented " |
|
||||
"by the calling party. N is the value you should enter in this " |
|
||||
"field.") |
|
||||
|
|
||||
_sql_constraints = [( |
|
||||
'number_of_digits_to_match_from_end_positive', |
|
||||
'CHECK (number_of_digits_to_match_from_end > 0)', |
|
||||
"The value of the field 'Number of Digits To Match From End' must " |
|
||||
"be positive."), |
|
||||
] |
|
@ -0,0 +1,134 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2016 Akretion (http://www.akretion.com) |
||||
|
# Sébastien BEAU <sebastien.beau@akretion.com> |
||||
|
# Alexis de Lattre <alexis.delattre@akretion.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
|
||||
|
from openerp import api, fields, models |
||||
|
from operator import attrgetter |
||||
|
import phonenumbers |
||||
|
import logging |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class Phone(fields.Char): |
||||
|
|
||||
|
_slots = { |
||||
|
'country_field': None, |
||||
|
'partner_field': None, |
||||
|
} |
||||
|
|
||||
|
def __init__( |
||||
|
self, string=None, country_field=None, partner_field=None, |
||||
|
**kwargs): |
||||
|
super(Phone, self).__init__( |
||||
|
string=string, country_field=country_field, |
||||
|
partner_field=partner_field, **kwargs) |
||||
|
|
||||
|
_related_country_field = property(attrgetter('country_field')) |
||||
|
_related_partner_field = property(attrgetter('partner_field')) |
||||
|
|
||||
|
def _setup_regular_full(self, model): |
||||
|
super(Phone, self)._setup_regular_full(model) |
||||
|
assert self.country_field in model._fields or \ |
||||
|
self.partner_field in model._fields, \ |
||||
|
"field %s with unknown country_field and partner_field" % self |
||||
|
|
||||
|
def convert_to_cache(self, value, record, validate=True): |
||||
|
res = super(Phone, self).convert_to_cache( |
||||
|
value, record, validate=validate) |
||||
|
# print 'db value', res |
||||
|
if res: |
||||
|
try: |
||||
|
res_parse = phonenumbers.parse(res) |
||||
|
res = phonenumbers.format_number( |
||||
|
res_parse, phonenumbers.PhoneNumberFormat.INTERNATIONAL) |
||||
|
# print "after parse+intl res=", res |
||||
|
except: |
||||
|
pass |
||||
|
# print 'cache value', res |
||||
|
return res |
||||
|
|
||||
|
|
||||
|
def convert_phone_field(value, country_code): |
||||
|
_logger.debug( |
||||
|
'convert_phone_field value=%s country=%s', value, country_code) |
||||
|
try: |
||||
|
res_parse = phonenumbers.parse( |
||||
|
value, country_code) |
||||
|
_logger.debug('res_parse=%s', res_parse) |
||||
|
new_value = phonenumbers.format_number( |
||||
|
res_parse, phonenumbers.PhoneNumberFormat.E164) |
||||
|
_logger.debug('new_value=%s', new_value) |
||||
|
except: |
||||
|
_logger.error( |
||||
|
"Cannot reformat the phone number '%s' to " |
||||
|
"international format with region=%s", |
||||
|
value, country_code) |
||||
|
new_value = value |
||||
|
return new_value |
||||
|
|
||||
|
|
||||
|
def convert_all_phone_fields(self, vals, fields_to_convert): |
||||
|
loc_vals = vals.copy() |
||||
|
for field in fields_to_convert: |
||||
|
country_key = self._fields[field].country_field |
||||
|
partner_key = self._fields[field].partner_field |
||||
|
country = False |
||||
|
if country_key: |
||||
|
if country_key in loc_vals: |
||||
|
country = self.env['res.country'].browse(vals[country_key]) |
||||
|
else: |
||||
|
country = self[country_key] |
||||
|
elif partner_key: |
||||
|
if partner_key in loc_vals: |
||||
|
partner = self.env['res.partner'].browse(vals[partner_key]) |
||||
|
else: |
||||
|
partner = self[partner_key] |
||||
|
if partner: |
||||
|
country = partner.country_id |
||||
|
if not country: |
||||
|
country = self.env.user.company_id.country_id |
||||
|
country_code = False |
||||
|
if country: |
||||
|
country_code = country.code.upper() |
||||
|
loc_vals[field] = convert_phone_field(loc_vals[field], country_code) |
||||
|
return loc_vals |
||||
|
|
||||
|
|
||||
|
def get_phone_fields(self, vals): |
||||
|
fields_to_convert = [] |
||||
|
for key in vals: |
||||
|
if isinstance(self._fields[key], Phone): |
||||
|
fields_to_convert.append(key) |
||||
|
return fields_to_convert |
||||
|
|
||||
|
original_write = models.Model.write |
||||
|
original_create = models.Model.create |
||||
|
|
||||
|
|
||||
|
@api.multi |
||||
|
def write(self, vals): |
||||
|
fields_to_convert = get_phone_fields(self, vals) |
||||
|
if fields_to_convert: |
||||
|
for record in self: |
||||
|
loc_vals = convert_all_phone_fields( |
||||
|
record, vals, fields_to_convert) |
||||
|
original_write(record, loc_vals) |
||||
|
return True |
||||
|
else: |
||||
|
return original_write(self, vals) |
||||
|
|
||||
|
|
||||
|
@api.model |
||||
|
@api.returns('self', lambda value: value.id) |
||||
|
def create(self, vals): |
||||
|
fields_to_convert = get_phone_fields(self, vals) |
||||
|
if fields_to_convert: |
||||
|
vals = convert_all_phone_fields(self, vals, fields_to_convert) |
||||
|
return original_create(self, vals) |
||||
|
|
||||
|
models.Model.write = write |
||||
|
models.Model.create = create |
@ -0,0 +1,6 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import controller |
||||
|
from . import res_company |
||||
|
from . import res_partner |
||||
|
from . import phone_common |
@ -0,0 +1,130 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from openerp import models, api |
||||
|
from openerp.addons.base_phone.fields import Phone |
||||
|
import logging |
||||
|
# Lib for phone number reformating -> pip install phonenumbers |
||||
|
import phonenumbers |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class PhoneCommon(models.AbstractModel): |
||||
|
_name = 'phone.common' |
||||
|
|
||||
|
@api.model |
||||
|
def get_name_from_phone_number(self, presented_number): |
||||
|
'''Function to get name from phone number. Usefull for use from IPBX |
||||
|
to add CallerID name to incoming calls.''' |
||||
|
res = self.get_record_from_phone_number(presented_number) |
||||
|
if res: |
||||
|
return res[2] |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
@api.model |
||||
|
def get_record_from_phone_number(self, presented_number): |
||||
|
'''If it finds something, it returns (object name, ID, record name) |
||||
|
For example : ('res.partner', 42, u'Alexis de Lattre (Akretion)') |
||||
|
''' |
||||
|
_logger.debug( |
||||
|
u"Call get_name_from_phone_number with number = %s" |
||||
|
% presented_number) |
||||
|
if not isinstance(presented_number, (str, unicode)): |
||||
|
_logger.warning( |
||||
|
u"Number '%s' should be a 'str' or 'unicode' but it is a '%s'" |
||||
|
% (presented_number, type(presented_number))) |
||||
|
return False |
||||
|
if not presented_number.isdigit(): |
||||
|
_logger.warning( |
||||
|
u"Number '%s' should only contain digits." % presented_number) |
||||
|
|
||||
|
nr_digits_to_match_from_end = \ |
||||
|
self.env.user.company_id.number_of_digits_to_match_from_end |
||||
|
if len(presented_number) >= nr_digits_to_match_from_end: |
||||
|
end_number_to_match = presented_number[ |
||||
|
-nr_digits_to_match_from_end:len(presented_number)] |
||||
|
else: |
||||
|
end_number_to_match = presented_number |
||||
|
|
||||
|
sorted_phonemodels = self._get_phone_models() |
||||
|
# [('res.parter', 10), ('crm.lead', 20)] |
||||
|
for (obj, prio) in sorted_phonemodels: |
||||
|
pg_search_number = str('%' + end_number_to_match) |
||||
|
_logger.debug( |
||||
|
"Will search phone and mobile numbers in %s ending with '%s'", |
||||
|
obj._name, end_number_to_match) |
||||
|
domain = [] |
||||
|
for field in obj._fields: |
||||
|
if isinstance(obj._fields[field], Phone): |
||||
|
domain.append((field, '=like', pg_search_number)) |
||||
|
if len(domain) > 1: |
||||
|
domain = ['|'] * (len(domain) - 1) + domain |
||||
|
_logger.debug('searching on %s with domain=%s', obj._name, domain) |
||||
|
res_obj = obj.search(domain) |
||||
|
if len(res_obj) > 1: |
||||
|
_logger.warning( |
||||
|
u"There are several %s (IDS = %s) with a phone number " |
||||
|
"ending with '%s'. Taking the first one.", |
||||
|
obj._name, res_obj.ids, end_number_to_match) |
||||
|
res_obj = res_obj[0] |
||||
|
if res_obj: |
||||
|
name = res_obj.name_get()[0][1] |
||||
|
res = (obj._name, res_obj.id, name) |
||||
|
_logger.debug( |
||||
|
u"Answer get_record_from_phone_number: (%s, %d, %s)", |
||||
|
res[0], res[1], res[2]) |
||||
|
return res |
||||
|
else: |
||||
|
_logger.debug( |
||||
|
u"No match on %s for end of phone number '%s'", |
||||
|
obj._name, end_number_to_match) |
||||
|
return False |
||||
|
|
||||
|
@api.model |
||||
|
def _get_phone_models(self): |
||||
|
res = [] |
||||
|
for model_name in self.env.registry.keys(): |
||||
|
senv = False |
||||
|
try: |
||||
|
senv = self.with_context(callerid=True).env[model_name] |
||||
|
except: |
||||
|
continue |
||||
|
if ( |
||||
|
hasattr(senv, '_phone_name_sequence') and |
||||
|
isinstance(senv._phone_name_sequence, int)): |
||||
|
res.append((senv, senv._phone_name_sequence)) |
||||
|
|
||||
|
phonemodels_sorted = sorted(res, key=lambda element: element[1]) |
||||
|
return phonemodels_sorted |
||||
|
|
||||
|
@api.model |
||||
|
def click2dial(self, erp_number): |
||||
|
'''This function is designed to be overridden in IPBX-specific |
||||
|
modules, such as asterisk_click2dial or ovh_telephony_connector''' |
||||
|
return {'dialed_number': erp_number} |
||||
|
|
||||
|
@api.model |
||||
|
def convert_to_dial_number(self, erp_number): |
||||
|
''' |
||||
|
This function is dedicated to the transformation of the number |
||||
|
available in Odoo to the number that can be dialed. |
||||
|
You may have to inherit this function in another module specific |
||||
|
for your company if you are not happy with the way I reformat |
||||
|
the numbers. |
||||
|
''' |
||||
|
assert(erp_number), 'Missing phone number' |
||||
|
_logger.debug('Number before reformat = %s' % erp_number) |
||||
|
# erp_number are supposed to be in E.164 format, so no need to |
||||
|
# give a country code here |
||||
|
parsed_num = phonenumbers.parse(erp_number, None) |
||||
|
country_code = self.env.user.company_id.country_id.code |
||||
|
assert(country_code), 'Missing country on company' |
||||
|
_logger.debug('Country code = %s' % country_code) |
||||
|
to_dial_number = phonenumbers.format_out_of_country_calling_number( |
||||
|
parsed_num, country_code.upper()) |
||||
|
to_dial_number = str(to_dial_number).translate(None, ' -.()/') |
||||
|
_logger.debug('Number to be sent to phone system: %s' % to_dial_number) |
||||
|
return to_dial_number |
@ -0,0 +1,28 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from openerp import models, fields |
||||
|
|
||||
|
|
||||
|
class ResCompany(models.Model): |
||||
|
_inherit = 'res.company' |
||||
|
|
||||
|
number_of_digits_to_match_from_end = fields.Integer( |
||||
|
string='Number of Digits To Match From End', |
||||
|
default=8, |
||||
|
help="In several situations, OpenERP will have to find a " |
||||
|
"Partner/Lead/Employee/... from a phone number presented by the " |
||||
|
"calling party. As the phone numbers presented by your phone " |
||||
|
"operator may not always be displayed in a standard format, " |
||||
|
"the best method to find the related Partner/Lead/Employee/... " |
||||
|
"in OpenERP is to try to match the end of the phone number in " |
||||
|
"OpenERP with the N last digits of the phone number presented " |
||||
|
"by the calling party. N is the value you should enter in this " |
||||
|
"field.") |
||||
|
|
||||
|
_sql_constraints = [( |
||||
|
'number_of_digits_to_match_from_end_positive', |
||||
|
'CHECK (number_of_digits_to_match_from_end > 0)', |
||||
|
"The value of the field 'Number of Digits To Match From End' must " |
||||
|
"be positive.")] |
@ -0,0 +1,30 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
|
||||
|
from openerp import models, api |
||||
|
from openerp.addons.base_phone import fields |
||||
|
|
||||
|
|
||||
|
class ResPartner(models.Model): |
||||
|
_inherit = 'res.partner' |
||||
|
_phone_name_sequence = 10 |
||||
|
|
||||
|
phone = fields.Phone(country_field='country_id', partner_field=None) |
||||
|
mobile = fields.Phone(country_field='country_id', partner_field=None) |
||||
|
fax = fields.Phone(country_field='country_id', partner_field=None) |
||||
|
|
||||
|
@api.multi |
||||
|
def name_get(self): |
||||
|
if self._context.get('callerid'): |
||||
|
res = [] |
||||
|
for partner in self: |
||||
|
if partner.parent_id and partner.parent_id.is_company: |
||||
|
name = u'%s (%s)' % (partner.name, partner.parent_id.name) |
||||
|
else: |
||||
|
name = partner.name |
||||
|
res.append((partner.id, name)) |
||||
|
return res |
||||
|
else: |
||||
|
return super(ResPartner, self).name_get() |
@ -1,63 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# Base Phone module for OpenERP |
|
||||
# Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr> |
|
||||
# |
|
||||
# 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 (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from openerp import models |
|
||||
from openerp.report import report_sxw |
|
||||
import phonenumbers |
|
||||
|
|
||||
|
|
||||
class BasePhoneInstalled(models.AbstractModel): |
|
||||
'''When you use monkey patching, the code is executed when the module |
|
||||
is in the addons_path of the OpenERP server, even is the module is not |
|
||||
installed ! In order to avoid the side-effects it can create, |
|
||||
we create an AbstractModel inside the module and we test the |
|
||||
availability of this Model in the code of the monkey patching below. |
|
||||
At Akretion, we call this the "Guewen trick", in reference |
|
||||
to a trick used by Guewen Baconnier in the "connector" module. |
|
||||
''' |
|
||||
_name = "base.phone.installed" |
|
||||
|
|
||||
|
|
||||
format_original = report_sxw.rml_parse.format |
|
||||
|
|
||||
|
|
||||
def format( |
|
||||
self, text, oldtag=None, phone=False, phone_format='international'): |
|
||||
if self.pool.get('base.phone.installed') and phone and text: |
|
||||
# text should already be in E164 format, so we don't have |
|
||||
# to give a country code to phonenumbers.parse() |
|
||||
phone_number = phonenumbers.parse(text) |
|
||||
if phone_format == 'international': |
|
||||
res = phonenumbers.format_number( |
|
||||
phone_number, phonenumbers.PhoneNumberFormat.INTERNATIONAL) |
|
||||
elif phone_format == 'national': |
|
||||
res = phonenumbers.format_number( |
|
||||
phone_number, phonenumbers.PhoneNumberFormat.NATIONAL) |
|
||||
elif phone_format == 'e164': |
|
||||
res = phonenumbers.format_number( |
|
||||
phone_number, phonenumbers.PhoneNumberFormat.E164) |
|
||||
else: |
|
||||
res = text |
|
||||
else: |
|
||||
res = format_original(self, text, oldtag=oldtag) |
|
||||
return res |
|
||||
|
|
||||
report_sxw.rml_parse.format = format |
|
1397
base_phone/static/lib/phonenumbers/PhoneFormat.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,22 +1,3 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
############################################################################## |
|
||||
# |
|
||||
# CRM Claim Phone module for Odoo/OpenERP |
|
||||
# Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr> |
|
||||
# |
|
||||
# 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 (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
|
||||
from . import crm_claim_phone |
from . import crm_claim_phone |
@ -1,43 +1,12 @@ |
|||||
# -*- encoding: utf-8 -*- |
|
||||
############################################################################## |
|
||||
# |
|
||||
# CRM Claim Phone module for Odoo/OpenERP |
|
||||
# Copyright (c) 2012-2014 Akretion (http://www.akretion.com) |
|
||||
# @author: Alexis de Lattre <alexis.delattre@akretion.com> |
|
||||
# |
|
||||
# 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 |
|
||||
# (at your option) any later version. |
|
||||
# |
|
||||
# This program is distributed in the hope that it will be useful, |
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||
# GNU Affero General Public License for more details. |
|
||||
# |
|
||||
# You should have received a copy of the GNU Affero General Public License |
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||
# |
|
||||
############################################################################## |
|
||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2012-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
from openerp.osv import orm |
|
||||
|
from openerp import models |
||||
|
from openerp.addons.base_phone.fields import Phone |
||||
|
|
||||
|
|
||||
class crm_claim(orm.Model): |
|
||||
_name = 'crm.claim' |
|
||||
_inherit = ['crm.claim', 'phone.common'] |
|
||||
_phone_fields = ['partner_phone'] |
|
||||
_country_field = None |
|
||||
_partner_field = 'partner_id' |
|
||||
|
class CrmClaim(models.Model): |
||||
|
_inherit = 'crm.claim' |
||||
|
|
||||
def create(self, cr, uid, vals, context=None): |
|
||||
vals_reformated = self._generic_reformat_phonenumbers( |
|
||||
cr, uid, None, vals, context=context) |
|
||||
return super(crm_claim, self).create( |
|
||||
cr, uid, vals_reformated, context=context) |
|
||||
|
|
||||
def write(self, cr, uid, ids, vals, context=None): |
|
||||
vals_reformated = self._generic_reformat_phonenumbers( |
|
||||
cr, uid, ids, vals, context=context) |
|
||||
return super(crm_claim, self).write( |
|
||||
cr, uid, ids, vals_reformated, context=context) |
|
||||
|
partner_phone = Phone(partner_field='partner_id') |
Write
Preview
Loading…
Cancel
Save
Reference in new issue