From c95456c68c8ae415ebc952666e62439060420356 Mon Sep 17 00:00:00 2001 From: EL HADJI DEM Date: Wed, 24 Sep 2014 15:23:24 -0400 Subject: [PATCH] [ADD] Add base_phone module;it validates phone numbers using the *phonenumbers* Python library. --- base_phone/__init__.py | 25 ++ base_phone/__openerp__.py | 86 ++++++ base_phone/base_phone.py | 269 +++++++++++++++++ base_phone/base_phone_demo.xml | 18 ++ base_phone/controller.py | 35 +++ base_phone/i18n/base_phone.pot | 273 ++++++++++++++++++ base_phone/i18n/fr.po | 273 ++++++++++++++++++ base_phone/report_sxw_format.py | 63 ++++ base_phone/res_company_view.xml | 33 +++ base_phone/res_partner_view.xml | 50 ++++ base_phone/res_users_view.xml | 40 +++ base_phone/security/ir.model.access.csv | 2 + base_phone/security/phone_security.xml | 17 ++ base_phone/static/src/js/phone_widget.js | 105 +++++++ base_phone/static/src/xml/phone.xml | 51 ++++ base_phone/wizard/__init__.py | 23 ++ base_phone/wizard/number_not_found.py | 129 +++++++++ base_phone/wizard/number_not_found_view.xml | 55 ++++ .../wizard/reformat_all_phonenumbers.py | 101 +++++++ .../wizard/reformat_all_phonenumbers_view.xml | 44 +++ 20 files changed, 1692 insertions(+) create mode 100644 base_phone/__init__.py create mode 100644 base_phone/__openerp__.py create mode 100644 base_phone/base_phone.py create mode 100644 base_phone/base_phone_demo.xml create mode 100644 base_phone/controller.py create mode 100644 base_phone/i18n/base_phone.pot create mode 100644 base_phone/i18n/fr.po create mode 100644 base_phone/report_sxw_format.py create mode 100644 base_phone/res_company_view.xml create mode 100644 base_phone/res_partner_view.xml create mode 100644 base_phone/res_users_view.xml create mode 100644 base_phone/security/ir.model.access.csv create mode 100644 base_phone/security/phone_security.xml create mode 100644 base_phone/static/src/js/phone_widget.js create mode 100644 base_phone/static/src/xml/phone.xml create mode 100644 base_phone/wizard/__init__.py create mode 100644 base_phone/wizard/number_not_found.py create mode 100644 base_phone/wizard/number_not_found_view.xml create mode 100644 base_phone/wizard/reformat_all_phonenumbers.py create mode 100644 base_phone/wizard/reformat_all_phonenumbers_view.xml diff --git a/base_phone/__init__.py b/base_phone/__init__.py new file mode 100644 index 000000000..36edafc7a --- /dev/null +++ b/base_phone/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Base Phone module for OpenERP +# Copyright (C) 2014 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 (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 . +# +############################################################################## + +from . import base_phone +from . import wizard +from . import report_sxw_format +from . import controller diff --git a/base_phone/__openerp__.py b/base_phone/__openerp__.py new file mode 100644 index 000000000..348a6f889 --- /dev/null +++ b/base_phone/__openerp__.py @@ -0,0 +1,86 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Base Phone module for OpenERP +# Copyright (C) 2014 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 (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 . +# +############################################################################## + + +{ + 'name': 'Base Phone', + 'version': '0.1', + 'category': 'Phone', + 'license': 'AGPL-3', + 'summary': 'Validate phone numbers', + 'description': """ +Base Phone +========== + +This module validates phone numbers using the *phonenumbers* Python library, +which is a port of the library used in Android smartphones. For example, +if your user is linked to a French company and you update the form +view of a partner with a badly written French phone number +such as '01-55-42-12-42', Odoo will automatically update the phone number +to E.164 format '+33155421242' and display in the form view of the partner +the readable equivalent '+33 1 55 42 12 42'. + +This module also adds *tel:* links on phone numbers and *fax:* links on fax +numbers. If you have a softphone or a client software on your PC +that is associated with *tel:* links, the softphone should propose +you to dial the phone number when you click on such a link. + +This module also updates the format() function for reports +and adds 2 arguments : + +* *phone* : should be True for a phone number, False (default) otherwize. +* *phone_format* : it can have 3 possible values : + * *international* (default) : the report will display '+33 1 55 42 12 42' + * *national* : the report will display '01 55 42 12 42' + * *e164* : the report will display '+33155421242' + +For example, in the Sale Order report, to display the phone +number of the Salesman, you can write : o.user_id and o.user_id.phone +and format(o.user_id.phone, phone=True, phone_format='national') or '' + +This module is independant from the Asterisk connector. + +Please contact Alexis de Lattre from Akretion +for any help or question about this module. +""", + 'author': 'Akretion', + 'website': 'http://www.akretion.com/', + 'depends': ['base', 'web'], + 'external_dependencies': {'python': ['phonenumbers']}, + 'data': [ + 'security/phone_security.xml', + 'security/ir.model.access.csv', + 'res_partner_view.xml', + 'res_company_view.xml', + 'res_users_view.xml', + 'wizard/reformat_all_phonenumbers_view.xml', + 'wizard/number_not_found_view.xml', + ], + 'js': [ + 'static/src/js/*.js', + 'static/lib/js/*.js', + ], + 'qweb': ['static/src/xml/*.xml'], + 'demo': ['base_phone_demo.xml'], + 'images': [], + 'installable': True, + 'active': False, +} diff --git a/base_phone/base_phone.py b/base_phone/base_phone.py new file mode 100644 index 000000000..51a2a2a5c --- /dev/null +++ b/base_phone/base_phone.py @@ -0,0 +1,269 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Base Phone module for Odoo/OpenERP +# Copyright (C) 2010-2014 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 (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 . +# +############################################################################## + +from openerp.osv import orm, fields +from openerp.tools.translate import _ +import logging +# Lib for phone number reformating -> pip install phonenumbers +import phonenumbers + +_logger = logging.getLogger(__name__) + + +class phone_common(orm.AbstractModel): + _name = 'phone.common' + + def generic_phonenumber_to_e164( + self, cr, uid, ids, field_from_to_seq, context=None): + result = {} + from_field_seq = [item[0] for item in field_from_to_seq] + for record in self.read(cr, uid, ids, from_field_seq, context=context): + result[record['id']] = {} + for fromfield, tofield in field_from_to_seq: + if not record.get(fromfield): + res = False + else: + try: + res = phonenumbers.format_number( + phonenumbers.parse(record.get(fromfield), None), + phonenumbers.PhoneNumberFormat.E164) + except Exception, e: + _logger.error( + "Cannot reformat the phone number '%s' to E.164 " + "format. Error message: %s" + % (record.get(fromfield), e)) + _logger.error( + "You should fix this number and run the wizard " + "'Reformat all phone numbers' from the menu " + "Settings > Configuration > Phones") + # If I raise an exception here, it won't be possible to + # install the module on a DB with bad phone numbers + res = False + result[record['id']][tofield] = res + return result + + def _generic_reformat_phonenumbers( + self, cr, uid, vals, + phonefields=[ + 'phone', 'partner_phone', 'work_phone', 'fax', + 'mobile', 'partner_mobile', 'mobile_phone', + ], + context=None): + """Reformat phone numbers in E.164 format i.e. +33141981242""" + if any([vals.get(field) for field in phonefields]): + user = self.pool['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 orm.except_orm( + _('Error:'), + _("You should set a country on the company '%s'") + % user.company_id.name) + for field in phonefields: + if vals.get(field): + init_value = vals.get(field) + try: + res_parse = phonenumbers.parse( + vals.get(field), user_countrycode) + except Exception, e: + raise orm.except_orm( + _('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.E164) + if init_value != vals[field]: + _logger.info( + "%s initial value: '%s' updated value: '%s'" + % (field, init_value, vals[field])) + return vals + + def get_name_from_phone_number( + self, cr, uid, presented_number, context=None): + '''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( + cr, uid, presented_number, context=context) + if res: + return res[2] + else: + return False + + def get_record_from_phone_number( + self, cr, uid, presented_number, context=None): + '''If it finds something, it returns (object name, ID, record name) + For example : ('res.partner', 42, u'Alexis de Lattre (Akretion)') + ''' + if context is None: + context = {} + ctx_phone = context.copy() + ctx_phone['callerid'] = True + _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) + + user = self.pool['res.users'].browse(cr, uid, uid, context=context) + nr_digits_to_match_from_end = \ + 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 + + phonefieldsdict = self._get_phone_fields(cr, uid, context=context) + phonefieldslist = [] + for objname, prop in phonefieldsdict.iteritems(): + if prop.get('get_name_sequence'): + phonefieldslist.append({objname: prop}) + phonefieldslist_sorted = sorted( + phonefieldslist, + key=lambda element: element.values()[0]['get_name_sequence']) + + for phonedict in phonefieldslist_sorted: + objname = phonedict.keys()[0] + prop = phonedict.values()[0] + phonefields = prop['phonefields'] + obj = self.pool[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 phonefields: + domain.append((phonefield, 'like', pg_search_number)) + if len(phonefields) > 1: + domain = ['|'] * (len(phonefields) - 1) + domain + res_ids = obj.search(cr, uid, domain, context=context) + if len(res_ids) > 1: + _logger.warning( + u"There are several %s (IDS = %s) with a phone number " + "ending with '%s'. Taking the first one." + % (objname, res_ids, end_number_to_match)) + if res_ids: + name = obj.name_get( + cr, uid, res_ids[0], context=ctx_phone)[0][1] + res = (objname, res_ids[0], 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 + + def _get_phone_fields(self, cr, uid, context=None): + '''Returns a dict with key = object name + and value = list of phone fields''' + res = { + 'res.partner': { + 'phonefields': ['phone', 'mobile'], + 'faxfields': ['fax'], + 'get_name_sequence': 10, + }, + } + return res + + def click2dial(self, cr, uid, erp_number, context=None): + '''This function is designed to be overridden in IPBX-specific + modules, such as asterisk_click2dial''' + return {'dialed_number': erp_number} + + +class res_partner(orm.Model): + _name = 'res.partner' + _inherit = ['res.partner', 'phone.common'] + + def create(self, cr, uid, vals, context=None): + vals_reformated = self._generic_reformat_phonenumbers( + cr, uid, vals, context=context) + return super(res_partner, 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, vals, context=context) + return super(res_partner, self).write( + cr, uid, ids, vals_reformated, context=context) + + def name_get(self, cr, uid, ids, context=None): + if context is None: + context = {} + if context.get('callerid'): + res = [] + if isinstance(ids, (int, long)): + ids = [ids] + for partner in self.browse(cr, uid, ids, context=context): + 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(res_partner, self).name_get( + cr, uid, ids, context=context) + + +class res_company(orm.Model): + _inherit = 'res.company' + + _columns = { + 'number_of_digits_to_match_from_end': fields.integer( + 'Number of Digits To Match From End', + 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."), + } + + _defaults = { + 'number_of_digits_to_match_from_end': 8, + } + + _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."), + ] diff --git a/base_phone/base_phone_demo.xml b/base_phone/base_phone_demo.xml new file mode 100644 index 000000000..4e341e159 --- /dev/null +++ b/base_phone/base_phone_demo.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/base_phone/controller.py b/base_phone/controller.py new file mode 100644 index 000000000..2722f933b --- /dev/null +++ b/base_phone/controller.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Asterisk click2dial 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 . +# +############################################################################## + +import openerp + + +class BasePhoneController(openerp.addons.web.http.Controller): + _cp_path = '/base_phone' + + @openerp.addons.web.http.jsonrequest + def click2dial(self, req, phone_number, click2dial_model, click2dial_id): + res = req.session.model('phone.common').click2dial( + phone_number, { + 'click2dial_model': click2dial_model, + 'click2dial_id': click2dial_id, + }) + return res diff --git a/base_phone/i18n/base_phone.pot b/base_phone/i18n/base_phone.pot new file mode 100644 index 000000000..72389ae71 --- /dev/null +++ b/base_phone/i18n/base_phone.pot @@ -0,0 +1,273 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * base_phone +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-08-25 21:28+0000\n" +"PO-Revision-Date: 2014-08-25 21:28+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "Cancel" +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:95 +#, python-format +msgid "Cannot reformat the phone number '%s' to international format. Error message: %s" +msgstr "" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:34 +#, python-format +msgid "Click2dial started" +msgstr "" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:46 +#, python-format +msgid "Click2dial successfull" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Close" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_res_company +msgid "Companies" +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:85 +#, python-format +msgid "Create New Partner" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Create Partner with this Number" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Create or Update a Partner" +msgstr "" + +#. module: base_phone +#: field:number.not.found,current_partner_mobile:0 +msgid "Current Mobile" +msgstr "" + +#. module: base_phone +#: field:number.not.found,current_partner_phone:0 +msgid "Current Phone" +msgstr "" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:31 +#, python-format +msgid "Dial" +msgstr "" + +#. module: base_phone +#: field:number.not.found,e164_number:0 +msgid "E.164 Number" +msgstr "" + +#. module: base_phone +#: help:number.not.found,e164_number:0 +msgid "E.164 equivalent of the calling number." +msgstr "" + +#. module: base_phone +#: view:res.users:0 +msgid "Email Preferences" +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:83 +#: code:addons/base_phone/base_phone.py:94 +#: code:addons/base_phone/wizard/number_not_found.py:99 +#, python-format +msgid "Error:" +msgstr "" + +#. module: base_phone +#: selection:number.not.found,number_type:0 +msgid "Fixed" +msgstr "" + +#. module: base_phone +#: field:number.not.found,number_type:0 +msgid "Fixed/Mobile" +msgstr "" + +#. module: base_phone +#: help:res.company,number_of_digits_to_match_from_end:0 +msgid "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." +msgstr "" + +#. module: base_phone +#: selection:number.not.found,number_type:0 +msgid "Mobile" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number Not Found" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number converted to international format:" +msgstr "" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:47 +#, python-format +msgid "Number dialed:" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_number_not_found +msgid "Number not found" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number not found:" +msgstr "" + +#. module: base_phone +#: field:res.company,number_of_digits_to_match_from_end:0 +msgid "Number of Digits To Match From End" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_res_partner +msgid "Partner" +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:105 +#, python-format +msgid "Partner: %s" +msgstr "" + +#. module: base_phone +#: view:res.company:0 +msgid "Phone" +msgstr "" + +#. module: base_phone +#: model:res.groups,name:base_phone.group_callerid +msgid "Phone CallerID" +msgstr "" + +#. module: base_phone +#: field:reformat.all.phonenumbers,phonenumbers_not_reformatted:0 +msgid "Phone numbers that couldn't be reformatted" +msgstr "" + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "Phone numbers that couldn't be reformatted:" +msgstr "" + +#. module: base_phone +#: model:ir.actions.act_window,name:base_phone.reformat_all_phonenumbers_action +#: model:ir.ui.menu,name:base_phone.reformat_all_phonenumbers_menu +msgid "Reformat Phone Numbers" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_reformat_all_phonenumbers +#: view:reformat.all.phonenumbers:0 +msgid "Reformat all phone numbers" +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:100 +#, python-format +msgid "Select the Partner to Update." +msgstr "" + +#. module: base_phone +#: model:ir.ui.menu,name:base_phone.menu_config_phone +#: view:res.users:0 +msgid "Telephony" +msgstr "" + +#. module: base_phone +#: view:res.users:0 +msgid "Telephony Preferences" +msgstr "" + +#. module: base_phone +#: sql_constraint:res.company:0 +msgid "The value of the field 'Number of Digits To Match From End' must be positive." +msgstr "" + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "This wizard reformats the phone, mobile and fax numbers of all partners in standard international format e.g. +33141981242" +msgstr "" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:35 +#, python-format +msgid "Unhook your ringing phone" +msgstr "" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Update Partner with this Number" +msgstr "" + +#. module: base_phone +#: constraint:res.partner:0 +msgid "You cannot create recursive Partner hierarchies." +msgstr "" + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:84 +#, python-format +msgid "You should set a country on the company '%s'" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_base_phone_installed +msgid "base.phone.installed" +msgstr "" + +#. module: base_phone +#: view:res.partner:0 +msgid "fax" +msgstr "" + +#. module: base_phone +#: view:res.partner:0 +msgid "phone" +msgstr "" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_phone_common +msgid "phone.common" +msgstr "" + diff --git a/base_phone/i18n/fr.po b/base_phone/i18n/fr.po new file mode 100644 index 000000000..648793758 --- /dev/null +++ b/base_phone/i18n/fr.po @@ -0,0 +1,273 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * base_phone +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-08-25 21:29+0000\n" +"PO-Revision-Date: 2014-08-25 21:29+0000\n" +"Last-Translator: Alexis de Lattre \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "Cancel" +msgstr "Annuler" + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:95 +#, python-format +msgid "Cannot reformat the phone number '%s' to international format. Error message: %s" +msgstr "Impossible de reformatter le numéro de téléphone '%s' au format international. Message d'erreur : %s" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:34 +#, python-format +msgid "Click2dial started" +msgstr "Début du Click2dial" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:46 +#, python-format +msgid "Click2dial successfull" +msgstr "Click2dial réussi" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Close" +msgstr "Fermer" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:85 +#, python-format +msgid "Create New Partner" +msgstr "Créer un nouveau partenaire" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Create Partner with this Number" +msgstr "Créer un partenaire avec ce numéro" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Create or Update a Partner" +msgstr "Créer ou mettre à jour un partenaire" + +#. module: base_phone +#: field:number.not.found,current_partner_mobile:0 +msgid "Current Mobile" +msgstr "Portable actuel" + +#. module: base_phone +#: field:number.not.found,current_partner_phone:0 +msgid "Current Phone" +msgstr "Téléphone actuel" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:31 +#, python-format +msgid "Dial" +msgstr "Composer" + +#. module: base_phone +#: field:number.not.found,e164_number:0 +msgid "E.164 Number" +msgstr "Numéro E.164" + +#. module: base_phone +#: help:number.not.found,e164_number:0 +msgid "E.164 equivalent of the calling number." +msgstr "Equivalent au format E.164 du numéro appelant." + +#. module: base_phone +#: view:res.users:0 +msgid "Email Preferences" +msgstr "Préférences de messagerie électronique" + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:83 +#: code:addons/base_phone/base_phone.py:94 +#: code:addons/base_phone/wizard/number_not_found.py:99 +#, python-format +msgid "Error:" +msgstr "Erreur :" + +#. module: base_phone +#: selection:number.not.found,number_type:0 +msgid "Fixed" +msgstr "Fixe" + +#. module: base_phone +#: field:number.not.found,number_type:0 +msgid "Fixed/Mobile" +msgstr "Fixe/Portable" + +#. module: base_phone +#: help:res.company,number_of_digits_to_match_from_end:0 +msgid "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." +msgstr "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." + +#. module: base_phone +#: selection:number.not.found,number_type:0 +msgid "Mobile" +msgstr "Portable" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number Not Found" +msgstr "Numéro introuvable" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number converted to international format:" +msgstr "Numéro converti au format international :" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:47 +#, python-format +msgid "Number dialed:" +msgstr "Numéro composé :" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_number_not_found +msgid "Number not found" +msgstr "Numéro introuvable" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Number not found:" +msgstr "Numéro introuvable :" + +#. module: base_phone +#: field:res.company,number_of_digits_to_match_from_end:0 +msgid "Number of Digits To Match From End" +msgstr "Nombre de chiffres à faire correspondre en partant de la fin" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_res_partner +msgid "Partner" +msgstr "Partenaire" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:105 +#, python-format +msgid "Partner: %s" +msgstr "Partenaire : %s" + +#. module: base_phone +#: view:res.company:0 +msgid "Phone" +msgstr "Téléphone" + +#. module: base_phone +#: model:res.groups,name:base_phone.group_callerid +msgid "Phone CallerID" +msgstr "CallerID du téléphone" + +#. module: base_phone +#: field:reformat.all.phonenumbers,phonenumbers_not_reformatted:0 +msgid "Phone numbers that couldn't be reformatted" +msgstr "Numéros de téléphone qui n'ont pas pu être reformatés" + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "Phone numbers that couldn't be reformatted:" +msgstr "Numéros de téléphone qui n'ont pas pu être reformatés :" + +#. module: base_phone +#: model:ir.actions.act_window,name:base_phone.reformat_all_phonenumbers_action +#: model:ir.ui.menu,name:base_phone.reformat_all_phonenumbers_menu +msgid "Reformat Phone Numbers" +msgstr "Reformate les numéros de téléphone" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_reformat_all_phonenumbers +#: view:reformat.all.phonenumbers:0 +msgid "Reformat all phone numbers" +msgstr "Reformate tous les numéros de téléphone" + +#. module: base_phone +#: code:addons/base_phone/wizard/number_not_found.py:100 +#, python-format +msgid "Select the Partner to Update." +msgstr "Selectionnez le partenaire à mettre à jour." + +#. module: base_phone +#: model:ir.ui.menu,name:base_phone.menu_config_phone +#: view:res.users:0 +msgid "Telephony" +msgstr "Téléphonie" + +#. module: base_phone +#: view:res.users:0 +msgid "Telephony Preferences" +msgstr "Préférences téléphoniques" + +#. module: base_phone +#: sql_constraint:res.company:0 +msgid "The value of the field 'Number of Digits To Match From End' must be positive." +msgstr "The value of the field 'Number of Digits To Match From End' must be positive." + +#. module: base_phone +#: view:reformat.all.phonenumbers:0 +msgid "This wizard reformats the phone, mobile and fax numbers of all partners in standard international format e.g. +33141981242" +msgstr "Cet assistant reformate le numéro de téléphone fixe, portable et le fax de tous les partenaires au format standard international i.e. +33141981242" + +#. module: base_phone +#. openerp-web +#: code:addons/base_phone/static/src/js/phone_widget.js:35 +#, python-format +msgid "Unhook your ringing phone" +msgstr "Décrochez votre téléphone" + +#. module: base_phone +#: view:number.not.found:0 +msgid "Update Partner with this Number" +msgstr "Mettre à jour le partenaire avec ce numéro" + +#. module: base_phone +#: constraint:res.partner:0 +msgid "You cannot create recursive Partner hierarchies." +msgstr "You cannot create recursive Partner hierarchies." + +#. module: base_phone +#: code:addons/base_phone/base_phone.py:84 +#, python-format +msgid "You should set a country on the company '%s'" +msgstr "Vous devez renseigner un pays pour la société '%s'" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_base_phone_installed +msgid "base.phone.installed" +msgstr "base.phone.installed" + +#. module: base_phone +#: view:res.partner:0 +msgid "fax" +msgstr "fax" + +#. module: base_phone +#: view:res.partner:0 +msgid "phone" +msgstr "phone" + +#. module: base_phone +#: model:ir.model,name:base_phone.model_phone_common +msgid "phone.common" +msgstr "phone.common" + diff --git a/base_phone/report_sxw_format.py b/base_phone/report_sxw_format.py new file mode 100644 index 000000000..91a48f78b --- /dev/null +++ b/base_phone/report_sxw_format.py @@ -0,0 +1,63 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Base Phone module for OpenERP +# Copyright (C) 2014 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 (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 . +# +############################################################################## + +from openerp.osv import orm +from openerp.report import report_sxw +import phonenumbers + + +class base_phone_installed(orm.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 diff --git a/base_phone/res_company_view.xml b/base_phone/res_company_view.xml new file mode 100644 index 000000000..5921a4c16 --- /dev/null +++ b/base_phone/res_company_view.xml @@ -0,0 +1,33 @@ + + + + + + + + + base_phone.company.form + res.company + + + + + + + + + phone + + + fax + + + + + + + diff --git a/base_phone/res_partner_view.xml b/base_phone/res_partner_view.xml new file mode 100644 index 000000000..f0a66f66a --- /dev/null +++ b/base_phone/res_partner_view.xml @@ -0,0 +1,50 @@ + + + + + + + + base.phone.res.partner.simplified.form + res.partner + + + + phone + + + phone + + + + + + base.phone.res.partner.form + res.partner + + + + phone + + + phone + + + fax + + + phone + + + phone + + + + + + + diff --git a/base_phone/res_users_view.xml b/base_phone/res_users_view.xml new file mode 100644 index 000000000..99b64c92a --- /dev/null +++ b/base_phone/res_users_view.xml @@ -0,0 +1,40 @@ + + + + + + + + base_phone.res.users.telephony_tab + res.users + + + + + + + + + + + + + + base_phone.user_preferences.view + res.users + + + + + + + + + + + + diff --git a/base_phone/security/ir.model.access.csv b/base_phone/security/ir.model.access.csv new file mode 100644 index 000000000..759334800 --- /dev/null +++ b/base_phone/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +callerid_res_partner_read,Read access on res.partner,base.model_res_partner,group_callerid,1,0,0,0 diff --git a/base_phone/security/phone_security.xml b/base_phone/security/phone_security.xml new file mode 100644 index 000000000..627bdad59 --- /dev/null +++ b/base_phone/security/phone_security.xml @@ -0,0 +1,17 @@ + + + + + + + + + Phone CallerID + + + + diff --git a/base_phone/static/src/js/phone_widget.js b/base_phone/static/src/js/phone_widget.js new file mode 100644 index 000000000..839fe4080 --- /dev/null +++ b/base_phone/static/src/js/phone_widget.js @@ -0,0 +1,105 @@ +/* Base phone module for OpenERP + Copyright (C) 2013-2014 Alexis de Lattre + The licence is in the file __openerp__.py */ + +openerp.base_phone = function (instance) { + + var _t = instance.web._t; + + instance.base_phone.FieldPhone = instance.web.form.FieldChar.extend({ + template: 'FieldPhone', + initialize_content: function() { + this._super(); + var $button = this.$el.find('button'); + $button.click(this.on_button_clicked); + this.setupFocus($button); + }, + render_value: function() { + if (!this.get('effective_readonly')) { + this._super(); + } else { + var self = this; + var phone_num = this.get('value'); + //console.log('BASE_PHONE phone_num = %s', phone_num); + if (phone_num) { + this.$el.find('a.oe_form_uri') + .attr('href', 'tel:' + phone_num) + .text(formatInternational('', phone_num) || ''); + } + if (phone_num && !this.options.dial_button_invisible) { + this.$el.find('#click2dial') + .text(phone_num && _t('Dial') || '') + .on('click', function(ev) { + self.do_notify( + _t('Click2dial started'), + _t('Unhook your ringing phone')); + var arg = { + 'phone_number': phone_num, + 'click2dial_model': self.view.dataset.model, + 'click2dial_id': self.view.datarecord.id}; + self.rpc('/base_phone/click2dial', arg).done(function(r) { + //console.log('Click2dial r=%s', JSON.stringify(r)); + if (r === false) { + self.do_warn("Click2dial failed"); + } else if (typeof r === 'object') { + self.do_notify( + _t('Click2dial successfull'), + _t('Number dialed:') + ' ' + r.dialed_number); + if (r.action_model) { + var context = {}; + if (self.view.dataset.model == 'res.partner') { + context = { + 'partner_id': self.view.datarecord.id}; + } + var action = { + name: r.action_name, + type: 'ir.actions.act_window', + res_model: r.action_model, + view_mode: 'form', + views: [[false, 'form']], + target: 'new', + context: context, + }; + instance.client.action_manager.do_action(action); + } + } + }); + }); + } + } + }, + on_button_clicked: function() { + location.href = 'tel:' + this.get('value'); + } + }); + + instance.web.form.widgets.add('phone', 'instance.base_phone.FieldPhone'); + + instance.base_phone.FieldFax = instance.web.form.FieldChar.extend({ + template: 'FieldFax', + initialize_content: function() { + this._super(); + var $button = this.$el.find('button'); + $button.click(this.on_button_clicked); + this.setupFocus($button); + }, + render_value: function() { + if (!this.get('effective_readonly')) { + this._super(); + } else { + var fax_num = this.get('value'); + if (fax_num) { + this.$el.find('a.oe_form_uri') + .attr('href', 'fax:' + fax_num) + .text(formatInternational('', fax_num) || ''); + } + } + }, + on_button_clicked: function() { + location.href = 'fax:' + this.get('value'); + } + }); + + instance.web.form.widgets.add('fax', 'instance.base_phone.FieldFax'); + +} diff --git a/base_phone/static/src/xml/phone.xml b/base_phone/static/src/xml/phone.xml new file mode 100644 index 000000000..0cd269c43 --- /dev/null +++ b/base_phone/static/src/xml/phone.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + +
+ +
+
+
+
+ + + + + +
+ +
+
+ + + +
diff --git a/base_phone/wizard/__init__.py b/base_phone/wizard/__init__.py new file mode 100644 index 000000000..61f2c9ee8 --- /dev/null +++ b/base_phone/wizard/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Base Phone module for OpenERP +# Copyright (C) 2012-2014 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 (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 . +# +############################################################################## + +from . import reformat_all_phonenumbers +from . import number_not_found diff --git a/base_phone/wizard/number_not_found.py b/base_phone/wizard/number_not_found.py new file mode 100644 index 000000000..2697c43da --- /dev/null +++ b/base_phone/wizard/number_not_found.py @@ -0,0 +1,129 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Asterisk Click2dial module for OpenERP +# Copyright (C) 2010-2013 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 (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 . +# +############################################################################## + +from openerp.osv import orm, fields +from openerp.tools.translate import _ +import logging +import phonenumbers + +_logger = logging.getLogger(__name__) + + +class number_not_found(orm.TransientModel): + _name = "number.not.found" + _description = "Number not found" + + _columns = { + 'calling_number': fields.char( + 'Calling Number', size=64, readonly=True, + help="Phone number of calling party that has been obtained " + "from Asterisk, in the format used by Asterisk (not E.164)."), + 'e164_number': fields.char( + 'E.164 Number', size=64, + help="E.164 equivalent of the calling number."), + 'number_type': fields.selection( + [('phone', 'Fixed'), ('mobile', 'Mobile')], + 'Fixed/Mobile', required=True), + 'to_update_partner_id': fields.many2one( + 'res.partner', 'Partner to Update', + help="Partner on which the phone number will be written"), + 'current_partner_phone': fields.related( + 'to_update_partner_id', 'phone', type='char', + relation='res.partner', string='Current Phone', readonly=True), + 'current_partner_mobile': fields.related( + 'to_update_partner_id', 'mobile', type='char', + relation='res.partner', string='Current Mobile', readonly=True), + } + + def default_get(self, cr, uid, fields_list, context=None): + res = super(number_not_found, self).default_get( + cr, uid, fields_list, context=context) + if not res: + res = {} + if res.get('calling_number'): + convert = self.pool['phone.common']._generic_reformat_phonenumbers( + cr, uid, {'phone': res.get('calling_number')}, context=context) + parsed_num = phonenumbers.parse(convert.get('phone')) + res['e164_number'] = phonenumbers.format_number( + parsed_num, phonenumbers.PhoneNumberFormat.INTERNATIONAL) + number_type = phonenumbers.number_type(parsed_num) + if number_type == 1: + res['number_type'] = 'mobile' + else: + res['number_type'] = 'phone' + return res + + def create_partner(self, cr, uid, ids, context=None): + '''Function called by the related button of the wizard''' + if context is None: + context = {} + wiz = self.browse(cr, uid, ids[0], context=context) + parsed_num = phonenumbers.parse(wiz.e164_number, None) + + context['default_%s' % wiz.number_type] = wiz.e164_number + action = { + 'name': _('Create New Partner'), + 'view_mode': 'form,tree,kanban', + 'res_model': 'res.partner', + 'type': 'ir.actions.act_window', + 'nodestroy': False, + 'target': 'current', + 'context': context, + } + return action + + def update_partner(self, cr, uid, ids, context=None): + wiz = self.browse(cr, uid, ids[0], context=context) + if not wiz.to_update_partner_id: + raise orm.except_orm( + _('Error:'), + _("Select the Partner to Update.")) + self.pool['res.partner'].write( + cr, uid, wiz.to_update_partner_id.id, + {wiz.number_type: wiz.e164_number}, context=context) + action = { + 'name': _('Partner: %s' % wiz.to_update_partner_id.name), + 'type': 'ir.actions.act_window', + 'res_model': 'res.partner', + 'view_mode': 'form,tree,kanban', + 'nodestroy': False, + 'target': 'current', + 'res_id': wiz.to_update_partner_id.id, + 'context': context, + } + return action + + def onchange_to_update_partner( + self, cr, uid, ids, to_update_partner_id, context=None): + res = {'value': {}} + if to_update_partner_id: + to_update_partner = self.pool['res.partner'].browse( + cr, uid, to_update_partner_id, context=context) + res['value'].update({ + 'current_partner_phone': to_update_partner.phone, + 'current_partner_mobile': to_update_partner.mobile, + }) + else: + res['value'].update({ + 'current_partner_phone': False, + 'current_partner_mobile': False, + }) + return res diff --git a/base_phone/wizard/number_not_found_view.xml b/base_phone/wizard/number_not_found_view.xml new file mode 100644 index 000000000..4011c0c52 --- /dev/null +++ b/base_phone/wizard/number_not_found_view.xml @@ -0,0 +1,55 @@ + + + + + + + + number.not.found.form + number.not.found + +
+
+
+ + +