Browse Source

Merge pull request #88 from akretion/9-full-port

[revolution] Port from 8 to 9 + new fields.Phone archi
pull/103/head
Alexis de Lattre 8 years ago
committed by GitHub
parent
commit
be71a0b58e
  1. 6
      .travis.yml
  2. 19
      asterisk_click2dial/__init__.py
  3. 47
      asterisk_click2dial/__openerp__.py
  4. 539
      asterisk_click2dial/asterisk_click2dial.py
  5. 69
      asterisk_click2dial/asterisk_click2dial_demo.xml
  6. 21
      asterisk_click2dial/asterisk_server_view.xml
  7. 37
      asterisk_click2dial/controller.py
  8. 1
      asterisk_click2dial/requirements.txt
  9. 21
      asterisk_click2dial/res_users_view.xml
  10. 6
      asterisk_click2dial/scripts/set_name_agi.py
  11. 5
      asterisk_click2dial/static/src/css/asterisk_click2dial.css
  12. 139
      asterisk_click2dial/static/src/js/asterisk_click2dial.js
  13. 5
      asterisk_click2dial/static/src/xml/asterisk_click2dial.xml
  14. 11
      asterisk_click2dial/web_asterisk_click2dial.xml
  15. 21
      asterisk_click2dial_crm/__init__.py
  16. 48
      asterisk_click2dial_crm/__openerp__.py
  17. 1
      base_phone/README.rst
  18. 5
      base_phone/__init__.py
  19. 7
      base_phone/__openerp__.py
  20. 307
      base_phone/base_phone.py
  21. 137
      base_phone/fields.py
  22. 6
      base_phone/models/__init__.py
  23. 0
      base_phone/models/controller.py
  24. 139
      base_phone/models/phone_common.py
  25. 28
      base_phone/models/res_company.py
  26. 31
      base_phone/models/res_partner.py
  27. 63
      base_phone/report_sxw_format.py
  28. 1397
      base_phone/static/lib/phonenumbers/PhoneFormat.js
  29. 77
      base_phone/static/src/js/phone_widget.js
  30. 48
      base_phone/static/src/xml/phone.xml
  31. 50
      base_phone/test/phonenum.yml
  32. 3
      base_phone/tests/__init__.py
  33. 55
      base_phone/tests/test_phone.py
  34. 9
      base_phone/views/res_company_view.xml
  35. 11
      base_phone/views/res_partner_view.xml
  36. 9
      base_phone/views/res_users_view.xml
  37. 11
      base_phone/web_phone.xml
  38. 68
      base_phone/wizard/number_not_found.py
  39. 12
      base_phone/wizard/number_not_found_view.xml
  40. 62
      base_phone/wizard/reformat_all_phonenumbers.py
  41. 19
      base_phone_popup/__init__.py
  42. 33
      base_phone_popup/__openerp__.py
  43. 68
      base_phone_popup/popup.py
  44. 8
      base_phone_popup/res_users_view.xml
  45. 19
      crm_claim_phone/__init__.py
  46. 47
      crm_claim_phone/crm_claim_phone.py
  47. 9
      crm_claim_phone/crm_claim_view.xml
  48. 19
      crm_phone/__init__.py
  49. 40
      crm_phone/__openerp__.py
  50. 199
      crm_phone/crm_phone.py
  51. 59
      crm_phone/demo/crm_phonecall.xml
  52. 3
      crm_phone/security/ir.model.access.csv
  53. 28
      crm_phone/security/phonecall_security.xml
  54. 41
      crm_phone/test/phonenum.yml
  55. 3
      crm_phone/tests/__init__.py
  56. 46
      crm_phone/tests/test_crm_phone.py
  57. 55
      crm_phone/view/crm_lead.xml
  58. 160
      crm_phone/view/crm_phonecall.xml
  59. 30
      crm_phone/view/res_partner.xml
  60. 9
      crm_phone/view/res_users.xml
  61. 19
      crm_phone/wizard/__init__.py
  62. 47
      crm_phone/wizard/create_crm_phonecall.py
  63. 96
      crm_phone/wizard/number_not_found.py
  64. 20
      crm_phone/wizard/number_not_found_view.xml
  65. 19
      event_phone/__init__.py
  66. 29
      event_phone/__openerp__.py
  67. 47
      event_phone/event_phone.py
  68. 23
      event_phone/event_view.xml
  69. 19
      hr_phone/__init__.py
  70. 30
      hr_phone/__openerp__.py
  71. 48
      hr_phone/hr_phone.py
  72. 10
      hr_phone/hr_view.xml
  73. 1
      hr_phone/security/ir.model.access.csv
  74. 19
      hr_recruitment_phone/__init__.py
  75. 30
      hr_recruitment_phone/__openerp__.py
  76. 62
      hr_recruitment_phone/hr_recruitment_phone.py
  77. 10
      hr_recruitment_phone/hr_recruitment_view.xml
  78. 19
      ovh_telephony_connector/__init__.py
  79. 4
      ovh_telephony_connector/__openerp__.py
  80. 24
      ovh_telephony_connector/ovh_connector.py

6
.travis.yml

@ -32,8 +32,10 @@ install:
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly
- pip install phonenumbers py-Asterisk SOAPpy
- hg clone http://bitbucket.org/juris_malinens/web_action_request -b ${VERSION} ${HOME}/web_action_request # Pull request is not merged in anybox
- hg clone http://bitbucket.org/anybox/bus_enhanced ${HOME}/bus_enhanced
- hg clone http://bitbucket.org/anybox/web_action_request -b ${VERSION} ${HOME}/web_action_request
- if [ -d /home/travis/${ODOO_REPO##*/}-${VERSION}/addons ]; then ln -s ${HOME}/web_action_request/web_action_request /home/travis/${ODOO_REPO##*/}-${VERSION}/addons; fi
- hg clone http://bitbucket.org/anybox/bus_enhanced -b ${VERSION} ${HOME}/bus_enhanced
- if [ -d /home/travis/${ODOO_REPO##*/}-${VERSION}/addons ]; then ln -s ${HOME}/bus_enhanced/bus_enhanced /home/travis/${ODOO_REPO##*/}-${VERSION}/addons; fi
script:
- travis_run_tests

19
asterisk_click2dial/__init__.py

@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Asterisk Click2Dial module for OpenERP
# Copyright (C) 2010-2013 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 asterisk_click2dial
from . import controller

47
asterisk_click2dial/__openerp__.py

@ -1,33 +1,15 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk Click2dial module for OpenERP
# Copyright (C) 2010-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/>.
#
##############################################################################
# -*- 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).
{
'name': 'Asterisk Click2dial',
'version': '8.0.0.4.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Asterisk-OpenERP connector',
'summary': 'Asterisk-Odoo connector',
'description': """
Asterisk-OpenERP connector
Asterisk-Odoo connector
==========================
The technical name of this module is *asterisk_click2dial*, but this module
@ -38,21 +20,21 @@ functionalities:
dial a phone number through Asterisk. This feature is usually known as
*click2dial*. Here is how it works :
* In OpenERP, the user clicks on the *Dial* button next to a phone number
* In Odoo, the user clicks on the *Dial* button next to a phone number
field in the partner view.
* OpenERP connects to the Asterisk Manager Interface and Asterisk makes the
* Odoo connects to the Asterisk Manager Interface and Asterisk makes the
user's phone ring.
* The user answers his own phone (if he doesn't, the process stops here).
* Asterisk dials the phone number found in OpenERP in place of the user.
* Asterisk dials the phone number found in Odoo in place of the user.
* If the remote party answers, the user can talk to his correspondent.
2) It adds the ability to show the name of the calling party on the screen of
your IP phone on incoming phone calls if the presented phone number is
present in the partner/leads/employees/... of OpenERP. Here is how it works:
present in the partner/leads/employees/... of Odoo. Here is how it works:
* On incoming phone calls, the Asterisk dialplan executes an AGI script
"set_name_incoming_timeout.sh".
@ -60,7 +42,7 @@ functionalities:
* The "set_name_incoming_timeout.sh" script calls the "set_name_agi.py"
script with a short timeout.
* The "set_name_agi.py" script will make an XML-RPC request on the OpenERP
* The "set_name_agi.py" script will make an XML-RPC request on the Odoo
server to try to find the name of the person corresponding to the phone
number presented by the calling party.
@ -75,14 +57,14 @@ functionalities:
(next to the Preferences) to get the partner/lead/candidate/registrations
corresponding to the calling party in one click. Here is how it works :
* When the user clicks on the phone icon, OpenERP sends a query to the
* When the user clicks on the phone icon, Odoo sends a query to the
Asterisk Manager Interface to get a list of the current phone calls
* If it finds a phone call involving the user's phone, it gets the phone
number of the calling party
* It searches the phone number of the calling party in the
Partners/Leads/Candidates/Registrations of OpenERP. If a record matches,
Partners/Leads/Candidates/Registrations of Odoo. If a record matches,
it takes you to the form view of this record. If no record matchs, it
opens a wizard which proposes to create a new Partner with the presented
phone number as *Phone* or *Mobile* number or update an existing Partner.
@ -107,6 +89,5 @@ http://www.akretion.com/products-and-services/openerp-asterisk-voip-connector
'qweb': ['static/src/xml/*.xml'],
'css': ['static/src/css/*.css'],
'application': True,
'installable': False,
'active': False,
'installable': True,
}

539
asterisk_click2dial/asterisk_click2dial.py

@ -1,31 +1,14 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk Click2dial module for OpenERP
# Copyright (C) 2010-2013 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.osv import fields, orm
from openerp.tools.translate import _
# -*- 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, fields, api, _
from openerp.exceptions import UserError, ValidationError
import logging
from pprint import pformat
try:
# Lib py-asterisk from http://code.google.com/p/py-asterisk/
# -> pip install py-Asterisk
# pip install py-Asterisk
from Asterisk import Manager
except ImportError:
Manager = None
@ -33,73 +16,66 @@ except ImportError:
_logger = logging.getLogger(__name__)
class asterisk_server(orm.Model):
class AsteriskServer(models.Model):
'''Asterisk server object, stores the parameters of the Asterisk IPBXs'''
_name = "asterisk.server"
_description = "Asterisk Servers"
_columns = {
'name': fields.char('Asterisk Server Name', size=50, required=True),
'active': fields.boolean(
'Active', help="The active field allows you to hide the Asterisk "
"server without deleting it."),
'ip_address': fields.char(
'Asterisk IP address or DNS', size=50, required=True,
help="IP address or DNS name of the Asterisk server."),
'port': fields.integer(
'Port', required=True,
help="TCP port on which the Asterisk Manager Interface listens. "
"Defined in /etc/asterisk/manager.conf on Asterisk."),
'out_prefix': fields.char(
'Out Prefix', size=4, help="Prefix to dial to make outgoing "
"calls. If you don't use a prefix to make outgoing calls, "
"leave empty."),
'login': fields.char(
'AMI Login', size=30, required=True,
help="Login that OpenERP will use to communicate with the "
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
"on your Asterisk server."),
'password': fields.char(
'AMI Password', size=30, required=True,
help="Password that OpenERP will use to communicate with the "
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
"on your Asterisk server."),
'context': fields.char(
'Dialplan Context', size=50, required=True,
help="Asterisk dialplan context from which the calls will be "
"made. Refer to /etc/asterisk/extensions.conf on your Asterisk "
"server."),
'wait_time': fields.integer(
'Wait Time (sec)', required=True,
help="Amount of time (in seconds) Asterisk will try to reach "
"the user's phone before hanging up."),
'extension_priority': fields.integer(
'Extension Priority', required=True,
help="Priority of the extension in the Asterisk dialplan. Refer "
"to /etc/asterisk/extensions.conf on your Asterisk server."),
'alert_info': fields.char(
'Alert-Info SIP Header', size=255,
help="Set Alert-Info header in SIP request to user's IP Phone "
"for the click2dial feature. If empty, the Alert-Info header "
"will not be added. You can use it to have a special ring tone "
"for click2dial (a silent one !) or to activate auto-answer "
"for example."),
'company_id': fields.many2one(
'res.company', 'Company',
help="Company who uses the Asterisk server."),
}
_defaults = {
'active': True,
'port': 5038, # Default AMI port
'extension_priority': 1,
'wait_time': 15,
'company_id': lambda self, cr, uid, context:
self.pool['res.company']._company_default_get(
cr, uid, 'asterisk.server', context=context),
}
def _check_validity(self, cr, uid, ids):
for server in self.browse(cr, uid, ids):
name = fields.Char(string='Asterisk Server Name', required=True)
active = fields.Boolean(
string='Active', default=True)
ip_address = fields.Char(
string='Asterisk IP address or DNS', required=True)
port = fields.Integer(
string='Port', required=True, default=5038,
help="TCP port on which the Asterisk Manager Interface listens. "
"Defined in /etc/asterisk/manager.conf on Asterisk.")
out_prefix = fields.Char(
string='Out Prefix', size=4, help="Prefix to dial to make outgoing "
"calls. If you don't use a prefix to make outgoing calls, "
"leave empty.")
login = fields.Char(
string='AMI Login', required=True,
help="Login that Odoo will use to communicate with the "
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
"on your Asterisk server.")
password = fields.Char(
string='AMI Password', required=True,
help="Password that Odoo will use to communicate with the "
"Asterisk Manager Interface. Refer to /etc/asterisk/manager.conf "
"on your Asterisk server.")
context = fields.Char(
string='Dialplan Context', required=True,
help="Asterisk dialplan context from which the calls will be "
"made. Refer to /etc/asterisk/extensions.conf on your Asterisk "
"server.")
wait_time = fields.Integer(
string='Wait Time', required=True, default=15,
help="Amount of time (in seconds) Asterisk will try to reach "
"the user's phone before hanging up.")
extension_priority = fields.Integer(
string='Extension Priority', required=True, default=1,
help="Priority of the extension in the Asterisk dialplan. Refer "
"to /etc/asterisk/extensions.conf on your Asterisk server.")
alert_info = fields.Char(
string='Alert-Info SIP Header',
help="Set Alert-Info header in SIP request to user's IP Phone "
"for the click2dial feature. If empty, the Alert-Info header "
"will not be added. You can use it to have a special ring tone "
"for click2dial (a silent one !) or to activate auto-answer "
"for example.")
company_id = fields.Many2one(
'res.company', string='Company',
default=lambda self: self.env['res.company']._company_default_get(
'asterisk.server'),
help="Company who uses the Asterisk server.")
@api.multi
@api.constrains(
'out_prefix', 'wait_time', 'extension_priority', 'port',
'context', 'alert_info', 'login', 'password')
def _check_validity(self):
for server in self:
out_prefix = ('Out prefix', server.out_prefix)
dialplan_context = ('Dialplan context', server.context)
alert_info = ('Alert-Info SIP header', server.alert_info)
@ -107,23 +83,19 @@ class asterisk_server(orm.Model):
password = ('AMI password', server.password)
if out_prefix[1] and not out_prefix[1].isdigit():
raise orm.except_orm(
_('Error:'),
raise ValidationError(
_("Only use digits for the '%s' on the Asterisk server "
"'%s'" % (out_prefix[0], server.name)))
if server.wait_time < 1 or server.wait_time > 120:
raise orm.except_orm(
_('Error:'),
raise ValidationError(
_("You should set a 'Wait time' value between 1 and 120 "
"seconds for the Asterisk server '%s'" % server.name))
if server.extension_priority < 1:
raise orm.except_orm(
_('Error:'),
raise ValidationError(
_("The 'extension priority' must be a positive value for "
"the Asterisk server '%s'" % server.name))
if server.port > 65535 or server.port < 1:
raise orm.except_orm(
_('Error:'),
raise ValidationError(
_("You should set a TCP port between 1 and 65535 for the "
"Asterisk server '%s'" % server.name))
for check_str in [dialplan_context, alert_info, login, password]:
@ -131,70 +103,34 @@ class asterisk_server(orm.Model):
try:
check_str[1].encode('ascii')
except UnicodeEncodeError:
raise orm.except_orm(
_('Error:'),
raise ValidationError(
_("The '%s' should only have ASCII caracters for "
"the Asterisk server '%s'"
% (check_str[0], server.name)))
return True
_constraints = [(
_check_validity,
"Error message in raise",
[
'out_prefix', 'wait_time', 'extension_priority', 'port',
'context', 'alert_info', 'login', 'password']
)]
def _get_asterisk_server_from_user(self, cr, uid, context=None):
'''Returns an asterisk.server browse object'''
# We check if the user has an Asterisk server configured
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
if user.asterisk_server_id.id:
ast_server = user.asterisk_server_id
else:
asterisk_server_ids = self.search(
cr, uid, [('company_id', '=', user.company_id.id)],
context=context)
# If the user doesn't have an asterisk server,
# we take the first one of the user's company
if not asterisk_server_ids:
raise orm.except_orm(
_('Error:'),
_("No Asterisk server configured for the company '%s'.")
% user.company_id.name)
else:
ast_server = self.browse(
cr, uid, asterisk_server_ids[0], context=context)
return ast_server
def _connect_to_asterisk(self, cr, uid, context=None):
@api.model
def _connect_to_asterisk(self):
'''
Open the connection to the Asterisk Manager
Returns an instance of the Asterisk Manager
'''
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
ast_server = self._get_asterisk_server_from_user(
cr, uid, context=context)
user = self.env.user
ast_server = user.get_asterisk_server_from_user()
# We check if the current user has a chan type
if not user.asterisk_chan_type:
raise orm.except_orm(
_('Error:'),
raise UserError(
_('No channel type configured for the current user.'))
# We check if the current user has an internal number
if not user.resource:
raise orm.except_orm(
_('Error:'),
raise UserError(
_('No resource name configured for the current user'))
_logger.debug(
"User's phone: %s/%s" % (user.asterisk_chan_type, user.resource))
"User's phone: %s/%s", user.asterisk_chan_type, user.resource)
_logger.debug(
"Asterisk server: %s:%d"
% (ast_server.ip_address, ast_server.port))
"Asterisk server: %s:%d", ast_server.ip_address, ast_server.port)
# Connect to the Asterisk Manager Interface
try:
@ -203,94 +139,97 @@ class asterisk_server(orm.Model):
ast_server.login, ast_server.password)
except Exception, e:
_logger.error(
"Error in the request to the Asterisk Manager Interface %s"
% ast_server.ip_address)
_logger.error("Here is the error message: %s" % e)
raise orm.except_orm(
_('Error:'),
_("Problem in the request from OpenERP to Asterisk. "
"Error in the request to the Asterisk Manager Interface %s",
ast_server.ip_address)
_logger.error("Here is the error message: %s", e)
raise UserError(
_("Problem in the request from Odoo to Asterisk. "
"Here is the error message: %s" % e))
return (user, ast_server, ast_manager)
def test_ami_connection(self, cr, uid, ids, context=None):
assert len(ids) == 1, 'Only 1 ID'
ast_server = self.browse(cr, uid, ids[0], context=context)
@api.multi
def test_ami_connection(self):
self.ensure_one()
ast_manager = False
try:
ast_manager = Manager.Manager(
(ast_server.ip_address, ast_server.port),
ast_server.login,
ast_server.password)
(self.ip_address, self.port),
self.login,
self.password)
except Exception, e:
raise orm.except_orm(
_("Connection Test Failed!"),
_("Here is the error message: %s" % e))
raise UserError(
_("Connection Test Failed! The error message is: %s" % e))
finally:
if ast_manager:
ast_manager.Logoff()
raise orm.except_orm(
_("Connection Test Successfull!"),
_("Odoo can successfully login to the Asterisk Manager "
"Interface."))
def _get_calling_number(self, cr, uid, context=None):
user, ast_server, ast_manager = self._connect_to_asterisk(
cr, uid, context=context)
raise UserError(_(
"Connection Test Successfull! Odoo can successfully login to "
"the Asterisk Manager Interface."))
@api.model
def _get_calling_number_from_channel(self, chan, user):
'''Method designed to be inherited to work with
very old or very new versions of Asterisk'''
sip_account = user.asterisk_chan_type + '/' + user.resource
internal_number = user.internal_number
# 4 = Ring
# 6 = Up
if (
chan.get('ChannelState') in ('4', '6') and (
chan.get('ConnectedLineNum') == internal_number or
chan.get('EffectiveConnectedLineNum') == internal_number or
sip_account in chan.get('BridgedChannel', ''))):
_logger.debug(
"Found a matching Event with channelstate = %s",
chan.get('ChannelState'))
return chan.get('CallerIDNum')
# Compatibility with Asterisk 1.4
if (
chan.get('State') == 'Up' and
sip_account in chan.get('Link', '')):
_logger.debug("Found a matching Event in 'Up' state")
return chan.get('CallerIDNum')
return False
@api.model
def _get_calling_number(self):
user, ast_server, ast_manager = self._connect_to_asterisk()
calling_party_number = False
try:
list_chan = ast_manager.Status()
# from pprint import pprint
# pprint(list_chan)
_logger.debug("Result of Status AMI request: %s", list_chan)
_logger.debug("Result of Status AMI request:")
_logger.debug(pformat(list_chan))
for chan in list_chan.values():
sip_account = user.asterisk_chan_type + '/' + user.resource
# 4 = Ring
if (
chan.get('ChannelState') == '4' and
chan.get('ConnectedLineNum') == user.internal_number):
_logger.debug("Found a matching Event in 'Ring' state")
calling_party_number = chan.get('CallerIDNum')
break
# 6 = Up
if (
chan.get('ChannelState') == '6'
and sip_account in chan.get('BridgedChannel', '')):
_logger.debug("Found a matching Event in 'Up' state")
calling_party_number = chan.get('CallerIDNum')
break
# Compatibility with Asterisk 1.4
if (
chan.get('State') == 'Up'
and sip_account in chan.get('Link', '')):
_logger.debug("Found a matching Event in 'Up' state")
calling_party_number = chan.get('CallerIDNum')
calling_party_number = self._get_calling_number_from_channel(
chan, user)
if calling_party_number:
break
except Exception, e:
_logger.error(
"Error in the Status request to Asterisk server %s"
% ast_server.ip_address)
"Error in the Status request to Asterisk server %s",
ast_server.ip_address)
_logger.error(
"Here are the details of the error: '%s'" % unicode(e))
raise orm.except_orm(
_('Error:'),
"Here are the details of the error: '%s'", unicode(e))
raise UserError(
_("Can't get calling number from Asterisk.\nHere is the "
"error: '%s'" % unicode(e)))
finally:
ast_manager.Logoff()
_logger.debug("Calling party number: '%s'" % calling_party_number)
_logger.debug("Calling party number: '%s'", calling_party_number)
return calling_party_number
def get_record_from_my_channel(self, cr, uid, context=None):
calling_number = self.pool['asterisk.server']._get_calling_number(
cr, uid, context=context)
@api.model
def get_record_from_my_channel(self):
calling_number = self.env['asterisk.server']._get_calling_number()
# calling_number = "0641981246"
if calling_number:
record = self.pool['phone.common'].get_record_from_phone_number(
cr, uid, calling_number, context=context)
record = self.env['phone.common'].get_record_from_phone_number(
calling_number)
if record:
return record
else:
@ -299,75 +238,72 @@ class asterisk_server(orm.Model):
return False
class res_users(orm.Model):
class ResUsers(models.Model):
_inherit = "res.users"
_columns = {
'internal_number': fields.char(
'Internal Number', size=15,
help="User's internal phone number."),
'dial_suffix': fields.char(
'User-specific Dial Suffix', size=15,
help="User-specific dial suffix such as aa=2wb for SCCP "
"auto answer."),
'callerid': fields.char(
'Caller ID', size=50,
help="Caller ID used for the calls initiated by this user."),
# You'd probably think: Asterisk should reuse the callerID of sip.conf!
# But it cannot, cf
# http://lists.digium.com/pipermail/asterisk-users/
# 2012-January/269787.html
'cdraccount': fields.char(
'CDR Account', size=50,
help="Call Detail Record (CDR) account used for billing this "
"user."),
'asterisk_chan_type': fields.selection([
('SIP', 'SIP'),
('IAX2', 'IAX2'),
('DAHDI', 'DAHDI'),
('Zap', 'Zap'),
('Skinny', 'Skinny'),
('MGCP', 'MGCP'),
('mISDN', 'mISDN'),
('H323', 'H323'),
('SCCP', 'SCCP'),
('Local', 'Local'),
], 'Asterisk Channel Type',
help="Asterisk channel type, as used in the Asterisk dialplan. "
"If the user has a regular IP phone, the channel type is 'SIP'."),
'resource': fields.char(
'Resource Name', size=64,
help="Resource name for the channel type selected. For example, "
"if you use 'Dial(SIP/phone1)' in your Asterisk dialplan to ring "
"the SIP phone of this user, then the resource name for this user "
"is 'phone1'. For a SIP phone, the phone number is often used as "
"resource name, but not always."),
'alert_info': fields.char(
'User-specific Alert-Info SIP Header', size=255,
help="Set a user-specific Alert-Info header in SIP request to "
"user's IP Phone for the click2dial feature. If empty, the "
"Alert-Info header will not be added. You can use it to have a "
"special ring tone for click2dial (a silent one !) or to "
"activate auto-answer for example."),
'variable': fields.char(
'User-specific Variable', size=255,
help="Set a user-specific 'Variable' field in the Asterisk "
"Manager Interface 'originate' request for the click2dial "
"feature. If you want to have several variable headers, separate "
"them with '|'."),
'asterisk_server_id': fields.many2one(
'asterisk.server', 'Asterisk Server',
help="Asterisk server on which the user's phone is connected. "
"If you leave this field empty, it will use the first Asterisk "
"server of the user's company."),
}
_defaults = {
'asterisk_chan_type': 'SIP',
}
def _check_validity(self, cr, uid, ids):
for user in self.browse(cr, uid, ids):
internal_number = fields.Char(
string='Internal Number', copy=False,
help="User's internal phone number.")
dial_suffix = fields.Char(
string='User-specific Dial Suffix',
help="User-specific dial suffix such as aa=2wb for SCCP auto answer.")
callerid = fields.Char(
string='Caller ID', copy=False,
help="Caller ID used for the calls initiated by this user.")
# You'd probably think: Asterisk should reuse the callerID of sip.conf!
# But it cannot, cf
# http://lists.digium.com/pipermail/asterisk-users/
# 2012-January/269787.html
cdraccount = fields.Char(
string='CDR Account',
help="Call Detail Record (CDR) account used for billing this user.")
asterisk_chan_type = fields.Selection([
('SIP', 'SIP'),
('IAX2', 'IAX2'),
('DAHDI', 'DAHDI'),
('Zap', 'Zap'),
('Skinny', 'Skinny'),
('MGCP', 'MGCP'),
('mISDN', 'mISDN'),
('H323', 'H323'),
('SCCP', 'SCCP'),
# Local works for click2dial, but it won't work in
# _get_calling_number() when trying to identify the
# channel of the user, so it's better not to propose it
# ('Local', 'Local'),
], string='Asterisk Channel Type', default='SIP',
help="Asterisk channel type, as used in the Asterisk dialplan. "
"If the user has a regular IP phone, the channel type is 'SIP'.")
resource = fields.Char(
string='Resource Name', copy=False,
help="Resource name for the channel type selected. For example, "
"if you use 'Dial(SIP/phone1)' in your Asterisk dialplan to ring "
"the SIP phone of this user, then the resource name for this user "
"is 'phone1'. For a SIP phone, the phone number is often used as "
"resource name, but not always.")
alert_info = fields.Char(
string='User-specific Alert-Info SIP Header',
help="Set a user-specific Alert-Info header in SIP request to "
"user's IP Phone for the click2dial feature. If empty, the "
"Alert-Info header will not be added. You can use it to have a "
"special ring tone for click2dial (a silent one !) or to "
"activate auto-answer for example.")
variable = fields.Char(
string='User-specific Variable',
help="Set a user-specific 'Variable' field in the Asterisk "
"Manager Interface 'originate' request for the click2dial "
"feature. If you want to have several variable headers, separate "
"them with '|'.")
asterisk_server_id = fields.Many2one(
'asterisk.server', string='Asterisk Server',
help="Asterisk server on which the user's phone is connected. "
"If you leave this field empty, it will use the first Asterisk "
"server of the user's company.")
@api.multi
@api.constrains('resource', 'internal_number', 'callerid')
def _check_validity(self):
for user in self:
strings_to_check = [
(_('Resource Name'), user.resource),
(_('Internal Number'), user.internal_number),
@ -378,47 +314,53 @@ class res_users(orm.Model):
try:
check_string[1].encode('ascii')
except UnicodeEncodeError:
raise orm.except_orm(
_('Error:'),
_("The '%s' for the user '%s' should only have "
"ASCII caracters")
raise ValidationError(_(
"The '%s' for the user '%s' should only have "
"ASCII caracters")
% (check_string[0], user.name))
return True
_constraints = [(
_check_validity,
"Error message in raise",
['resource', 'internal_number', 'callerid']
)]
@api.multi
def get_asterisk_server_from_user(self):
'''Returns an asterisk.server recordset'''
self.ensure_one()
# We check if the user has an Asterisk server configured
if self.asterisk_server_id:
ast_server = self.asterisk_server_id
else:
asterisk_servers = self.env['asterisk.server'].search(
[('company_id', '=', self.company_id.id)])
# If the user doesn't have an asterisk server,
# we take the first one of the user's company
if not asterisk_servers:
raise UserError(
_("No Asterisk server configured for the company '%s'.")
% self.company_id.name)
else:
ast_server = asterisk_servers[0]
return ast_server
class PhoneCommon(orm.AbstractModel):
class PhoneCommon(models.AbstractModel):
_inherit = 'phone.common'
def click2dial(self, cr, uid, erp_number, context=None):
res = super(PhoneCommon, self).click2dial(
cr, uid, erp_number, context=context)
@api.model
def click2dial(self, erp_number):
res = super(PhoneCommon, self).click2dial(erp_number)
if not erp_number:
raise orm.except_orm(
_('Error:'),
_('Missing phone number'))
raise UserError(_('Missing phone number'))
user, ast_server, ast_manager = \
self.pool['asterisk.server']._connect_to_asterisk(
cr, uid, context=context)
ast_number = self.convert_to_dial_number(
cr, uid, erp_number, context=context)
self.env['asterisk.server']._connect_to_asterisk()
ast_number = self.convert_to_dial_number(erp_number)
# Add 'out prefix'
if ast_server.out_prefix:
_logger.debug('Out prefix = %s' % ast_server.out_prefix)
_logger.debug('Out prefix = %s', ast_server.out_prefix)
ast_number = '%s%s' % (ast_server.out_prefix, ast_number)
_logger.debug('Number to be sent to Asterisk = %s' % ast_number)
_logger.debug('Number to be sent to Asterisk = %s', ast_number)
# The user should have a CallerID
if not user.callerid:
raise orm.except_orm(
_('Error:'),
_('No callerID configured for the current user'))
raise UserError(_('No callerID configured for the current user'))
variable = []
if user.asterisk_chan_type == 'SIP':
@ -441,19 +383,18 @@ class PhoneCommon(orm.AbstractModel):
channel,
context=ast_server.context,
extension=ast_number,
priority=str(ast_server.extension_priority),
timeout=str(ast_server.wait_time * 1000),
priority=unicode(ast_server.extension_priority),
timeout=unicode(ast_server.wait_time * 1000),
caller_id=user.callerid,
account=user.cdraccount,
variable=variable)
except Exception, e:
_logger.error(
"Error in the Originate request to Asterisk server %s"
% ast_server.ip_address)
"Error in the Originate request to Asterisk server %s",
ast_server.ip_address)
_logger.error(
"Here are the details of the error: '%s'" % unicode(e))
raise orm.except_orm(
_('Error:'),
"Here are the details of the error: '%s'", unicode(e))
raise UserError(
_("Click to dial with Asterisk failed.\nHere is the error: "
"'%s'")
% unicode(e))

69
asterisk_click2dial/asterisk_click2dial_demo.xml

@ -1,38 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Asterisk Click2dial module for OpenERP
Copyright (C) 2010-2012 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
Demo data for the click2dial module
© 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<data noupdate="1">
<record id="demo_ast_server" model="asterisk.server">
<field name="name">Akretion Asterisk IPBX</field>
<field name="ip_address">asterisk.akretion.com</field>
<field name="login">click2dial</field>
<field name="password">mypassword</field>
<field name="context">from-internal</field>
<field name="alert_info">info=&lt;Bellcore-dr5&gt;</field>
<field name="company_id" ref="base.main_company" />
</record>
<record id="base.user_root" model="res.users">
<field name="internal_number">11</field>
<field name="resource">11</field>
<field name="callerid">Administrator &lt;0141981242&gt;</field>
<field name="asterisk_server_id" ref="demo_ast_server"/>
</record>
<record id="base.user_demo" model="res.users">
<field name="internal_number">12</field>
<field name="resource">12</field>
<field name="callerid">Demo user &lt;0141984212&gt;</field>
<field name="asterisk_server_id" ref="demo_ast_server"/>
</record>
<record id="base.main_partner" model="res.partner">
<field name="country_id" ref="base.fr"/>
</record>
</data>
</openerp>
<odoo>
<data noupdate="1">
<record id="demo_ast_server" model="asterisk.server">
<field name="name">Akretion Asterisk IPBX</field>
<field name="ip_address">asterisk.akretion.com</field>
<field name="login">odoo</field>
<field name="password">mypassword</field>
<field name="context">from-internal</field>
<field name="alert_info">info=&lt;Bellcore-dr5&gt;</field>
<field name="company_id" ref="base.main_company" />
</record>
<record id="base.user_root" model="res.users">
<field name="internal_number">11</field>
<field name="resource">11</field>
<field name="callerid">Administrator &lt;0141981242&gt;</field>
<field name="asterisk_server_id" ref="demo_ast_server"/>
</record>
<record id="base.user_demo" model="res.users">
<field name="internal_number">12</field>
<field name="resource">12</field>
<field name="callerid">Demo user &lt;0141984212&gt;</field>
<field name="asterisk_server_id" ref="demo_ast_server"/>
</record>
<record id="base.main_partner" model="res.partner">
<field name="country_id" ref="base.fr"/>
</record>
</data>
</odoo>

21
asterisk_click2dial/asterisk_server_view.xml

@ -1,14 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Asterisk Click2dial module for OpenERP
Copyright (C) 2010-2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<!-- Search view for asterisk.server -->
<record id="view_asterisk_server_search" model="ir.ui.view">
<field name="name">asterisk.server.search</field>
<field name="model">asterisk.server</field>
@ -20,12 +18,11 @@
</field>
</record>
<!-- Form view for asterisk.server -->
<record id="view_asterisk_server_form" model="ir.ui.view">
<field name="name">asterisk.server.form</field>
<field name="model">asterisk.server</field>
<field name="arch" type="xml">
<form string="Asterisk Servers" version="7.0">
<form string="Asterisk Servers">
<sheet>
<div class="oe_title">
<label for="name" string="Server Name" class="oe_edit_only"/>
@ -52,14 +49,17 @@
<field name="extension_priority" />
<field name="out_prefix" />
<field name="alert_info" />
<field name="wait_time" />
<label for="wait_time"/>
<div>
<field name="wait_time" class="oe_inline"/>
<label string=" seconds" class="oe_inline"/>
</div>
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view for asterisk.server -->
<record id="view_asterisk_server_tree" model="ir.ui.view">
<field name="name">asterisk.server.tree</field>
<field name="model">asterisk.server</field>
@ -73,7 +73,6 @@
</field>
</record>
<!-- Action for asterisk.server -->
<record id="action_asterisk_server" model="ir.actions.act_window">
<field name="name">Asterisk Servers</field>
<field name="res_model">asterisk.server</field>
@ -85,4 +84,4 @@
</data>
</openerp>
</odoo>

37
asterisk_click2dial/controller.py

@ -1,31 +1,16 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# © 2015-2016 Juris Malinens (port to v9)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import openerp
from openerp import http
class AsteriskClick2dialController(openerp.addons.web.http.Controller):
_cp_path = '/asterisk_click2dial'
class AsteriskClick2dialController(http.Controller):
@openerp.addons.web.http.jsonrequest
def get_record_from_my_channel(self, req):
res = req.session.model('asterisk.server').get_record_from_my_channel()
@http.route(
'/asterisk_click2dial/get_record_from_my_channel/',
type='json', auth='public')
def get_record_from_my_channel(self, **kw):
res = http.request.env['asterisk.server'].get_record_from_my_channel()
return res

1
asterisk_click2dial/requirements.txt

@ -1,2 +1 @@
phonenumbers
py-Asterisk

21
asterisk_click2dial/res_users_view.xml

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Asterisk Click2dial module for OpenERP
Copyright (C) 2010-2013 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
Inherit res_users view to add the click2dial-related fields
-->
<openerp>
<odoo>
<data>
<record id="view_users_form" model="ir.ui.view">
@ -36,5 +35,17 @@
</field>
</record>
<record id="view_users_form_simple_modif" model="ir.ui.view">
<field name="name">asterisk_click2dial.users.preferences.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base_phone.view_users_form_simple_modif"/>
<field name="arch" type="xml">
<group name="phone" position="attributes">
<attribute name="invisible">0</attribute>
</group>
</field>
</record>
</data>
</openerp>
</odoo>

6
asterisk_click2dial/scripts/set_name_agi.py

@ -1,6 +1,6 @@
#! /usr/bin/python
# -*- encoding: utf-8 -*-
# Copyright (C) 2010-2015 Alexis de Lattre <alexis.delattre@akretion.com>
# -*- coding: utf-8 -*-
# © 2010-2016 Akretion (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
@ -104,7 +104,7 @@ __version__ = "0.6"
# Name that will be displayed if there is no match
# and no geolocalisation. Set it to False if you don't want
# to have a 'not_found_name' when nothing is found
not_found_name = "Not in Odoo"
not_found_name = False
# Define command line options
options = [

5
asterisk_click2dial/static/src/css/asterisk_click2dial.css

@ -1,7 +1,6 @@
/*
Asterisk Click2dial module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
*/
.openerp .oe_topbar_item.oe_topbar_open_caller{

139
asterisk_click2dial/static/src/js/asterisk_click2dial.js

@ -1,73 +1,96 @@
/* Asterisk_click2dial module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py */
/* © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
© 2015-2016 Juris Malinens (port to v9)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
openerp.asterisk_click2dial = function (instance) {
odoo.define('asterisk_click2dial.click2dial', function (require) {
"use strict";
var _t = instance.web._t;
var UserMenu = require('web.UserMenu');
var WebClient = require('web.WebClient');
var web_client = require('web.web_client');
var Widget = require('web.Widget');
var core = require('web.core');
var _t = core._t;
instance.web.OpenCaller = instance.web.Widget.extend({
template:'asterisk_click2dial.OpenCaller',
var click2dial = {};
start: function () {
this.$('#asterisk-open-caller').on(
'click', this.on_open_caller);
this._super();
},
click2dial.OpenCaller = Widget.extend({
template: 'asterisk_click2dial.OpenCaller',
start: function () {
this.$('#asterisk-open-caller').on(
'click', this.on_open_caller);
this._super();
},
on_open_caller: function (event) {
event.stopPropagation();
var self = this;
self.rpc('/asterisk_click2dial/get_record_from_my_channel', {}).done(function(r) {
// console.log('RESULT RPC r='+r);
// console.log('RESULT RPC type r='+typeof r);
// console.log('RESULT RPC isNaN r='+isNaN(r));
if (r === false) {
self.do_warn(
_t('Failure'),
_t('Problem in the connection to Asterisk'),
false);
}
else if (typeof r == 'string' && isNaN(r)) {
self.do_warn(
r,
_t('The calling number is not a phone number!'),
false);
}
else if (typeof r == 'string') {
var action = {
name: _t('Number Not Found'),
type: 'ir.actions.act_window',
res_model: 'number.not.found',
view_mode: 'form',
views: [[false, 'form']],
target: 'new',
context: {'default_calling_number': r},
};
web_client.action_manager.do_action(action);
on_open_caller: function (event) {
event.stopPropagation();
var self = this;
self.rpc('/asterisk_click2dial/get_record_from_my_channel', {}).done(function(r) {
// console.log('RESULT RPC r='+r);
// console.log('RESULT RPC type r='+typeof r);
if (r === false) {
self.do_notify(
_t('Failure'),
_t('Problem in the connection to Asterisk'));
}
else if (typeof r == 'string') {
var action = {
name: _t('Number Not Found'),
type: 'ir.actions.act_window',
res_model: 'number.not.found',
view_mode: 'form',
views: [[false, 'form']],
target: 'new',
context: {'default_calling_number': r},
};
instance.client.action_manager.do_action(action);
}
else if (typeof r == 'object' && r.length == 3) {
self.do_notify( // Not working
_t('Success'),
_t('Moving to %s ID %d', r[0], r[1]));
var action = {
type: 'ir.actions.act_window',
res_model: r[0],
res_id: r[1],
view_mode: 'form,tree',
views: [[false, 'form']],
target: 'current',
context: {},
};
instance.client.action_manager.do_action(action);
}
});
},
else if (typeof r == 'object' && r.length == 3) {
self.do_notify( // Not working
_t('Success'),
_t('Moving to %s ID %d', r[0], r[1]),
false);
var action = {
type: 'ir.actions.act_window',
res_model: r[0],
res_id: r[1],
view_mode: 'form,tree',
views: [[false, 'form']],
/* If you want to make it work with the 'web' module
of Odoo Enterprise edition, you have to change the line
target: 'current',
to:
target: 'new',
If you want to use target: 'current', with web/enterprise,
you have to reload the Web page just after */
target: 'current',
context: {},
};
web_client.action_manager.do_action(action);
}
});
},
});
instance.web.UserMenu.include({
UserMenu.include({
do_update: function(){
this._super.apply(this, arguments);
this.update_promise.then(function() {
var asterisk_button = new instance.web.OpenCaller();
asterisk_button.appendTo(instance.webclient.$el.find('.oe_systray'));
var asterisk_button = new click2dial.OpenCaller();
// attach the phone logo/button to the systray
asterisk_button.appendTo($('.oe_systray'));
});
},
});
};
});

5
asterisk_click2dial/static/src/xml/asterisk_click2dial.xml

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Asterisk Click2dial module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<template>

11
asterisk_click2dial/web_asterisk_click2dial.xml

@ -1,14 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Asterisk Click2dial module for OpenERP/Odoo
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<template id="assets_backend" name="asterisk_click2dial assets"
inherit_id="web.assets_backend">
<xpath expr="." position="inside">
@ -17,6 +15,5 @@
</xpath>
</template>
</data>
</openerp>
</odoo>

21
asterisk_click2dial_crm/__init__.py

@ -1,21 +0,0 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk click2dial CRM module for 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/>.
#
##############################################################################

48
asterisk_click2dial_crm/__openerp__.py

@ -1,48 +0,0 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Asterisk click2dial CRM module for 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/>.
#
##############################################################################
{
"name": "Asterisk Click2dial CRM",
"version": "8.0.0.1.0",
"author": "Akretion,Odoo Community Association (OCA)",
"website": "http://www.akretion.com",
"license": "AGPL-3",
"category": "Phone",
"description": """
Asterisk Click2dial CRM
=======================
This module is *EMPTY* ; so you should uninstall it now.
The code that used to be in this module has been moved to the module
*crm_phone* that is available in the same GitHub repository
https://github.com/OCA/connector-telephony
This module will be removed from the repository in the near future.
""",
"depends": [
'asterisk_click2dial',
'crm_phone',
],
"data": [],
'installable': False,
"application": False,
}

1
base_phone/README.rst

@ -81,6 +81,7 @@ Contributors
------------
* Alexis de Lattre <alexis.delattre@akretion.com>
* Sébastien Beau <sebastien.beau@akretion.com>
Maintainer
----------

5
base_phone/__init__.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from . import base_phone
from . import fields
from . import models
from . import wizard
from . import report_sxw_format
from . import controller

7
base_phone/__openerp__.py

@ -33,15 +33,14 @@
'data': [
'security/phone_security.xml',
'security/ir.model.access.csv',
'res_partner_view.xml',
'res_company_view.xml',
'res_users_view.xml',
'views/res_partner_view.xml',
'views/res_company_view.xml',
'views/res_users_view.xml',
'wizard/reformat_all_phonenumbers_view.xml',
'wizard/number_not_found_view.xml',
'web_phone.xml',
],
'qweb': ['static/src/xml/*.xml'],
'test': ['test/phonenum.yml'],
'images': [],
'installable': True,
}

307
base_phone/base_phone.py

@ -1,307 +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):
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(
int(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."),
]

137
base_phone/fields.py

@ -0,0 +1,137 @@
# -*- 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)
no_break_space = u'\u00A0'
res = res.replace(' ', no_break_space)
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]
if partner_key and not country:
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()
if loc_vals[field]:
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.get(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

6
base_phone/models/__init__.py

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import controller
from . import res_company
from . import res_partner
from . import phone_common

0
base_phone/controller.py → base_phone/models/controller.py

139
base_phone/models/phone_common.py

@ -0,0 +1,139 @@
# -*- 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()
for obj_dict in sorted_phonemodels:
obj = obj_dict['object']
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_dict['fields']:
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):
phoneobj = []
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)):
phoneobj.append((senv, senv._phone_name_sequence))
phoneobj_sorted = sorted(phoneobj, key=lambda element: element[1])
res = []
for (obj, prio) in phoneobj_sorted:
entry = {'object': obj, 'fields': []}
for field in obj._fields:
if isinstance(obj._fields[field], Phone):
entry['fields'].append(field)
res.append(entry)
# [{'fields': ['fax', 'phone', 'mobile'], 'object': res.partner()},
# {'fields': ['fax', 'phone', 'mobile'], 'object': crm.lead()}]
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 International 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

28
base_phone/models/res_company.py

@ -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.")]

31
base_phone/models/res_partner.py

@ -0,0 +1,31 @@
# -*- 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='parent_id')
mobile = fields.Phone(
country_field='country_id', partner_field='parent_id')
fax = fields.Phone(country_field='country_id', partner_field='parent_id')
@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()

63
base_phone/report_sxw_format.py

@ -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

77
base_phone/static/src/js/phone_widget.js

@ -1,5 +1,5 @@
/* Base phone module for Odoo
Copyright (C) 2013-2015 Alexis de Lattre <alexis@via.ecp.fr>
Copyright (C) 2013-2016 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py */
odoo.define('base_phone.phone_widget', function (require) {
@ -7,9 +7,9 @@ odoo.define('base_phone.phone_widget', function (require) {
var core = require('web.core');
var formwidgets = require('web.form_widgets');
var web_client = require('web.web_client');
var _t = core._t;
var FieldPhone = formwidgets.FieldChar.extend({
template: 'FieldPhone',
initialize_content: function() {
@ -25,21 +25,18 @@ var FieldPhone = formwidgets.FieldChar.extend({
var self = this;
var phone_num = this.get('value');
// console.log('BASE_PHONE phone_num = %s', phone_num);
var href = '#';
var href_text = '';
var raw_phone_num = '';
if (phone_num) {
href = 'tel:' + phone_num;
href_text = formatInternational('', phone_num) || '';
// remove non-breaking-space
raw_phone_num = phone_num.replace(/ /g, '');
raw_phone_num = raw_phone_num.replace(/-/g, '');
this.$el.find('a.oe_form_uri').attr('href', 'tel:' + raw_phone_num).text(phone_num);
}
if (href_text) {
this.$el.find('a.oe_form_uri').attr('href', href).text(href_text);
this.$el.find('span.oe_form_char_content').text('');
} else {
else {
this.$el.find('a.oe_form_uri').attr('href', '').text('');
this.$el.find('span.oe_form_char_content').text(phone_num || '');
}
var click2dial_text = '';
if (href_text && !this.options.dial_button_invisible) {
if (phone_num && !this.options.dial_button_invisible) {
click2dial_text = _t('Dial');
}
this.$el.find('#click2dial').off('click');
@ -48,9 +45,10 @@ var FieldPhone = formwidgets.FieldChar.extend({
.on('click', function(ev) {
self.do_notify(
_t('Click2dial started'),
_t('Unhook your ringing phone'));
_t('Unhook your ringing phone'),
false);
var arg = {
'phone_number': phone_num,
'phone_number': raw_phone_num,
'click2dial_model': self.view.dataset.model,
'click2dial_id': self.view.datarecord.id};
self.rpc('/base_phone/click2dial', arg).done(function(r) {
@ -60,12 +58,13 @@ var FieldPhone = formwidgets.FieldChar.extend({
} else if (typeof r === 'object') {
self.do_notify(
_t('Click2dial successfull'),
_t('Number dialed:') + ' ' + r.dialed_number);
_t('Number dialed:') + ' ' + r.dialed_number,
false);
if (r.action_model) {
var context = {
'click2dial_model': self.view.dataset.model,
'click2dial_id': self.view.datarecord.id,
'phone_number': phone_num,
'phone_number': raw_phone_num,
};
var action = {
name: r.action_name,
@ -76,7 +75,7 @@ var FieldPhone = formwidgets.FieldChar.extend({
target: 'new',
context: context,
};
formwidgets.client.action_manager.do_action(action);
web_client.action_manager.do_action(action);
}
}
});
@ -103,18 +102,13 @@ var FieldFax = formwidgets.FieldChar.extend({
} else {
var fax_num = this.get('value');
// console.log('BASE_PHONE fax_num = %s', fax_num);
var href = '#';
var href_text = '';
if (fax_num) {
href = 'fax:' + fax_num;
href_text = formatInternational('', fax_num) || '';
var raw_fax_num = fax_num.replace(/ /g, '');
raw_fax_num = raw_fax_num.replace(/-/g, '');
this.$el.find('a').attr('href', 'fax:' + raw_fax_num).text(fax_num);
}
if (href_text) {
this.$el.find('a.oe_form_uri').attr('href', href).text(href_text);
this.$el.find('span.oe_form_char_content').text('');
} else {
this.$el.find('a.oe_form_uri').attr('href', '').text('');
this.$el.find('span.oe_form_char_content').text(fax_num || '');
else {
this.$el.find('a').attr('href', '').text('');
}
}
},
@ -132,27 +126,28 @@ if(!core.form_widget_registry.get('phone')){
core.form_widget_registry.add('phone', FieldPhone);
}
/*
var Column = require('web.list_view.js');
var ColumnPhone = Column.extend({
var treewidgets = require('web.ListView');
var ColumnPhone = treewidgets.Column.extend({
// ability to add widget="phone" in TREE view
_format: function(row_data, options) {
console.log('row_data=' + row_data);
console.log('options=');
console.log(options);
var value = row_data[this.id].value;
if (value && this.widget === 'phone') {
readable_space = formatInternational('', value);
readable_no_break_space = readable_space.replace(/\s/g, ' ');
return readable_no_break_space;
var phone_num = row_data[this.id].value;
if (phone_num) {
var raw_phone_num = phone_num.replace(/ /g, '');
raw_phone_num = raw_phone_num.replace(/-/g, '');
return _.template("<a href='tel:<%-href%>'><%-text%></a>")({
href: raw_phone_num,
text: phone_num
});
}
console.log('return normal');
return this._super(row_data, options);
}
});
core.list_widget_registry.add('field.phone', ColumnPhone);
*/
if (!core.list_widget_registry.get('phone')) {
core.list_widget_registry.add('field.phone', ColumnPhone);
}
});

48
base_phone/static/src/xml/phone.xml

@ -7,48 +7,16 @@
<templates id="template" xml:space="preserve">
<t t-name="FieldPhone">
<span class="oe_form_field oe_form_field_email" t-att-style="widget.node.attrs.style">
<t t-if="widget.get('effective_readonly')">
<a href="#" class="oe_form_uri"/><span class="oe_form_char_content"/>
<!--
You should add the following "<a .. />" in your IPBX-specific
module (such as asterisk_click2dial or ovh_telephony_connector)
via <t t-extend="FieldPhone">
<a id="click2dial" href="#" class="oe_bold"/>
-->
</t>
<t t-if="!widget.get('effective_readonly')">
<div>
<input type="text"
t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
t-att-maxlength="widget.field.size"
/>
</div>
</t>
</span>
<t t-name="FieldPhone" t-extend="FieldEmail">
<t t-jquery="span:first">
this.removeClass('oe_form_field_email').addClass('oe_form_field_phone');
</t>
</t>
<t t-name="FieldFax">
<span class="oe_form_field oe_form_field_email" t-att-style="widget.node.attrs.style">
<t t-if="widget.get('effective_readonly')">
<a href="#" class="oe_form_uri"/><span class="oe_form_char_content"/>
</t>
<t t-if="!widget.get('effective_readonly')">
<div>
<input type="text"
t-att-id="widget.id_for_label"
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
t-att-maxlength="widget.field.size"
/>
</div>
</t>
</span>
<t t-name="FieldFax" t-extend="FieldEmail">
<t t-jquery="span:first">
this.removeClass('oe_form_field_email').addClass('oe_form_field_fax');
</t>
</t>
</templates>

50
base_phone/test/phonenum.yml

@ -1,50 +0,0 @@
-
Write country = FR for the main company
-
!record {model: res.company, id: base.main_company}:
country_id: base.fr
-
Write french phone numbers in national format
-
!record {model: res.partner, id: partner1}:
name: Pierre Paillet
mobile: 06 42 77 42 66
fax: (0) 1 45 42 12 42
-
Write swiss phone numbers in international format
-
!record {model: res.partner, id: partner2}:
name: Joël Grand-Guillaume
parent_id: base.res_partner_12
phone: +41 21 619 10 10
mobile: +41 79 606 42 42
-
Write invalid phone number
-
!record {model: res.partner, id: partner3}:
name: Jean Badphone
phone: 42
-
Check that valid phone numbers have been converted to E.164
-
!python {model: res.partner}: |
partner1 = self.browse(cr, uid, ref('partner1'), context=context)
assert partner1.mobile == '+33642774266', 'Mobile number not written in E.164 format (partner1)'
assert partner1.fax == '+33145421242', 'Fax number not written in E.164 format (partner1)'
partner2 = self.browse(cr, uid, ref('partner2'), context=context)
assert partner2.phone == '+41216191010', 'Phone number not written in E.164 format (partner2)'
assert partner2.mobile == '+41796064242', 'Mobile number not written in E.164 format (partner2)'
-
Check that invalid phone numbers are kept unchanged
-
!python {model: res.partner}: |
partner3 = self.browse(cr, uid, ref('partner3'), context=context)
assert partner3.phone == '42', 'Invalid phone numbers should not be changed'
-
Get name from phone number
-
!python {model: phone.common}: |
name = self.get_name_from_phone_number(cr, uid, '0642774266')
assert name == 'Pierre Paillet', 'Wrong result for get_name_from_phone_number'
name2 = self.get_name_from_phone_number(cr, uid, '0041216191010')
assert name2 == u'Joël Grand-Guillaume (Camptocamp)', 'Wrong result for get_name_from_phone_number (partner2)'

3
base_phone/tests/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_phone

55
base_phone/tests/test_phone.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion France (Alexis de Lattre <alexis.delattre@akretion.com>)
from openerp.tests.common import TransactionCase
class TestPhone(TransactionCase):
def test_phone(self):
company = self.env.ref('base.main_company')
company.country_id = self.env.ref('base.fr').id
rpo = self.env['res.partner']
# Create an existing partner without country
partner1 = rpo.create({
'name': u'Pierre Paillet',
'phone': '04-72-08-87-32',
'mobile': '06.42.77.42.66',
'fax': '(0) 1 45 42 12 42',
})
self.assertEquals(partner1.phone, u'+33 4 72 08 87 32')
self.assertEquals(partner1.mobile, u'+33 6 42 77 42 66')
self.assertEquals(partner1.fax, u'+33 1 45 42 12 42')
# Create a partner with country
self.env.ref('base.res_partner_12').country_id =\
self.env.ref('base.ch').id
partner2 = rpo.create({
'name': u'Joël Grand-Guillaume',
'parent_id': self.env.ref('base.res_partner_12').id,
'use_parent_address': True,
'phone': '(0) 21 619 10 10',
'mobile': '(0) 79 606 42 42',
})
self.assertEquals(partner2.country_id, self.env.ref('base.ch'))
self.assertEquals(partner2.phone, u'+41 21 619 10 10')
self.assertEquals(partner2.mobile, u'+41 79 606 42 42')
# Write on an existing partner
agrolait = self.env.ref('base.res_partner_2')
self.assertEquals(agrolait.country_id, self.env.ref('base.be'))
agrolait.write({'phone': '(0) 2 391 43 74'})
self.assertEquals(agrolait.phone, u'+32 2 391 43 74')
# Write on an existing partner with country at the same time
agrolait.write({
'fax': '04 72 89 32 43',
'country_id': self.env.ref('base.fr').id,
})
self.assertEquals(agrolait.fax, u'+33 4 72 89 32 43')
# Write an invalid phone number
partner2.fax = '42'
self.assertEquals(partner2.fax, u'42')
# Test get_name_from_phone_number
pco = self.env['phone.common']
name = pco.get_name_from_phone_number('0642774266')
self.assertEquals(name, 'Pierre Paillet')
name2 = pco.get_name_from_phone_number('0041216191010')
self.assertEquals(name2, u'Joël Grand-Guillaume (Camptocamp)')

9
base_phone/res_company_view.xml → base_phone/views/res_company_view.xml

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="view_company_form" model="ir.ui.view">
@ -30,4 +29,4 @@
</data>
</openerp>
</odoo>

11
base_phone/res_partner_view.xml → base_phone/views/res_partner_view.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Base Phone module for OpenERP
Copyright (C) 2010-2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="view_partner_form" model="ir.ui.view">
@ -31,8 +30,8 @@
</record>
<record id="base.action_partner_form" model="ir.actions.act_window">
<field name="context">{'search_default_customer':1, 'raise_if_phone_parse_fails': True}</field>
<field name="context">{'search_default_customer': 1, 'raise_if_phone_parse_fails': True}</field>
</record>
</data>
</openerp>
</odoo>

9
base_phone/res_users_view.xml → base_phone/views/res_users_view.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Base Phone module for OpenERP
Copyright (C) 2010-2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2010-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="view_users_form" model="ir.ui.view">
@ -37,4 +36,4 @@
</record>
</data>
</openerp>
</odoo>

11
base_phone/web_phone.xml

@ -1,19 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Base Phone module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<template id="assets_backend" name="base_phone assets"
inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript"
src="/base_phone/static/lib/phonenumbers/PhoneFormat.js"></script>
<script type="text/javascript"
src="/base_phone/static/src/js/phone_widget.js"></script>
</xpath>
@ -21,4 +18,4 @@
</data>
</openerp>
</odoo>

68
base_phone/wizard/number_not_found.py

@ -1,26 +1,9 @@
# -*- 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/>.
#
##############################################################################
# © 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, fields, api, exceptions
from openerp.tools.translate import _
from openerp import models, fields, api, _
from openerp.exceptions import UserError
import logging
import phonenumbers
@ -59,16 +42,26 @@ class NumberNotFound(models.TransientModel):
if not res:
res = {}
if res.get('calling_number'):
convert = self.env['res.partner']._generic_reformat_phonenumbers(
None, {'phone': res.get('calling_number')})
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'
if not self.env.user.company_id.country_id:
raise UserError(_(
'Missing country on company %s'
% self.env.user.company_id.name))
country_code = self.env.user.company_id.country_id.code
try:
parsed_num = phonenumbers.parse(
res['calling_number'], country_code)
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'
except Exception, e:
_logger.error(
"Cannot reformat the phone number '%s': %s",
res['calling_number'], e)
pass
return res
@api.multi
@ -93,13 +86,11 @@ class NumberNotFound(models.TransientModel):
@api.multi
def update_partner(self):
self.ensure_one()
wiz = self[0]
if not wiz.to_update_partner_id:
raise exceptions.Warning(
_('Error'),
_('Select the Partner to Update.'))
self.env['res.partner'].write(
wiz.to_update_partner_id.id,
raise UserError(_('Select the Partner to Update.'))
wiz.to_update_partner_id.write(
{wiz.number_type: wiz.e164_number})
action = {
'name': _('Partner: %s' % wiz.to_update_partner_id.name),
@ -112,8 +103,3 @@ class NumberNotFound(models.TransientModel):
'context': self._context,
}
return action
@api.onchange('to_update_partner_id')
def onchange_to_update_partner(self):
self.current_partner_phone = self.to_update_partner.phone or False
self.current_partner_mobile = self.to_update_partner.mobile or False

12
base_phone/wizard/number_not_found_view.xml

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2015 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2012-2016 (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="number_not_found_form" model="ir.ui.view">
<field name="name">number.not.found.form</field>
<field name="model">number.not.found</field>
<field name="arch" type="xml">
<form string="Number Not Found" version="7.0">
<form string="Number Not Found">
<div class="oe_title">
<label string="Number not found:" for="calling_number"/>
<h1>
<field name="calling_number" />
</h1>
@ -50,4 +50,4 @@
</data>
</openerp>
</odoo>

62
base_phone/wizard/reformat_all_phonenumbers.py

@ -1,23 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Base Phone module for Odoo
# Copyright (C) 2012-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/>.
#
##############################################################################
# © 2012-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, api
import logging
@ -42,15 +25,15 @@ class ReformatAllPhonenumbers(models.TransientModel):
self.ensure_one()
logger.info('Starting to reformat all the phone numbers')
phonenumbers_not_reformatted = u''
phoneobjects = self.env['phone.common']._get_phone_fields()
for objname in phoneobjects:
fields = self.env[objname]._phone_fields
obj = self.env[objname]
phoneobjects = self.env['phone.common']._get_phone_models()
for obj_dict in phoneobjects:
fields = obj_dict['fields']
obj = obj_dict['object']
logger.info(
'Starting to reformat phone numbers on object %s '
'(fields = %s)', objname, fields)
'(fields = %s)', obj._name, fields)
# search if this object has an 'active' field
if obj._columns.get('active') or objname == 'hr.employee':
if obj._fields.get('active') or obj._name == 'hr.employee':
# hr.employee inherits from 'resource.resource' and
# 'resource.resource' has an active field
# As I don't know how to detect such cases, I hardcode it here
@ -61,32 +44,11 @@ class ReformatAllPhonenumbers(models.TransientModel):
all_entries = obj.search(domain)
for entry in all_entries:
init_entry_vals = {}
vals = {}
for field in fields:
init_entry_vals[field] = entry[field]
entry_vals = init_entry_vals.copy()
# entry is _updated_ by the fonction
# _generic_reformat_phonenumbers()
try:
entry.with_context(raise_if_phone_parse_fails=True).\
_reformat_phonenumbers_write(entry_vals)
except Exception, e:
name = entry.name_get()[0][1]
phonenumbers_not_reformatted += \
"Problem on %s '%s'. Error message: %s\n" % (
obj._description, name, unicode(e))
logger.warning(
"Problem on %s '%s'. Error message: %s",
obj._description, name, unicode(e))
continue
if any([
init_entry_vals.get(field) != entry_vals.get(field) for
field in fields]):
logger.info(
'[%s] Reformating phone number: FROM %s TO %s',
obj._description, unicode(init_entry_vals),
unicode(entry_vals))
entry.write(entry_vals)
vals[field] = entry[field]
if any([value for value in vals.values()]):
entry.write(vals)
if not phonenumbers_not_reformatted:
phonenumbers_not_reformatted = \
'All phone numbers have been reformatted successfully.'

19
base_phone_popup/__init__.py

@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Base Phone Pop-up 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 popup

33
base_phone_popup/__openerp__.py

@ -1,28 +1,11 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Base Phone Pop-up 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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Base Phone Pop-up',
'version': '8.0.0.4.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Pop-up the related form view to the user on incoming calls',
@ -55,10 +38,6 @@ http://www.akretion.com/products-and-services/openerp-asterisk-voip-connector
'author': "Akretion,Odoo Community Association (OCA)",
'website': 'http://www.akretion.com/',
'depends': ['base_phone', 'web_action_request'],
'data': [
'res_users_view.xml',
],
'images': [],
'active': False,
'installable': False,
'data': ['res_users_view.xml'],
'installable': True,
}

68
base_phone_popup/popup.py

@ -1,40 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Base Phone Pop-up 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/>.
#
##############################################################################
# © 2014-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, fields
from openerp.tools.translate import _
from openerp import models, fields, api, _
import logging
logger = logging.getLogger(__name__)
class phone_common(orm.AbstractModel):
class PhoneCommon(models.AbstractModel):
_inherit = 'phone.common'
def _prepare_incall_pop_action(
self, cr, uid, record_res, number, context=None):
@api.model
def _prepare_incall_pop_action(self, record_res, number):
action = False
if record_res:
obj = self.pool[record_res[0]]
obj = self.env[record_res[0]]
action = {
'name': obj._description,
'type': 'ir.actions.act_window',
@ -56,28 +38,23 @@ class phone_common(orm.AbstractModel):
}
return action
def incall_notify_by_login(
self, cr, uid, number, login_list, context=None):
@api.model
def incall_notify_by_login(self, number, login_list):
assert isinstance(login_list, list), 'login_list must be a list'
res = self.get_record_from_phone_number(
cr, uid, number, context=context)
user_ids = self.pool['res.users'].search(
cr, uid, [('login', 'in', login_list)], context=context)
res = self.get_record_from_phone_number(number)
users = self.env['res.users'].search(
[('login', 'in', login_list)])
logger.debug(
'Notify incoming call from number %s to users %s'
% (number, user_ids))
action = self._prepare_incall_pop_action(
cr, uid, res, number, context=context)
% (number, users.ids))
action = self._prepare_incall_pop_action(res, number)
if action:
users = self.pool['res.users'].read(
cr, uid, user_ids, ['context_incall_popup'], context=context)
for user in users:
if user['context_incall_popup']:
self.pool['action.request'].notify(
cr, user['id'], action)
if user.context_incall_popup:
self.sudo(user.id).env['action.request'].notify(action)
logger.debug(
'This action has been sent to user ID %d: %s'
% (user['id'], action))
% (user.id, action))
if res:
callerid = res[2]
else:
@ -85,13 +62,8 @@ class phone_common(orm.AbstractModel):
return callerid
class res_users(orm.Model):
class ResUsers(models.Model):
_inherit = 'res.users'
_columns = {
'context_incall_popup': fields.boolean('Pop-up on Incoming Calls'),
}
_defaults = {
'context_incall_popup': True,
}
context_incall_popup = fields.Boolean(
string='Pop-up on Incoming Calls', default=True)

8
base_phone_popup/res_users_view.xml

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="view_users_form" model="ir.ui.view">
@ -36,4 +36,4 @@
</record>
</data>
</openerp>
</odoo>

19
crm_claim_phone/__init__.py

@ -1,22 +1,3 @@
# -*- 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

47
crm_claim_phone/crm_claim_phone.py

@ -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')

9
crm_claim_phone/crm_claim_view.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
CRM Claim Phone module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="crm_case_claims_form_view" model="ir.ui.view">
@ -20,4 +19,4 @@
</record>
</data>
</openerp>
</odoo>

19
crm_phone/__init__.py

@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# CRM 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_phone
from . import wizard

40
crm_phone/__openerp__.py

@ -1,28 +1,10 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# CRM 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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'CRM Phone',
'version': '8.0.0.1.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Validate phone numbers in CRM',
@ -42,16 +24,18 @@ for any help or question about this module.
'author': "Akretion,Odoo Community Association (OCA)",
'website': 'http://www.akretion.com/',
'depends': ['base_phone', 'crm'],
'conflicts': ['crm_voip'],
'data': [
'security/phonecall_security.xml',
'security/ir.model.access.csv',
'crm_view.xml',
'res_users_view.xml',
'view/crm_phonecall.xml',
'view/crm_lead.xml',
'view/res_partner.xml',
'view/res_users.xml',
'wizard/number_not_found_view.xml',
'wizard/create_crm_phonecall_view.xml',
],
'test': ['test/phonenum.yml'],
'images': [],
'installable': False,
'demo': ['demo/crm_phonecall.xml'],
'installable': True,
'auto_install': True,
'active': False,
}

199
crm_phone/crm_phone.py

@ -1,56 +1,29 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# CRM 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 import models, fields, api, _
from openerp.addons.base_phone.fields import Phone
class CrmLead(models.Model):
_name = 'crm.lead'
_inherit = ['crm.lead', 'phone.common']
_phone_fields = ['phone', 'mobile', 'fax']
_inherit = 'crm.lead'
_phone_name_sequence = 20
_country_field = 'country_id'
_partner_field = None
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, None, vals, context=context)
return super(CrmLead, 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(CrmLead, 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'):
phone = Phone(country_field='country_id', partner_field='partner_id')
mobile = Phone(country_field='country_id', partner_field='partner_id')
fax = Phone(country_field='country_id', partner_field='partner_id')
phonecall_ids = fields.One2many(
'crm.phonecall', 'opportunity_id', string='Phone Calls')
phonecall_count = fields.Integer(
compute='_count_phonecalls', string='Number of Phonecalls',
readonly=True)
@api.multi
def name_get(self):
if self._context.get('callerid'):
res = []
if isinstance(ids, (int, long)):
ids = [ids]
for lead in self.browse(cr, uid, ids, context=context):
for lead in self:
if lead.partner_name and lead.contact_name:
name = u'%s (%s)' % (lead.contact_name, lead.partner_name)
elif lead.partner_name:
@ -62,28 +35,126 @@ class CrmLead(models.Model):
res.append((lead.id, name))
return res
else:
return super(CrmLead, self).name_get(
cr, uid, ids, context=context)
return super(CrmLead, self).name_get()
@api.multi
@api.depends('phonecall_ids')
def _count_phonecalls(self):
cpo = self.env['crm.phonecall']
for lead in self:
try:
lead.phonecall_count = cpo.search_count(
[('opportunity_id', '=', lead.id)])
except:
lead.phonecall_count = 0
class CrmPhonecall(models.Model):
_name = 'crm.phonecall'
_inherit = ['crm.phonecall', 'phone.common']
_phone_fields = ['partner_phone', 'partner_mobile']
_country_field = None
_partner_field = 'partner_id'
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, None, vals, context=context)
return super(CrmPhonecall, 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(CrmPhonecall, self).write(
cr, uid, ids, vals_reformated, context=context)
_inherit = ['mail.thread']
_order = "id desc"
# Restore the object that existed in v8
# and doesn't exist in v9 community any more
name = fields.Char(
string='Call Summary', required=True, track_visibility='onchange')
date = fields.Datetime(
string='Date', track_visibility='onchange', copy=False,
default=lambda self: fields.Datetime.now())
description = fields.Text(string='Description', copy=False)
company_id = fields.Many2one(
'res.company', string='Company',
default=lambda self: self.env['res.company']._company_default_get(
'crm.phonecall'))
user_id = fields.Many2one(
'res.users', string='Responsible', track_visibility='onchange',
default=lambda self: self.env.user)
team_id = fields.Many2one(
'crm.team', string='Sales Team', track_visibility='onchange',
default=lambda self: self.env['crm.team']._get_default_team_id())
partner_id = fields.Many2one(
'res.partner', string='Contact', ondelete='cascade')
partner_phone = Phone(string='Phone', partner_field='partner_id')
partner_mobile = Phone(string='Mobile', partner_field='partner_id')
priority = fields.Selection([
('0', 'Low'),
('1', 'Normal'),
('2', 'High')
], string='Priority', track_visibility='onchange', default='1')
opportunity_id = fields.Many2one(
'crm.lead', string='Lead/Opportunity',
ondelete='cascade', track_visibility='onchange')
state = fields.Selection([
('open', 'To Do'),
('done', 'Held'),
('cancel', 'Cancelled'),
], string='Status', default='open', copy=False, required=True,
track_visibility='onchange',
help='The status is set to Confirmed, when a case is created.\n'
'When the call is over, the status is set to Held.\n'
'If the call is not applicable anymore, the status can be set to '
'Cancelled.')
direction = fields.Selection([
('inbound', 'Inbound'),
('outbound', 'Outbound'),
], string='Type', required=True, default='outbound')
@api.onchange('partner_id')
def onchange_partner_id(self):
if self.partner_id:
self.partner_phone = self.partner_id.phone
self.partner_mobile = self.partner_id.mobile
@api.onchange('opportunity_id')
def onchange_opportunity_id(self):
if self.opportunity_id:
self.partner_phone = self.opportunity_id.phone
self.partner_mobile = self.opportunity_id.mobile
self.team_id = self.opportunity_id.team_id.id
self.partner_id = self.opportunity_id.partner_id.id
@api.multi
def schedule_another_call(self):
self.ensure_one()
cur_call = self[0]
ctx = self._context.copy()
ctx.update({
'default_date': False,
'default_partner_id': cur_call.partner_id.id,
'default_opportunity_id': cur_call.opportunity_id.id,
'default_direction': 'outbound',
'default_partner_phone': cur_call.partner_phone,
'default_partner_mobile': cur_call.partner_mobile,
})
action = {
'name': _('Phone Call'),
'type': 'ir.actions.act_window',
'res_model': 'crm.phonecall',
'view_mode': 'form,tree,calendar',
'context': ctx,
}
return action
class ResPartner(models.Model):
_inherit = 'res.partner'
phonecall_ids = fields.One2many(
'crm.phonecall', 'partner_id', string='Phone Calls')
phonecall_count = fields.Integer(
compute='_count_phonecalls', string='Number of Phonecalls',
readonly=True)
@api.multi
@api.depends('phonecall_ids')
def _count_phonecalls(self):
cpo = self.env['crm.phonecall']
for partner in self:
try:
partner.phonecall_count = cpo.search_count(
[('partner_id', 'child_of', partner.id)])
except:
partner.phonecall_count = 0
class ResUsers(models.Model):

59
crm_phone/demo/crm_phonecall.xml

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<odoo>
<data noupdate="1">
<record id="crm_phonecall1" model="crm.phonecall">
<field name="date" eval="time.strftime('%Y-%m-01 11:42:12')"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field name="user_id" ref="base.user_root"/>
<field name="name">Presentation of new product line</field>
<field name="state">done</field>
<field name="partner_phone">+33442127812</field>
<field name="direction">outbound</field>
</record>
<record id="crm_phonecall2" model="crm.phonecall">
<field name="date" eval="time.strftime('%Y-%m-28 09:12:42')"/>
<field name="partner_id" ref="base.res_partner_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="name">Close the deal</field>
<field name="state">open</field>
<field name="partner_phone">+33543137913</field>
<field name="direction">outbound</field>
</record>
<record id="crm_phonecall3" model="crm.phonecall">
<field name="date" eval="time.strftime('%Y-%m-27 09:45:42')"/>
<field name="partner_id" ref="base.res_partner_address_3"/>
<field name="user_id" ref="base.user_demo"/>
<field name="name">Lobby about our offer</field>
<field name="state">open</field>
<field name="partner_phone">+33543137914</field>
<field name="direction">outbound</field>
</record>
<record id="crm_phonecall4" model="crm.phonecall">
<field name="date" eval="time.strftime('%Y-%m-26 09:45:42')"/>
<field name="opportunity_id" ref="crm.crm_case_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="name">Remote Demo of the software</field>
<field name="state">open</field>
<field name="partner_phone">+33789320421</field>
<field name="direction">outbound</field>
</record>
<record id="crm_phonecall5" model="crm.phonecall">
<field name="date" eval="time.strftime('%Y-%m-02 09:45:42')"/>
<field name="opportunity_id" ref="crm.crm_case_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="name">Asked training program</field>
<field name="state">done</field>
<field name="partner_phone">+33389320442</field>
<field name="direction">inbound</field>
</record>
</data>
</odoo>

3
crm_phone/security/ir.model.access.csv

@ -1,2 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
callerid_crm_lead_read,Read access on crm.lead,crm.model_crm_lead,base_phone.group_callerid,1,0,0,0
access_crm_phonecall_partner_manager,Full access on crm.phonecall to Contact mgr,model_crm_phonecall,base.group_partner_manager,1,1,1,1
access_crm_phonecall_sale_manager,Full access on crm.phonecall to Sale mgr,model_crm_phonecall,base.group_sale_manager,1,1,1,1
access_crm_phonecall_sale_user,Read/Write/Create access on crm.phonecall to Sale users,model_crm_phonecall,base.group_sale_salesman,1,1,1,0

28
crm_phone/security/phonecall_security.xml

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="my_crm_phonecall_rule" model="ir.rule">
<field name="name">Personal Phone Calls</field>
<field name="model_id" ref="model_crm_phonecall"/>
<field name="groups" eval="[(4, ref('base.group_sale_salesman'))]"/>
<field name="domain_force">['|', ('user_id', '=', False), ('user_id', '=', user.id)]</field>
</record>
<record id="all_crm_phonecall_rule" model="ir.rule">
<field name="name">All Phone Calls</field>
<field name="model_id" ref="model_crm_phonecall"/>
<field name="groups" eval="[(4, ref('base.group_sale_salesman_all_leads'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="multi_company_crm_phonecall_rule" model="ir.rule">
<field name="name">Multi-company Phone Calls</field>
<field name="model_id" ref="model_crm_phonecall"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</data>
</odoo>

41
crm_phone/test/phonenum.yml

@ -1,41 +0,0 @@
-
Write french phone numbers in national format
-
!record {model: crm.lead, id: lead1}:
name: Jacques Toufaux
mobile: 06 42 77 42 77
fax: (0) 1 45 44 42 43
country_id: base.fr
-
Write swiss phone numbers in national format
-
!record {model: crm.lead, id: lead2}:
name: Michel Content
country_id: base.ch
phone: 04 31 23 45 67
-
Create a german lead
-
!record {model: crm.lead, id: lead3}:
name: Angela Strasse
country_id: base.de
-
Check that valid phone numbers have been converted to E.164
-
!python {model: crm.lead}: |
lead1 = self.browse(cr, uid, ref('lead1'), context=context)
assert lead1.mobile == '+33642774277', 'Mobile number not written in E.164 format (lead1)'
assert lead1.fax == '+33145444243', 'Fax number not written in E.164 format (lead1)'
lead2 = self.browse(cr, uid, ref('lead2'), context=context)
assert lead2.phone == '+41431234567', 'Phone number not written in E.164 format (lead2)'
self.write(cr, uid, ref('lead3'), {'phone': '0891234567'})
lead3 = self.browse(cr, uid, ref('lead3'), context=context)
assert lead3.phone == '+49891234567', 'Phone number not written in E.164 format (lead3)'
-
Get name from phone number
-
!python {model: phone.common}: |
name = self.get_name_from_phone_number(cr, uid, '0642774277')
assert name == 'Jacques Toufaux', 'Wrong result for get_name_from_phone_number (lead1)'
name2 = self.get_name_from_phone_number(cr, uid, '0041431234567')
assert name2 == 'Michel Content', 'Wrong result for get_name_from_phone_number (lead2)'

3
crm_phone/tests/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_crm_phone

46
crm_phone/tests/test_crm_phone.py

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion France (Alexis de Lattre <alexis.delattre@akretion.com>)
from openerp.tests.common import TransactionCase
class TestCRMPhone(TransactionCase):
def test_crm_phone(self):
clo = self.env['crm.lead']
lead1 = clo.create({
'name': 'The super deal of the year !',
'partner_name': 'Ford',
'contact_name': 'Jacques Toufaux',
'mobile': '06.42.77.42.77',
'fax': '(0) 1 45 44 42 43',
'country_id': self.env.ref('base.fr').id,
})
self.assertEquals(lead1.mobile, u'+33 6 42 77 42 77')
self.assertEquals(lead1.fax, u'+33 1 45 44 42 43')
lead2 = clo.create({
'name': u'Automobile Odoo deployment',
'partner_name': u'Kia',
'contact_name': u'Mikaël Content',
'country_id': self.env.ref('base.ch').id,
'phone': '04 31 23 45 67',
})
self.assertEquals(lead2.phone, u'+41 43 123 45 67')
lead3 = clo.create({
'name': 'Angela Strasse',
'country_id': self.env.ref('base.de').id,
})
lead3.write({'phone': '08912345678'})
self.assertEquals(lead3.phone, u'+49 89 12345678')
lead4 = clo.create({
'name': 'Large Odoo deployment',
'partner_id': self.env.ref('base.res_partner_2').id,
})
lead4.write({'mobile': '(0) 2-391-43-75'})
self.assertEquals(lead4.mobile, u'+32 2 391 43 75')
pco = self.env['phone.common']
name = pco.get_name_from_phone_number('0642774277')
self.assertEquals(name, 'Jacques Toufaux (Ford)')
name2 = pco.get_name_from_phone_number('0041431234567')
self.assertEquals(name2, u'Mikaël Content (Kia)')

55
crm_phone/crm_view.xml → crm_phone/view/crm_lead.xml

@ -1,26 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="crm_case_form_view_leads" model="ir.ui.view">
<field name="name">crm_phone.crm_lead.form</field>
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_case_form_view_leads"/>
<field name="arch" type="xml">
<field name="phone" position="attributes">
<attribute name="widget">phone</attribute>
</field>
<field name="mobile" position="attributes">
<attribute name="widget">phone</attribute>
</field>
<field name="fax" position="attributes">
<attribute name="widget">fax</attribute>
</field>
<div name="button_box" position="inside">
<button class="oe_inline oe_stat_button" type="action"
name="%(crm_phone.crm_phonecall_action)d"
icon="fa-phone"
context="{'search_default_opportunity_id': active_id}">
<field string="Calls" name="phonecall_count" widget="statinfo"/>
</button>
</div>
</field>
</record>
@ -40,42 +46,23 @@
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_case_form_view_oppor"/>
<field name="arch" type="xml">
<field name="phone" position="attributes">
<attribute name="widget">phone</attribute>
</field>
<field name="mobile" position="attributes">
<attribute name="widget">phone</attribute>
</field>
<field name="fax" position="attributes">
<attribute name="widget">fax</attribute>
</field>
<div name="button_box" position="inside">
<button class="oe_inline oe_stat_button" type="action"
name="%(crm_phone.crm_phonecall_action)d"
icon="fa-phone"
context="{'search_default_opportunity_id': active_id}">
<field string="Calls" name="phonecall_count" widget="statinfo"/>
</button>
</div>
</field>
</record>
<record id="crm_case_phone_form_view" model="ir.ui.view">
<field name="name">crm_phone.crm_phonecall.form</field>
<field name="model">crm.phonecall</field>
<field name="inherit_id" ref="crm.crm_case_phone_form_view"/>
<field name="arch" type="xml">
<field name="partner_phone" position="attributes">
<attribute name="widget">phone</attribute>
</field>
<field name="partner_mobile" position="attributes">
<attribute name="widget">phone</attribute>
</field>
</field>
</record>
<record id="crm_case_phone_tree_view" model="ir.ui.view">
<field name="name">crm_phone.crm_phonecall.tree</field>
<field name="model">crm.phonecall</field>
<field name="inherit_id" ref="crm.crm_case_phone_tree_view"/>
<field name="arch" type="xml">
<field name="partner_phone" position="attributes">
<attribute name="widget">phone</attribute>
</field>
</field>
</record>
</data>
</openerp>
</odoo>

160
crm_phone/view/crm_phonecall.xml

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<data>
<record id="crm_phonecall_form" model="ir.ui.view">
<field name="name">crm.phonecall.form</field>
<field name="model">crm.phonecall</field>
<field name="arch" type="xml">
<form string="Phone Call">
<header>
<field name="state" widget="statusbar" clickable="True"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" type="object"
name="schedule_another_call" icon="fa-phone"
string="Schedule Another Call"/>
</div>
<div class="oe_title">
<div class="oe_edit_only">
<label for="name"/>
</div>
<h1><field name="name"/></h1>
</div>
<group name="main">
<group name="left">
<field name="date"/>
<field name="user_id"/>
<field name="partner_id"/>
<field name="partner_phone" widget="phone"/>
<field name="partner_mobile" widget="phone"/>
<field name="opportunity_id"/>
</group>
<group name="right">
<field name="direction"/>
<field name="team_id" groups="base.group_multi_salesteams"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="priority" widget="priority"/>
</group>
</group>
<group name="description" string="Description">
<field name="description" nolabel="1"
placeholder="Summary of the phone call..."/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="crm_phonecall_tree" model="ir.ui.view">
<field name="name">crm.phonecall.tree</field>
<field name="model">crm.phonecall</field>
<field name="arch" type="xml">
<tree string="Phone Calls" editable="top">
<field name="date"/>
<field name="name"/>
<field name="partner_id"/>
<field name="opportunity_id"/>
<field name="user_id"/>
<field name="team_id" groups="base.group_multi_salesteams"/>
<field name="direction"/>
<field name="state"/>
<button name="schedule_another_call" type="object"
string="Schedule Another Call" icon="terp-call-start"/>
</tree>
</field>
</record>
<record id="crm_phonecall_calendar" model="ir.ui.view">
<field name="name">crm.phonecall.calendar</field>
<field name="model">crm.phonecall</field>
<field name="arch" type="xml">
<calendar string="Phone Calls" date_start="date" color="user_id">
<field name="name"/>
<field name="partner_id"/>
</calendar>
</field>
</record>
<record id="crm_phonecall_search" model="ir.ui.view">
<field name="name">crm.phonecall.search</field>
<field name="model">crm.phonecall</field>
<field name="arch" type="xml">
<search string="Search Phone Calls">
<field name="name" string="Phonecalls"/>
<field name="date"/>
<field name="user_id"/>
<field name="partner_id" operator="child_of"/>
<field name="opportunity_id"/>
<field name="team_id" groups="base.group_multi_salesteams"/>
<filter string="My Phonecalls" domain="[('user_id', '=', uid)]"/>
<filter string="My Team" domain="[('section_id.user_id', '=', uid)]"/>
<filter string="Unassigned" domain="[('user_id', '=', False)]"/>
<separator/>
<filter string="To Do" name="open" domain="[('state', '=', 'open')]"/>
<filter string="Held" name="done" domain="[('state', '=', 'done')]"/>
<separator/>
<filter string="Inbound" domain="[('direction', '=', 'inbound')]"/>
<filter string="Outbound" domain="[('direction', '=', 'outbound')]"/>
<group string="Group By" name="groupby">
<filter name="partner_groupby" string="Partner"
context="{'group_by': 'partner_id'}"/>
<filter name="user_groupby" string="Responsible"
context="{'group_by': 'user_id'}"/>
<filter name="team_groupby" string="Team"
context="{'group_by': 'team_id'}"
groups="base.group_multi_salesteams"/>
<filter name="date_groupby" string="Month"
context="{'group_by': 'date'}"/>
<filter name="state_groupby" string="State"
context="{'group_by': 'state'}"/>
<filter name="direction" string="Direction"
context="{'group_by': 'direction'}"/>
</group>
</search>
</field>
</record>
<menuitem id="crm_phonecall_menu" name="Phone Calls"
parent="base.menu_base_partner" sequence="7" groups="base.group_sale_salesman"/>
<record id="crm_phonecall_action" model="ir.actions.act_window">
<field name="name">Phone Calls</field>
<field name="res_model">crm.phonecall</field>
<field name="view_mode">tree,calendar,form</field>
</record>
<record id="crm_phonecall_action_done" model="ir.actions.act_window">
<field name="name">Logged Calls</field>
<field name="res_model">crm.phonecall</field>
<field name="view_mode">tree,calendar,form</field>
<field name="context">{'search_default_done': 1, 'default_state': 'done'}</field>
</record>
<menuitem id="crm_phonecall_menu_done" action="crm_phonecall_action_done"
parent="crm_phonecall_menu" sequence="10"/>
<record id="crm_phonecall_action_open" model="ir.actions.act_window">
<field name="name">Scheduled Calls</field>
<field name="res_model">crm.phonecall</field>
<field name="view_mode">tree,calendar,form</field>
<field name="context">{'search_default_open': 1, 'default_state': 'open'}</field>
</record>
<menuitem id="crm_phonecall_menu_open" action="crm_phonecall_action_open"
parent="crm_phonecall_menu" sequence="20"/>
</data>
</odoo>

30
crm_phone/view/res_partner.xml

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<data>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">phonecall.res.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="groups_id" eval="[(4, ref('base.group_sale_salesman')), (4, ref('base.group_partner_manager'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_inline oe_stat_button" type="action"
name="%(crm_phone.crm_phonecall_action)d"
icon="fa-phone"
context="{'search_default_partner_id': active_id}">
<field string="Calls" name="phonecall_count" widget="statinfo"/>
</button>
</div>
</field>
</record>
</data>
</odoo>

9
crm_phone/res_users_view.xml → crm_phone/view/res_users.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2015 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
The licence is in the file __openerp__.py
© 2012-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<!-- Add option on user form view -->
@ -34,4 +33,4 @@
</record>
</data>
</openerp>
</odoo>

19
crm_phone/wizard/__init__.py

@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# CRM 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 . import number_not_found
from . import create_crm_phonecall

47
crm_phone/wizard/create_crm_phonecall.py

@ -1,54 +1,33 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# CRM Phone module for Odoo
# Copyright (c) 2012-2015 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 import models, api, _
import phonenumbers
class wizard_create_crm_phonecall(models.TransientModel):
class WizardCreateCrmPhonecall(models.TransientModel):
_name = "wizard.create.crm.phonecall"
@api.multi
def button_create_outgoing_phonecall(self):
self.ensure_one()
return self._create_open_crm_phonecall(crm_categ='Outbound')
return self._create_open_crm_phonecall('outbound')
@api.model
def _create_open_crm_phonecall(self, crm_categ):
categ = self.with_context(lang='en_US').env['crm.case.categ'].search(
[('name', '=', crm_categ)])
case_section = self.env['crm.case.section'].search(
def _create_open_crm_phonecall(self, direction='outbound'):
teams = self.env['crm.team'].search(
[('member_ids', 'in', self._uid)])
action_ctx = self.env.context.copy()
action_ctx.update({
'default_categ_id': categ and categ[0].id or False,
'default_section_id':
case_section and case_section[0].id or False,
'default_direction': direction,
'default_team_id': teams and teams[0].id or False,
})
domain = False
if self.env.context.get('click2dial_model') == 'res.partner':
partner_id = self.env.context.get('click2dial_id')
action_ctx['default_partner_id'] = partner_id
domain = [('partner_id', '=', partner_id)]
domain = [('partner_id', 'child_of', partner_id)]
elif self.env.context.get('click2dial_model') == 'crm.lead':
lead_id = self.env.context.get('click2dial_id')
action_ctx['default_opportunity_id'] = lead_id
@ -63,10 +42,10 @@ class wizard_create_crm_phonecall(models.TransientModel):
self.env.context.get('phone_number')
return {
'name': _('Phone Call'),
'domain': domain,
'res_model': 'crm.phonecall',
'view_mode': 'form,tree',
'type': 'ir.actions.act_window',
'res_model': 'crm.phonecall',
'domain': domain,
'view_mode': 'form,tree,calendar',
'nodestroy': False, # close the pop-up wizard after action
'target': 'current',
'context': action_ctx,

96
crm_phone/wizard/number_not_found.py

@ -1,49 +1,29 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# CRM 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/>.
#
##############################################################################
# -*- 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.osv import orm, fields
from openerp.tools.translate import _
from openerp import models, fields, api, _
from openerp.exceptions import UserError
class number_not_found(orm.TransientModel):
class NumberNotFound(models.TransientModel):
_inherit = "number.not.found"
_columns = {
'to_update_lead_id': fields.many2one(
'crm.lead', 'Lead to Update',
domain=[('type', '=', 'lead')],
help="Lead on which the phone number will be written"),
'current_lead_phone': fields.related(
'to_update_lead_id', 'phone', type='char',
relation='crm.lead', string='Current Phone', readonly=True),
'current_lead_mobile': fields.related(
'to_update_lead_id', 'mobile', type='char',
relation='crm.lead', string='Current Mobile', readonly=True),
}
to_update_lead_id = fields.Many2one(
'crm.lead', string='Lead to Update',
domain=[('type', '=', 'lead')],
help="Lead on which the phone number will be written")
current_lead_phone = fields.Char(
related='to_update_lead_id.phone', string='Current Phone',
readonly=True)
current_lead_mobile = fields.Char(
related='to_update_lead_id.mobile', string='Current Mobile',
readonly=True)
def create_lead(self, cr, uid, ids, context=None):
@api.multi
def create_lead(self):
'''Function called by the related button of the wizard'''
if context is None:
context = {}
wiz = self.browse(cr, uid, ids[0], context=context)
self.ensure_one()
action = {
'name': _('Create New Lead'),
@ -54,7 +34,7 @@ class number_not_found(orm.TransientModel):
'nodestroy': False,
'target': 'current',
'context': {
'default_%s' % wiz.number_type: wiz.e164_number,
'default_%s' % self.number_type: self.e164_number,
'default_type': 'lead',
'stage_type': 'lead',
'needaction_menu_ref': 'crm.menu_crm_opportunities',
@ -62,43 +42,23 @@ class number_not_found(orm.TransientModel):
}
return action
def update_lead(self, cr, uid, ids, context=None):
wiz = self.browse(cr, uid, ids[0], context=context)
if not wiz.to_update_lead_id:
raise orm.except_orm(
_('Error:'),
_("Select the Lead to Update."))
self.pool['crm.lead'].write(
cr, uid, wiz.to_update_lead_id.id,
{wiz.number_type: wiz.e164_number}, context=context)
@api.multi
def update_lead(self):
self.ensure_one()
if not self.to_update_lead_id:
raise UserError(_("Select the Lead to Update."))
self.to_update_lead_id.write({self.number_type: self.e164_number})
action = {
'name': _('Lead: %s' % wiz.to_update_lead_id.name),
'name': _('Lead: %s' % self.to_update_lead_id.name),
'type': 'ir.actions.act_window',
'res_model': 'crm.lead',
'view_mode': 'form,tree',
'nodestroy': False,
'target': 'current',
'res_id': wiz.to_update_lead_id.id,
'res_id': self.to_update_lead_id.id,
'context': {
'stage_type': 'lead',
'needaction_menu_ref': 'crm.menu_crm_opportunities',
},
}
return action
def onchange_to_update_lead(
self, cr, uid, ids, to_update_lead_id, context=None):
res = {'value': {}}
if to_update_lead_id:
to_update_lead = self.pool['crm.lead'].browse(
cr, uid, to_update_lead_id, context=context)
res['value'].update({
'current_lead_phone': to_update_lead.phone,
'current_lead_mobile': to_update_lead.mobile,
})
else:
res['value'].update({
'current_lead_phone': False,
'current_lead_mobile': False,
})
return res

20
crm_phone/wizard/number_not_found_view.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
CRM Phone module for OpenERP
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="number_not_found_form" model="ir.ui.view">
@ -15,17 +14,18 @@
<field name="arch" type="xml">
<group name="create-update" position="inside">
<group name="lead" string="Create or Update a Lead"
colspan="1" col="2">
colspan="1">
<button name="create_lead" colspan="2"
string="Create Lead with this Number" type="object"/>
<field name="to_update_lead_id"
on_change="onchange_to_update_lead(to_update_lead_id)"/>
string="Create Lead with this Number" type="object"
class="oe_highlight"/>
<field name="to_update_lead_id"/>
<field name="current_lead_phone" widget="phone"
options="{'dial_button_invisible': True}"/>
<field name="current_lead_mobile" widget="phone"
options="{'dial_button_invisible': True}"/>
<button name="update_lead" colspan="2"
string="Update Lead with this Number" type="object"/>
string="Update Lead with this Number" type="object"
class="oe_highlight"/>
</group>
</group>
</field>
@ -33,4 +33,4 @@
</data>
</openerp>
</odoo>

19
event_phone/__init__.py

@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Event 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 event_phone

29
event_phone/__openerp__.py

@ -1,28 +1,11 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Event 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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Event Phone',
'version': '8.0.0.1.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Validate phone numbers in Events',
@ -46,8 +29,6 @@ for any help or question about this module.
'security/ir.model.access.csv',
'event_view.xml',
],
'images': [],
'installable': False,
'installable': True,
'auto_install': True,
'active': False,
}

47
event_phone/event_phone.py

@ -1,44 +1,13 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Event 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 event_registration(orm.Model):
_name = 'event.registration'
_inherit = ['event.registration', 'phone.common']
_phone_fields = ['phone']
class EventRegistration(models.Model):
_inherit = 'event.registration'
_phone_name_sequence = 100
_country_field = None
_partner_field = 'partner_id'
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, None, vals, context=context)
return super(event_registration, 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(event_registration, self).write(
cr, uid, ids, vals_reformated, context=context)
phone = Phone(partner_field='partner_id')

23
event_phone/event_view.xml

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-->
<openerp>
<odoo>
<data>
<record id="view_event_registration_form" model="ir.ui.view">
<field name="name">event_phone.event_registration.form</field>
<field name="model">event.registration</field>
@ -18,20 +19,6 @@
</field>
</record>
<record id="view_event_form" model="ir.ui.view">
<field name="name">event_phone.event.form</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event.view_event_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='registration_ids']/form//field[@name='phone']" position="attributes">
<attribute name="widget">phone</attribute>
</xpath>
<xpath expr="////field[@name='registration_ids']/tree/field[@name='phone']" position="attributes">
<attribute name="widget">phone</attribute>
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>

19
hr_phone/__init__.py

@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# HR 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 hr_phone

30
hr_phone/__openerp__.py

@ -1,28 +1,10 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# HR 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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'HR Phone',
'version': '8.0.0.1.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Validate phone numbers in HR',
@ -46,8 +28,6 @@ for any help or question about this module.
'security/ir.model.access.csv',
'hr_view.xml',
],
'images': [],
'installable': False,
'installable': True,
'auto_install': True,
'active': False,
}

48
hr_phone/hr_phone.py

@ -1,44 +1,14 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# HR 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 hr_employee(orm.Model):
_name = 'hr.employee'
_inherit = ['hr.employee', 'phone.common']
_phone_fields = ['work_phone', 'mobile_phone']
class HrEmployee(models.Model):
_inherit = 'hr.employee'
_phone_name_sequence = 30
_country_field = 'country_id'
_partner_field = None
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, None, vals, context=context)
return super(hr_employee, 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(hr_employee, self).write(
cr, uid, ids, vals_reformated, context=context)
work_phone = Phone(country_field='country_id')
mobile_phone = Phone(country_field='country_id')

10
hr_phone/hr_view.xml

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<openerp>
<odoo>
<data>
<record id="view_employee_form" model="ir.ui.view">
<field name="name">hr_phone.hr_employee.form</field>
<field name="model">hr.employee</field>
@ -43,5 +44,6 @@
</field>
</record>
</data>
</openerp>
</odoo>

1
hr_phone/security/ir.model.access.csv

@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
callerid_hr_employee_read,Read access on hr.employee,hr.model_hr_employee,base_phone.group_callerid,1,0,0,0
callerid_resource_resource_read,Read access on resource.resource,resource.model_resource_resource,base_phone.group_callerid,1,0,0,0

19
hr_recruitment_phone/__init__.py

@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# HR Recruitment 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 hr_recruitment_phone

30
hr_recruitment_phone/__openerp__.py

@ -1,28 +1,10 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# HR Recruitment 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/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'HR Recruitment Phone',
'version': '8.0.0.1.0',
'version': '9.0.1.0.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'Validate phone numbers in HR Recruitment',
@ -46,8 +28,6 @@ for any help or question about this module.
'security/ir.model.access.csv',
'hr_recruitment_view.xml',
],
'images': [],
'installable': False,
'installable': True,
'auto_install': True,
'active': False,
}

62
hr_recruitment_phone/hr_recruitment_phone.py

@ -1,44 +1,30 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# HR Recruitment 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, api
from openerp.addons.base_phone.fields import Phone
class hr_applicant(orm.Model):
_name = 'hr.applicant'
_inherit = ['hr.applicant', 'phone.common']
_phone_fields = ['partner_phone', 'partner_mobile']
class HrApplicant(models.Model):
_inherit = 'hr.applicant'
_phone_name_sequence = 50
_country_field = None
_partner_field = 'partner_id'
def create(self, cr, uid, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, None, vals, context=context)
return super(hr_applicant, self).create(
cr, uid, vals_reformated, context=context)
partner_phone = Phone(partner_field='partner_id')
partner_mobile = Phone(partner_field='partner_id')
def write(self, cr, uid, ids, vals, context=None):
vals_reformated = self._generic_reformat_phonenumbers(
cr, uid, ids, vals, context=context)
return super(hr_applicant, self).write(
cr, uid, ids, vals_reformated, context=context)
@api.multi
def name_get(self):
if self._context.get('callerid'):
res = []
for appl in self:
if appl.partner_id:
name = u'%s (%s)' % (appl.partner_id.name, appl.name)
elif appl.partner_name:
name = u'%s (%s)' % (appl.partner_name, appl.name)
else:
name = appl.name
res.append((appl.id, name))
return res
else:
return super(HrApplicant, self).name_get()

10
hr_recruitment_phone/hr_recruitment_view.xml

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 Alexis de Lattre <alexis@via.ecp.fr>
The licence is in the file __openerp__.py
© 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-->
<openerp>
<odoo>
<data>
<record id="crm_case_form_view_job" model="ir.ui.view">
<field name="name">hr_recruitment_phone.hr_applicant.form</field>
<field name="model">hr.applicant</field>
@ -32,5 +33,6 @@
</field>
</record>
</data>
</openerp>
</odoo>

19
ovh_telephony_connector/__init__.py

@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OVH Connector module for Odoo
# Copyright (C) 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 . import ovh_connector

4
ovh_telephony_connector/__openerp__.py

@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# OVH Connector module for Odoo
@ -21,7 +21,7 @@
{
'name': 'OVH Telephony Connector',
'version': '8.0.0.1.0',
'version': '9.0.0.1.0',
'category': 'Phone',
'license': 'AGPL-3',
'summary': 'OVH-Odoo telephony connector (click2call)',

24
ovh_telephony_connector/ovh_connector.py

@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# OVH connector module for Odoo
@ -20,7 +20,7 @@
##############################################################################
from openerp import models, fields, api, _
from openerp.exceptions import Warning
from openerp.exceptions import UserError
import logging
try:
@ -51,24 +51,24 @@ class PhoneCommon(models.AbstractModel):
def click2dial(self, erp_number):
res = super(PhoneCommon, self).click2dial(erp_number)
if not erp_number:
raise Warning(
raise UserError(
_('Missing phone number'))
user = self.env.user
if not user.ovh_billing_number:
raise Warning(
raise UserError(
_('Missing OVH Billing Number on user %s') % user.name)
if not user.ovh_calling_number:
raise Warning(
raise UserError(
_('Missing OVH Calling Number on user %s') % user.name)
if not user.ovh_click2call_login:
raise Warning(
raise UserError(
_('Missing OVH Click2call Login on user %s') % user.name)
if not user.ovh_click2call_password:
raise Warning(
raise UserError(
_('Missing OVH Click2dial Password on user %s') % user.name)
soap = WSDL.Proxy('https://www.ovh.com/soapi/soapi-re-1.63.wsdl')
@ -77,9 +77,9 @@ class PhoneCommon(models.AbstractModel):
_logger.debug(
'Starting OVH telephonyClick2CallDo request with '
'login = %s billing number = %s calling number = %s '
'and called_number = %s'
% (user.ovh_click2call_login, user.ovh_billing_number,
user.ovh_calling_number, called_number))
'and called_number = %s',
user.ovh_click2call_login, user.ovh_billing_number,
user.ovh_calling_number, called_number)
try:
soap.telephonyClick2CallDo(
@ -94,8 +94,8 @@ class PhoneCommon(models.AbstractModel):
_logger.error(
"Error in the OVH telephonyClick2CallDo request")
_logger.error(
"Here are the details of the error: '%s'" % unicode(e))
raise Warning(
"Here are the details of the error: '%s'", unicode(e))
raise UserError(
_("Click to call to OVH failed.\nHere is the error: "
"'%s'")
% unicode(e))

Loading…
Cancel
Save