diff --git a/connector_voicent/__manifest__.py b/connector_voicent/__manifest__.py index 8067dd0..0a710a2 100644 --- a/connector_voicent/__manifest__.py +++ b/connector_voicent/__manifest__.py @@ -7,9 +7,7 @@ 'version': '12.0.1.0.0', 'category': 'Connector', 'license': 'AGPL-3', - 'summary': """This module allows you to connect Odoo with Voicent - (https://www.voicent.com) and is meant to be extended to integrate - Odoo records and processes with phone calls made by Voicent.""", + 'summary': """Connect Odoo with Voicent""", "author": "Open Source Integrators, " "Odoo Community Association (OCA)", "website": "https://github.com/OCA/connector-telephony", @@ -18,10 +16,10 @@ ], 'data': [ 'security/ir.model.access.csv', - 'data/check_the_voicent_status.xml', - 'view/res_partner.xml', - 'view/backend_voicent.xml', - 'view/queue_job_view.xml', + 'data/ir_cron.xml', + 'views/res_partner.xml', + 'views/backend_voicent_call_line.xml', + 'views/backend_voicent.xml', ], 'installable': True, 'maintainers': [ diff --git a/connector_voicent/data/check_the_voicent_status.xml b/connector_voicent/data/ir_cron.xml similarity index 78% rename from connector_voicent/data/check_the_voicent_status.xml rename to connector_voicent/data/ir_cron.xml index ae7fa47..6786f88 100644 --- a/connector_voicent/data/check_the_voicent_status.xml +++ b/connector_voicent/data/ir_cron.xml @@ -2,7 +2,7 @@ - Fetch check the Voicent status + Voicent: Update the next call @@ -11,7 +11,7 @@ -1 code - model._run_check_the_voicent_status() + model._run_update_next_call() diff --git a/connector_voicent/examples/prototype.py b/connector_voicent/examples/prototype.py deleted file mode 100644 index 1f6a9c2..0000000 --- a/connector_voicent/examples/prototype.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2019 Open Source Integrators -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -# Simple message -import voicent - - -v = voicent.Voicent() -phoneno = "6024275632" -reqid = v.callText(phoneno, "Hello, This is a test of the autodialer.", "1") -status = v.callStatus(reqid) - -# Using Campaign in 2 steps -v = voicent.Voicent() -filepath = "/home/mchambreuil/odoo/pvm/voicent.csv" -listname = "Test" -leadsrc_id = v.importCampaign(listname, filepath) -res = v.runCampaign(listname) -v.checkStatus(res['leadsrc_id']) - -# Using Campaign in 1 step with TTS -v = voicent.Voicent() -filepath = "/home/mchambreuil/odoo/pvm/voicent.csv" -res = v.importAndRunCampaign(filepath, "tts", "Hello, This is a test. Bye") -status = v.checkStatus(res['camp_id']) - -# Using Campaign in 1 step with Template -v = voicent.Voicent() -filepath = "/home/mchambreuil/odoo/pvm/voicent.csv" -res = v.importAndRunCampaign(filepath, "template", "Test") -status = v.checkStatus(res['camp_id']) diff --git a/connector_voicent/examples/voicent.csv b/connector_voicent/examples/voicent.csv deleted file mode 100644 index 3353237..0000000 --- a/connector_voicent/examples/voicent.csv +++ /dev/null @@ -1,2 +0,0 @@ -Name,Phone -Maxime Chambreuil,6024275632 diff --git a/connector_voicent/examples/voicent.py b/connector_voicent/examples/voicent.py index d0c8015..a6cf4b6 100644 --- a/connector_voicent/examples/voicent.py +++ b/connector_voicent/examples/voicent.py @@ -4,16 +4,21 @@ # # Documentation available at https://voicent.com/developer/docs/camp-api/ +import ast +import csv import ntpath +import os import requests -import ast class Voicent(): - def __init__(self, host="localhost", port="8155"): + def __init__(self, host="localhost", port="8155", callerid="000000000", + line="1"): self.host_ = host self.port_ = port + self.callerid_ = callerid + self.line_ = line def postToGateway(self, urlstr, params, files=None): url = "http://" + self.host_ + ":" + self.port_ + urlstr @@ -22,11 +27,11 @@ class Voicent(): def getReqId(self, rcstr): index1 = rcstr.find("[ReqId=") - if (index1 == -1): + if index1 == -1: return "" index1 += 7 index2 = rcstr.find("]", index1) - if (index2 == -1): + if index2 == -1: return "" return rcstr[index1:index2] @@ -132,9 +137,9 @@ class Voicent(): 'CAMP_NAME': 'Test', 'listname': listname, 'phonecols': 'Phone', - 'lines': '4', + 'lines': self.line_, 'calldisps': '', - 'callerid': '+18884728568', + 'callerid': self.callerid_, } res = self.postToGateway(urlstr, params) return ast.literal_eval(res) @@ -146,16 +151,17 @@ class Voicent(): # Parameters for importing the campaign 'importfile': ntpath.basename(filepath), 'importfilepath': filepath, + 'mergeopt': 'skip', # 'profile': 'Test', 'mod': 'cus', 'row1': 1, - 'leadsrcname': 'Test', + 'leadsrcname': 'Odoo Voicent Connector', # Parameters for running the campaign - 'CAMP_NAME': 'Test', + 'CAMP_NAME': 'Odoo Voicent Connector', 'phonecols': 'Phone', - 'lines': '4', + 'lines': self.line_, 'calldisps': '', - 'callerid': '+18884728568', + 'callerid': self.callerid_, # Parameters for Autodialer 'msgtype': msgtype, 'msginfo': msginfo, @@ -166,22 +172,34 @@ class Voicent(): res = self.postToGateway(urlstr, params, files) return ast.literal_eval(res) - def checkStatus(self, leadsrc_id): + def checkStatus(self, camp_id): urlstr = "/ocall/campapi" params = { 'action': 'campstats', - 'leadsrc_id': leadsrc_id + 'camp_id': camp_id } res = self.postToGateway(urlstr, params) return ast.literal_eval(res) - def exportResult(self, camp_id, filename, extracols): + def exportResult(self, camp_id, filename, extracols=None): urlstr = "/ocall/campapi" params = { 'action': 'exportcamp', - 'camp_id_id': camp_id, - 'f': filename, + 'camp_id': camp_id, + 'f': 'webapps/ROOT/assets/global/' + filename, 'extracols': extracols } - res = self.postToGateway(urlstr, params) - return ast.literal_eval(res) + res = ast.literal_eval(self.postToGateway(urlstr, params)) + if res.get('status') == 'OK': + url = 'http://' + self.host_ + ':' + self.port_ + \ + '/assets/global/' + filename + res2 = requests.get(url) + with open(filename, 'wb') as local_file: + local_file.write(res2.content) + local_file.close() + reader = csv.DictReader(open(filename, 'r')) + os.remove(filename) + for row in reader: + return row + else: + return res diff --git a/connector_voicent/models/__init__.py b/connector_voicent/models/__init__.py index 46f473c..f6f7d9f 100644 --- a/connector_voicent/models/__init__.py +++ b/connector_voicent/models/__init__.py @@ -1,5 +1,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import res_partner +from . import backend_voicent_call_line +from . import backend_voicent_time_line from . import backend_voicent -from . import queue_job diff --git a/connector_voicent/models/backend_voicent.py b/connector_voicent/models/backend_voicent.py index 48320f7..c00777b 100644 --- a/connector_voicent/models/backend_voicent.py +++ b/connector_voicent/models/backend_voicent.py @@ -7,58 +7,46 @@ from odoo import api, fields, models from datetime import datetime, timedelta from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT from pytz import timezone -from ..examples import voicent class BackendVoicent(models.Model): _name = 'backend.voicent' _description = 'Voicent Backend' _inherit = ['connector.backend'] - _rec_name = 'host' - host = fields.Char( - string='Host', - required=True, - ) - port = fields.Integer( - string='Port', - required=True, - ) - next_call = fields.Datetime( - string='Next Call', - copy=False, - ) + name = fields.Char(string='Name', required=True) + host = fields.Char(string='Host', default='localhost', required=True) + port = fields.Integer(string='Port', default='8155', required=True) + callerid = fields.Char(string='Caller ID', required=True) + line = fields.Integer(string='Number of lines', required=True) + next_call = fields.Datetime(string='Next Call', copy=False) call_line_ids = fields.One2many( string='Call Lines', comodel_name='backend.voicent.call.line', - inverse_name='backend_id', - ) + inverse_name='backend_id') time_line_ids = fields.One2many( string='Call Times', comodel_name='backend.voicent.time.line', - inverse_name='backend_id', - ) - is_active = fields.Boolean('Is Active') + inverse_name='backend_id') + active = fields.Boolean('Active', default=True) @api.model - def _run_check_the_voicent_status(self): - ''' This method is called from a cron job. ''' + def _run_update_next_call(self): + """ This method is called from a cron job. """ cr_time_list = [] - is_next_day = False - backend_voicent_rec = self.search([('is_active', '=', True)]) - for backend_voicent in backend_voicent_rec: + backends = self.search([('active', '=', True)]) + for backend in backends: current_dt = datetime.now(timezone('UTC')) user_tz = timezone( self.env.context.get('tz') or self.env.user.tz or 'UTC') dt_value = current_dt.astimezone(user_tz) - convt_dt_strf = dt_value.strftime( - DEFAULT_SERVER_DATETIME_FORMAT) + convt_dt_strf = dt_value.strftime(DEFAULT_SERVER_DATETIME_FORMAT) convt_dt = datetime.strptime( convt_dt_strf, DEFAULT_SERVER_DATETIME_FORMAT) current_time = convt_dt.strftime("%H:%M") - for time_line_rec in backend_voicent.time_line_ids: + for time_line_rec in backend.time_line_ids: hours, minutes = divmod(abs(time_line_rec.time) * 60, 60) minutes = round(minutes) if minutes == 60: @@ -67,64 +55,25 @@ class BackendVoicent(models.Model): line_time = '%02d:%02d' % (hours, minutes) cr_time_list.append(line_time) cr_time_list = sorted(cr_time_list) - next_call = datetime.now() - for each_time_entry in cr_time_list: - if each_time_entry > current_time and not is_next_day: + next_call = False + for time_entry in cr_time_list: + if time_entry > current_time: next_call = datetime.now().replace( - hour=int(each_time_entry.split(':')[0]), - minute=int(each_time_entry.split(':')[1])) - is_next_day = True - if cr_time_list and not is_next_day: + hour=int(time_entry.split(':')[0]), + minute=int(time_entry.split(':')[1]), + second=0) + break + if not next_call: next_call = datetime.now().replace( hour=int(cr_time_list[0].split(':')[0]), - minute=int(cr_time_list[0].split(':')[1])) + timedelta( - days=1) - next_call_tz = timezone(self.env.context.get( - 'tz') or self.env.user.tz).localize(next_call, is_dst=False) + minute=int(cr_time_list[0].split(':')[1]), + second=0) + timedelta( + days=0) + next_call_tz = timezone(self.env.context.get('tz') or + self.env.user.tz).localize(next_call, + is_dst=False) next_call_utc = next_call_tz.astimezone(timezone('UTC')) next_call_utc = datetime.strptime( fields.Datetime.to_string(next_call_utc), DEFAULT_SERVER_DATETIME_FORMAT) - backend_voicent.next_call = fields.Datetime.to_string( - next_call_utc) - - -class BackendVoicentTimeLine(models.Model): - _name = 'backend.voicent.time.line' - _description = 'Voicent Backend Time Line' - - name = fields.Char( - string='Name', - required=True, - ) - time = fields.Float( - string='Time', - copy=False, - ) - backend_id = fields.Many2one( - string='Backend', - comodel_name='backend.voicent', - ondelete='set null', - ) - - -class BackendVoicentCallLine(models.Model): - _name = 'backend.voicent.call.line' - _description = 'Voicent Backend Call Line' - - name = fields.Char( - string='Name', - required=True, - ) - applies_on = fields.Selection( - string='Applies on', - selection=[], - ) - voicent_app = fields.Char( - string='Voicent App', - ) - backend_id = fields.Many2one( - string='Backend', - comodel_name='backend.voicent', - ondelete='set null', - ) + backend.next_call = fields.Datetime.to_string(next_call_utc) diff --git a/connector_voicent/models/backend_voicent_call_line.py b/connector_voicent/models/backend_voicent_call_line.py new file mode 100644 index 0000000..3656e02 --- /dev/null +++ b/connector_voicent/models/backend_voicent_call_line.py @@ -0,0 +1,109 @@ +# Copyright (C) 2019 Open Source Integrators +# +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +VOICENT_CONTACT_COLUMNS = [('First Name', 'First Name (Required)'), + ('Last Name', 'Last Name'), + ('Business', 'Business'), + ('Phone', 'Phone (Required)'), + ('Email', 'Email'), + ('Category', 'Category'), + ('Assigned To', 'Assigned To'), + ('Contact Status', 'Contact Status'), + ('Lead Source', 'Lead Source'), + ('Other', 'Other')] + +VOICENT_REPLY = [('availableagents', 'Available Agents'), + ('callback', 'Callback'), + ('campid', 'Campaign ID'), + ('campname', 'Campaign Name'), + ('campsize', 'Campaign Size'), + ('connected', 'Connected'), + ('dnc', 'Contact DNC'), + ('nophone', 'Contact No Phone'), + ('disc', 'Disc. Number'), + ('dropped', 'Dropped'), + ('failed', 'Failed'), + ('fax', 'Fax'), + ('info', 'Info'), + ('in', 'Interested'), + ('lines', 'Lines'), + ('linebusy', 'Line Busy'), + ('live', 'Live Answer'), + ('machine', 'Machine Answer'), + ('made', 'Made'), + ('maxlines', 'Max Lines'), + ('noact', 'No Activity'), + ('noanswer', 'No Answer'), + ('notin', 'Not Interested'), + ('notes', 'Notes'), + ('optout', 'Opt Out'), + ('serverr', 'Service Error'), + ('status', 'Status'), + ('totalagents', 'Total Agents'), + ('wit', 'Wit')] + +MSGTYPE = [('audio', 'Audio'), + ('ivr', 'IVR'), + ('survey', 'Survey'), + ('template', 'Template'), + ('tts', 'Text-To-Speech')] + + +class BackendVoicentCallLine(models.Model): + _name = 'backend.voicent.call.line' + _description = 'Voicent Backend Call Line' + + name = fields.Char(string='Name', required=True) + sequence = fields.Integer(string='Sequence', default=0) + applies_on = fields.Selection(string='Applies on', selection=[]) + msgtype = fields.Selection(MSGTYPE, string='Message Type', required=True) + msginfo = fields.Char(string='Message Info') + backend_id = fields.Many2one( + string='Backend', + comodel_name='backend.voicent', + ondelete='set null') + reply_ids = fields.One2many('backend.voicent.call.line.reply', 'line_id', + string="Replies") + contact_ids = fields.One2many('backend.voicent.call.line.contact', + 'line_id', + string="Contact Info") + + +class BackendVoicentCallLineContact(models.Model): + _name = 'backend.voicent.call.line.contact' + _description = 'Columns of the CSV file to provide the contact list' + _order = 'sequence' + + name = fields.Selection(VOICENT_CONTACT_COLUMNS, string='Voicent Field', + required=True) + other = fields.Char(string='Other') + sequence = fields.Integer(string='Sequence', default=0) + field_domain = fields.Char(string='Odoo Field', + required=True) + default_value = fields.Char(string='Default Value', required=True) + line_id = fields.Many2one( + string='Call Line', + comodel_name='backend.voicent.call.line', + ondelete='set null') + + +class BackendVoicentCallLineReply(models.Model): + _name = 'backend.voicent.call.line.reply' + _description = 'Reply to a Voicent Call' + + name = fields.Char(string='Name', required=True) + line_id = fields.Many2one( + string='Call Line', + comodel_name='backend.voicent.call.line', + ondelete='set null') + reply_field = fields.Selection(VOICENT_REPLY, string="Voicent Reply Field", + required=True) + reply_value = fields.Char(string="Voicent Reply Value", required=True) + action_id = fields.Many2one('ir.actions.server', string="Server Action", + required=True, + help="""If the Voicent reply field is equal to + the Voicent reply value, the server action is + executed.""") diff --git a/connector_voicent/models/backend_voicent_time_line.py b/connector_voicent/models/backend_voicent_time_line.py new file mode 100644 index 0000000..7d0760f --- /dev/null +++ b/connector_voicent/models/backend_voicent_time_line.py @@ -0,0 +1,18 @@ +# Copyright (C) 2019 Open Source Integrators +# +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class BackendVoicentTimeLine(models.Model): + _name = 'backend.voicent.time.line' + _description = 'Voicent Backend Time Line' + _order = 'time' + + name = fields.Char(string='Name', required=True) + time = fields.Float(string='Time', copy=False) + backend_id = fields.Many2one( + string='Backend', + comodel_name='backend.voicent', + ondelete='set null') diff --git a/connector_voicent/models/queue_job.py b/connector_voicent/models/queue_job.py deleted file mode 100644 index 8c3e8be..0000000 --- a/connector_voicent/models/queue_job.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (C) 2019 Open Source Integrators -# -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class QueueJob(models.Model): - """ Job status and result """ - _inherit = 'queue.job' - - call_line_id = fields.Many2one( - 'backend.voicent.call.line', 'Call Line', - ) - voicent_campaign = fields.Text( - 'Campaign ID', - ) diff --git a/connector_voicent/models/res_partner.py b/connector_voicent/models/res_partner.py index 6d2359f..d3e8e5f 100644 --- a/connector_voicent/models/res_partner.py +++ b/connector_voicent/models/res_partner.py @@ -8,6 +8,4 @@ from odoo import fields, models class ResPartner(models.Model): _inherit = 'res.partner' - can_call = fields.Boolean( - string='Accepts Calls', - ) + can_call = fields.Boolean(string='Accepts Voicent Calls', default=True) diff --git a/connector_voicent/readme/CONFIGURATION.rst b/connector_voicent/readme/CONFIGURATION.rst new file mode 100644 index 0000000..8fa1155 --- /dev/null +++ b/connector_voicent/readme/CONFIGURATION.rst @@ -0,0 +1,6 @@ +#. Go to Settings > Users & Companies > Users +#. Select the filter "Inactive Users" only +#. Select the user "OdooBot" +#. Click on "Edit" +#. Go to the tab "Preferences" +#. Set the timezone diff --git a/connector_voicent/readme/ROADMAP.rst b/connector_voicent/readme/ROADMAP.rst new file mode 100644 index 0000000..c793651 --- /dev/null +++ b/connector_voicent/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* On the backend, replace the 'Applies On' selection list with a Many2one to ir.model.fields +* On the backend call line contact, use the domain widget to select the field +* Update the voicent library on Pypi with examples/voicent.py diff --git a/connector_voicent/readme/USAGE.rst b/connector_voicent/readme/USAGE.rst index 5272d0a..f6907cc 100644 --- a/connector_voicent/readme/USAGE.rst +++ b/connector_voicent/readme/USAGE.rst @@ -1,4 +1,6 @@ #. Go to Connectors > Backends > Voicent Backends -#. Create a new Voicent Backend with the host and port +#. Create a new Voicent Backend with the host, port, the caller ID and the number of lines #. Create Call Lines to determine when (which stage in the process) calls are added to the queue +#. Create Contact Info to create the structure of the CSV file to send to Voicent +#. Create Replies to determine what to do based on the replies (see example below) #. Create Time Line to determine when (what time) calls are made diff --git a/connector_voicent/security/ir.model.access.csv b/connector_voicent/security/ir.model.access.csv index d9bbcff..00ecd4e 100644 --- a/connector_voicent/security/ir.model.access.csv +++ b/connector_voicent/security/ir.model.access.csv @@ -1,4 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_backend_voicent,access_backend_voicent,model_backend_voicent,base.group_user,1,1,1,1 -access_backend_voicent_call_line,access_backend_voicent_call_line,model_backend_voicent_call_line,base.group_user,1,1,1,1 -access_backend_voicent_time_line,access_backend_voicent_time_line,model_backend_voicent_time_line,base.group_user,1,1,1,1 +access_backend_voicent,access_backend_voicent,model_backend_voicent,connector.group_connector_manager,1,1,1,1 +access_backend_voicent_call_line,access_backend_voicent_call_line,model_backend_voicent_call_line,connector.group_connector_manager,1,1,1,1 +access_backend_voicent_call_line_reply,access_backend_voicent_call_line_reply,model_backend_voicent_call_line_reply,connector.group_connector_manager,1,1,1,1 +access_backend_voicent_call_line_contact,access_backend_voicent_call_line_contact,model_backend_voicent_call_line_contact,connector.group_connector_manager,1,1,1,1 +access_backend_voicent_time_line,access_backend_voicent_time_line,model_backend_voicent_time_line,connector.group_connector_manager,1,1,1,1 diff --git a/connector_voicent/static/description/icon.png b/connector_voicent/static/description/icon.png index 3a0328b..40429d1 100644 Binary files a/connector_voicent/static/description/icon.png and b/connector_voicent/static/description/icon.png differ diff --git a/connector_voicent/tests/test_backend_voicent.py b/connector_voicent/tests/test_backend_voicent.py index 95fd16f..35bf588 100644 --- a/connector_voicent/tests/test_backend_voicent.py +++ b/connector_voicent/tests/test_backend_voicent.py @@ -10,15 +10,18 @@ class TestBackendVoicent(TransactionCase): def setUp(self): super(TestBackendVoicent, self).setUp() self.backend_voicent_model = self.env['backend.voicent'] - self.backend_voicent_id = self.backend_voicent_model.create( - {'host': 'localhost', + {'name': 'Test', + 'host': 'localhost', 'port': '8155', - 'is_active': True, + 'callerid': '0000000000', + 'line': '1', + 'active': True, 'call_line_ids': [(0, 0, {'name': 'call 1', 'applies_on': False, - 'voicent_app': 'App'})], + 'msgtype': 'tts', + 'msginfo': 'Hello World!'})], 'time_line_ids': [(0, 0, {'name': 'Call Time 1', 'time': 10.0}), (0, 0, {'name': 'Call Time 2', @@ -31,4 +34,4 @@ class TestBackendVoicent(TransactionCase): def test_run_check_the_voicent_status(self): """To call the scheduler method.""" - self.backend_voicent_id._run_check_the_voicent_status() + self.backend_voicent_id._run_update_next_call() diff --git a/connector_voicent/view/queue_job_view.xml b/connector_voicent/view/queue_job_view.xml deleted file mode 100644 index f22f39b..0000000 --- a/connector_voicent/view/queue_job_view.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - queue.job.form.inherit.voicent - queue.job - - - - - - - - - - - - diff --git a/connector_voicent/view/backend_voicent.xml b/connector_voicent/views/backend_voicent.xml similarity index 61% rename from connector_voicent/view/backend_voicent.xml rename to connector_voicent/views/backend_voicent.xml index 181c238..c9a3269 100644 --- a/connector_voicent/view/backend_voicent.xml +++ b/connector_voicent/views/backend_voicent.xml @@ -1,37 +1,54 @@ - backend.voicent.tree.view backend.voicent - + + - - + + + + - + backend.voicent.form.view backend.voicent
+
+ +
+
+

+
- - - + + + + + + + + - + + - + + @@ -43,12 +60,20 @@ + + + + + + + +
- + backend.voicent.search.view backend.voicent @@ -56,11 +81,13 @@ - + + - + ir.actions.act_window Voicent Backend @@ -87,5 +114,5 @@ sequence="10" action="action_backend_voicent_act_window" groups="connector.group_connector_manager"/> - +
diff --git a/connector_voicent/views/backend_voicent_call_line.xml b/connector_voicent/views/backend_voicent_call_line.xml new file mode 100644 index 0000000..98b41de --- /dev/null +++ b/connector_voicent/views/backend_voicent_call_line.xml @@ -0,0 +1,76 @@ + + + + backend.voicent.call.line.tree.view + backend.voicent.call.line + + + + + + + + + + + + backend.voicent.call.line.form.view + backend.voicent.call.line + +
+ +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
diff --git a/connector_voicent/view/res_partner.xml b/connector_voicent/views/res_partner.xml similarity index 53% rename from connector_voicent/view/res_partner.xml rename to connector_voicent/views/res_partner.xml index 7c7c214..805002f 100644 --- a/connector_voicent/view/res_partner.xml +++ b/connector_voicent/views/res_partner.xml @@ -1,4 +1,3 @@ - @@ -6,11 +5,10 @@ res.partner - - - - - + + +