From d02d51a685c372e108d9ee156d5774e77a457a73 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Wed, 19 Jun 2019 11:50:09 -0500 Subject: [PATCH] [FIX] connector_voicent --- connector_voicent/__manifest__.py | 12 +- ...eck_the_voicent_status.xml => ir_cron.xml} | 4 +- connector_voicent/examples/prototype.py | 32 ----- connector_voicent/examples/voicent.csv | 2 - connector_voicent/examples/voicent.py | 52 +++++--- connector_voicent/models/__init__.py | 3 +- connector_voicent/models/backend_voicent.py | 111 +++++------------- .../models/backend_voicent_call_line.py | 109 +++++++++++++++++ .../models/backend_voicent_time_line.py | 18 +++ connector_voicent/models/queue_job.py | 17 --- connector_voicent/models/res_partner.py | 4 +- connector_voicent/readme/CONFIGURATION.rst | 6 + connector_voicent/readme/ROADMAP.rst | 3 + connector_voicent/readme/USAGE.rst | 4 +- .../security/ir.model.access.csv | 8 +- connector_voicent/static/description/icon.png | Bin 9455 -> 22045 bytes .../tests/test_backend_voicent.py | 13 +- connector_voicent/view/queue_job_view.xml | 18 --- .../{view => views}/backend_voicent.xml | 55 ++++++--- .../views/backend_voicent_call_line.xml | 76 ++++++++++++ .../{view => views}/res_partner.xml | 10 +- 21 files changed, 348 insertions(+), 209 deletions(-) rename connector_voicent/data/{check_the_voicent_status.xml => ir_cron.xml} (78%) delete mode 100644 connector_voicent/examples/prototype.py delete mode 100644 connector_voicent/examples/voicent.csv create mode 100644 connector_voicent/models/backend_voicent_call_line.py create mode 100644 connector_voicent/models/backend_voicent_time_line.py delete mode 100644 connector_voicent/models/queue_job.py create mode 100644 connector_voicent/readme/CONFIGURATION.rst create mode 100644 connector_voicent/readme/ROADMAP.rst delete mode 100644 connector_voicent/view/queue_job_view.xml rename connector_voicent/{view => views}/backend_voicent.xml (61%) create mode 100644 connector_voicent/views/backend_voicent_call_line.xml rename connector_voicent/{view => views}/res_partner.xml (53%) 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 3a0328b516c4980e8e44cdb63fd945757ddd132d..40429d11f57cde31b1946cb1b22fef991a2b9506 100644 GIT binary patch literal 22045 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�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 M07*qoM6N<$g0+>V>i_@% literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I 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 - - - - - + + +