Youness MAAFI
6 years ago
committed by
Maxime Chambreuil
23 changed files with 484 additions and 0 deletions
-
13connector_voicent/README.rst
-
1connector_voicent/__init__.py
-
30connector_voicent/__manifest__.py
-
32connector_voicent/examples/prototype.py
-
2connector_voicent/examples/voicent.csv
-
187connector_voicent/examples/voicent.py
-
4connector_voicent/models/__init__.py
-
33connector_voicent/models/backend_voicent.py
-
25connector_voicent/models/backend_voicent_call_line.py
-
23connector_voicent/models/backend_voicent_time_line.py
-
11connector_voicent/models/res_partner.py
-
1connector_voicent/readme/CONFIGURE.rst
-
2connector_voicent/readme/CONTRIBUTORS.rst
-
0connector_voicent/readme/CREDITS.rst
-
2connector_voicent/readme/DESCRIPTION.rst
-
0connector_voicent/readme/HISTORY.rst
-
1connector_voicent/readme/INSTALL.rst
-
0connector_voicent/readme/ROADMAP.rst
-
4connector_voicent/readme/USAGE.rst
-
4connector_voicent/security/ir.model.access.csv
-
BINconnector_voicent/static/description/icon.png
-
94connector_voicent/view/backend_voicent.xml
-
15connector_voicent/view/res_partner.xml
@ -0,0 +1,13 @@ |
|||
**Connector Voicent** |
|||
|
|||
*This module allows you to connect Odoo with Voicent (https://www.voicent.com).* |
|||
|
|||
* **DESCRIPTION.rst** |
|||
* **USAGE.rst** |
|||
* **CONTRIBUTORS.rst** |
|||
* INSTALL.rst |
|||
* CONFIGURE.rst |
|||
* DEVELOP.rst |
|||
* ROADMAP.rst |
|||
* HISTORY.rst |
|||
* CREDITS.rst |
@ -0,0 +1 @@ |
|||
from . import models |
@ -0,0 +1,30 @@ |
|||
# @TODO: To use the right License. |
|||
# @TODO: Description, Website, Contributros..etc. |
|||
{ |
|||
'name': 'Voicent Connector', |
|||
'version': '12.0.1.0.0', |
|||
'category': 'Phone', |
|||
'license': 'AGPL-3', |
|||
'summary': 'Automate phone calls based on stage changes', |
|||
'development_status': 'Stable', |
|||
'maintainers': [ |
|||
'younessmaafi', |
|||
'max3903', |
|||
], |
|||
'author': 'Maxime Chambreuil, Youness MAAFI,' |
|||
' Odoo Community Association (OCA)', |
|||
'website': 'http://opensourceintegrators.com', |
|||
'depends': [ |
|||
'base', |
|||
], |
|||
'data': [ |
|||
# Security |
|||
'security/ir.model.access.csv', |
|||
# Views |
|||
'view/res_partner.xml', |
|||
'view/backend_voicent.xml', |
|||
], |
|||
'demo': [], |
|||
'installable': True, |
|||
'auto_install': False, |
|||
} |
@ -0,0 +1,32 @@ |
|||
# 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']) |
@ -0,0 +1,2 @@ |
|||
Name,Phone |
|||
Maxime Chambreuil,6024275632 |
@ -0,0 +1,187 @@ |
|||
# Copyright (C) 2018 Voicent |
|||
# Copyright (C) 2019 Open Source Integrators |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
# |
|||
# Documentation available at https://voicent.com/developer/docs/camp-api/ |
|||
|
|||
import ntpath |
|||
import requests |
|||
import ast |
|||
|
|||
|
|||
class Voicent(): |
|||
|
|||
def __init__(self, host="localhost", port="8155"): |
|||
self.host_ = host |
|||
self.port_ = port |
|||
|
|||
def postToGateway(self, urlstr, params, files=None): |
|||
url = "http://" + self.host_ + ":" + self.port_ + urlstr |
|||
res = requests.post(url, params, files=files) |
|||
return res.text |
|||
|
|||
def getReqId(self, rcstr): |
|||
index1 = rcstr.find("[ReqId=") |
|||
if (index1 == -1): |
|||
return "" |
|||
index1 += 7 |
|||
index2 = rcstr.find("]", index1) |
|||
if (index2 == -1): |
|||
return "" |
|||
return rcstr[index1:index2] |
|||
|
|||
def callText(self, phoneno, text, selfdelete): |
|||
urlstr = "/ocall/callreqHandler.jsp" |
|||
params = { |
|||
'info': 'simple text call', |
|||
'phoneno': phoneno, |
|||
'firstocc': 10, |
|||
'txt': text, |
|||
'selfdelete': selfdelete |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
def callAudio(self, phoneno, filename, selfdelete): |
|||
urlstr = "/ocall/callreqHandler.jsp" |
|||
params = { |
|||
'info': 'simple audio call', |
|||
'phoneno': phoneno, |
|||
'firstocc': 10, |
|||
'audiofile': filename, |
|||
'selfdelete': selfdelete |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
def callIvr(self, phoneno, appname, selfdelete): |
|||
urlstr = "/ocall/callreqHandler.jsp" |
|||
params = { |
|||
'info': 'simple text call', |
|||
'phoneno': phoneno, |
|||
'firstocc': 10, |
|||
'startapp': appname, |
|||
'selfdelete': selfdelete |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
def callStatus(self, reqid): |
|||
urlstr = "/ocall/callstatusHandler.jsp" |
|||
params = {'reqid': reqid} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
def callRemove(self, reqId): |
|||
urlstr = "/ocall/callremoveHandler.jsp" |
|||
params = {'reqid': reqId} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
def callTillConfirm(self, vcastexe, vocfile, wavfile, ccode): |
|||
urlstr = "/ocall/callreqHandler.jsp" |
|||
cmdline = "\"" |
|||
cmdline += vocfile |
|||
cmdline += "\"" |
|||
cmdline += " -startnow" |
|||
cmdline += " -confirmcode " |
|||
cmdline += ccode |
|||
cmdline += " -wavfile " |
|||
cmdline += "\"" |
|||
cmdline += wavfile |
|||
cmdline += "\"" |
|||
|
|||
params = { |
|||
'info': 'Simple Call till Confirm', |
|||
'phoneno': '1111111', |
|||
'firstocc': 10, |
|||
'selfdelete': 0, |
|||
'startexec': vcastexe, |
|||
'cmdline': cmdline |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return self.getReqId(res) |
|||
|
|||
################ |
|||
# CAMPAIGN API # |
|||
################ |
|||
|
|||
def importCampaign(self, listname, filepath): |
|||
urlstr = "/ocall/campapi" |
|||
params = { |
|||
'action': 'import', |
|||
'importfile': ntpath.basename(filepath), |
|||
'importfilepath': filepath, |
|||
'profile': 'Test', |
|||
'mod': 'cus', |
|||
'fieldsep': ',', |
|||
'row1': 1, |
|||
'mergeopt': 'empty', |
|||
'leadsrcname': listname |
|||
} |
|||
files = { |
|||
'file': (ntpath.basename(filepath), open(filepath, 'rb')) |
|||
} |
|||
res = self.postToGateway(urlstr, params, files) |
|||
return ast.literal_eval(res) |
|||
|
|||
def runCampaign(self, listname): |
|||
urlstr = "/ocall/campapi" |
|||
params = { |
|||
'action': 'bbp', |
|||
'CAMP_NAME': 'Test', |
|||
'listname': listname, |
|||
'phonecols': 'Phone', |
|||
'lines': '4', |
|||
'calldisps': '', |
|||
'callerid': '+18884728568', |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return ast.literal_eval(res) |
|||
|
|||
def importAndRunCampaign(self, filepath, msgtype, msginfo): |
|||
urlstr = "/ocall/campapi" |
|||
params = { |
|||
'action': 'bbp', |
|||
# Parameters for importing the campaign |
|||
'importfile': ntpath.basename(filepath), |
|||
'importfilepath': filepath, |
|||
# 'profile': 'Test', |
|||
'mod': 'cus', |
|||
'row1': 1, |
|||
'leadsrcname': 'Test', |
|||
# Parameters for running the campaign |
|||
'CAMP_NAME': 'Test', |
|||
'phonecols': 'Phone', |
|||
'lines': '4', |
|||
'calldisps': '', |
|||
'callerid': '+18884728568', |
|||
# Parameters for Autodialer |
|||
'msgtype': msgtype, |
|||
'msginfo': msginfo, |
|||
} |
|||
files = { |
|||
'file': (ntpath.basename(filepath), open(filepath, 'rb')) |
|||
} |
|||
res = self.postToGateway(urlstr, params, files) |
|||
return ast.literal_eval(res) |
|||
|
|||
def checkStatus(self, leadsrc_id): |
|||
urlstr = "/ocall/campapi" |
|||
params = { |
|||
'action': 'campstats', |
|||
'leadsrc_id': leadsrc_id |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return ast.literal_eval(res) |
|||
|
|||
def exportResult(self, camp_id, filename, extracols): |
|||
urlstr = "/ocall/campapi" |
|||
params = { |
|||
'action': 'exportcamp', |
|||
'camp_id_id': camp_id, |
|||
'f': filename, |
|||
'extracols': extracols |
|||
} |
|||
res = self.postToGateway(urlstr, params) |
|||
return ast.literal_eval(res) |
@ -0,0 +1,4 @@ |
|||
from . import res_partner |
|||
from . import backend_voicent |
|||
from . import backend_voicent_call_line |
|||
from . import backend_voicent_time_line |
@ -0,0 +1,33 @@ |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BackendVoicent(models.Model): |
|||
_name = 'backend.voicent' |
|||
_description = 'Voicent Backend' |
|||
_rec_name = 'host' |
|||
|
|||
host = fields.Char( |
|||
string=u'Host', |
|||
required=True, |
|||
) |
|||
port = fields.Integer( |
|||
string=u'Port', |
|||
required=True, |
|||
) |
|||
next_call = fields.Datetime( |
|||
string=u'Next Call', |
|||
copy=False, |
|||
default=lambda self: fields.Datetime.now(), |
|||
) |
|||
call_line_ids = fields.One2many( |
|||
string=u'Call Lines', |
|||
comodel_name='backend.voicent.call.line', |
|||
inverse_name='backend_id', |
|||
) |
|||
time_line_ids = fields.One2many( |
|||
string=u'Call Times', |
|||
comodel_name='backend.voicent.time.line', |
|||
inverse_name='backend_id', |
|||
) |
@ -0,0 +1,25 @@ |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BackendVoicentCallLine(models.Model): |
|||
_name = 'backend.voicent.call.line' |
|||
_description = 'Voicent Backend Call Line' |
|||
|
|||
name = fields.Char( |
|||
string=u'Name', |
|||
required=True, |
|||
) |
|||
applies_on = fields.Selection( |
|||
string=u'Applies on', |
|||
selection=[], |
|||
) |
|||
voicent_app = fields.Char( |
|||
string=u'Voicent App', |
|||
) |
|||
backend_id = fields.Many2one( |
|||
string=u'Backend', |
|||
comodel_name='backend.voicent', |
|||
ondelete='set null', |
|||
) |
@ -0,0 +1,23 @@ |
|||
# 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' |
|||
|
|||
name = fields.Char( |
|||
string=u'Name', |
|||
required=True, |
|||
) |
|||
time = fields.Datetime( |
|||
string=u'Time', |
|||
copy=False, |
|||
default=lambda self: fields.Datetime.now(), |
|||
) |
|||
backend_id = fields.Many2one( |
|||
string=u'Backend', |
|||
comodel_name='backend.voicent', |
|||
ondelete='set null', |
|||
) |
@ -0,0 +1,11 @@ |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from odoo import fields, models |
|||
|
|||
|
|||
class ResPartner(models.Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
can_call = fields.Boolean( |
|||
string=u'Accepts Calls', |
|||
) |
@ -0,0 +1 @@ |
|||
There is no specific configuration procedure for this module. |
@ -0,0 +1,2 @@ |
|||
* Maxime Chambreuil <mchambreuil@opensourceintegrators.com> |
|||
* Youness MAAFI <youness.maafi@gmail.com> |
@ -0,0 +1,2 @@ |
|||
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. |
@ -0,0 +1 @@ |
|||
There is no specific installation procedure for this module. |
@ -0,0 +1,4 @@ |
|||
#. Go to Connectors > Backends > Voicent Backends |
|||
#. Create a new Voicent Backend with the host and port |
|||
#. Create Call Lines to determine when (which stage in the process) calls are added to the queue |
|||
#. Create Time Line to determine when (what time) calls are made |
@ -0,0 +1,4 @@ |
|||
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,0 |
|||
access_backend_voicent_call_line,access_backend_voicent_call_line,model_backend_voicent_call_line,base.group_user,1,1,1,0 |
|||
access_backend_voicent_time_line,access_backend_voicent_time_line,model_backend_voicent_time_line,base.group_user,1,0,1,0 |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,94 @@ |
|||
|
|||
<odoo> |
|||
|
|||
<record id="view_backend_voicent_tree" model="ir.ui.view"> |
|||
<field name="name">view.backend.voicent.tree</field> |
|||
<field name="model">backend.voicent</field> |
|||
<field name="mode">primary</field> |
|||
<field name="arch" type="xml"> |
|||
<tree string="Voicent Backend" create="1" delete="1" edit="1"> |
|||
<field name="host"/> |
|||
<field name="port"/> |
|||
<field name="next_call"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_backend_voicent_form" model="ir.ui.view"> |
|||
<field name="name">view.backend.voicent.form</field> |
|||
<field name="model">backend.voicent</field> |
|||
<field name="mode">primary</field> |
|||
<field name="arch" type="xml"> |
|||
<form string="Voicent Backend" create="1" delete="1" edit="1"> |
|||
<header> |
|||
</header> |
|||
<sheet> |
|||
<group> |
|||
<field name="host"/> |
|||
<field name="port"/> |
|||
</group> |
|||
<separator string="Call Lines"/> |
|||
<field name="call_line_ids"> |
|||
<tree string="Call Lines" create="1" delete="1" edit="1" editable="top"> |
|||
<field name="name"/> |
|||
<field name="applies_on"/> |
|||
<field name="voicent_app"/> |
|||
</tree> |
|||
</field> |
|||
<separator string="Call Times"/> |
|||
<field name="time_line_ids"> |
|||
<tree string="Call Times" create="1" delete="1" edit="1" editable="top"> |
|||
<field name="name"/> |
|||
<field name="time"/> |
|||
</tree> |
|||
</field> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="view_backend_voicent_search" model="ir.ui.view"> |
|||
<field name="name">view.backend.voicent.search</field> |
|||
<field name="model">backend.voicent</field> |
|||
<field name="mode">primary</field> |
|||
<field name="arch" type="xml"> |
|||
<search string="Voicent Backend"> |
|||
<field name="host"/> |
|||
<field name="port"/> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="action_backend_voicent_act_window" model="ir.actions.act_window"> |
|||
<field name="type">ir.actions.act_window</field> |
|||
<field name="name">Voicent Backend</field> |
|||
<field name="res_model">backend.voicent</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="view_type">form</field> |
|||
<field name="search_view_id" ref="view_backend_voicent_search"/> |
|||
<field name="help" type="html"> |
|||
<p class="oe_view_nocontent_create"> |
|||
Click to add new Voicent Backend |
|||
</p> |
|||
</field> |
|||
</record> |
|||
|
|||
<record id="menu_connectors_root" model="ir.ui.menu" > |
|||
<field name="name">Connectors</field> |
|||
<field name="sequence" eval="10" /> |
|||
</record> |
|||
|
|||
<record id="menu_connectors_backends" model="ir.ui.menu" > |
|||
<field name="name">Backends</field> |
|||
<field name="sequence" eval="10" /> |
|||
<field name="parent_id" ref="connector_voicent.menu_connectors_root" /> |
|||
</record> |
|||
|
|||
<record id="menu_backend_voicent" model="ir.ui.menu" > |
|||
<field name="name">Voicent Backends</field> |
|||
<field name="sequence" eval="10" /> |
|||
<field name="action" ref="action_backend_voicent_act_window" /> |
|||
<field name="parent_id" ref="connector_voicent.menu_connectors_backends" /> |
|||
</record> |
|||
|
|||
</odoo> |
@ -0,0 +1,15 @@ |
|||
|
|||
<odoo> |
|||
<record id="view_res_partner_form" model="ir.ui.view"> |
|||
<field name="name">view.res.partner.form</field> |
|||
<field name="model">res.partner</field> |
|||
<field name="inherit_id" ref="base.view_partner_form"/> |
|||
<field name="arch" type="xml"> |
|||
<xpath expr="//h1//field[@name='name']/.." position="after"> |
|||
<group attrs="{'invisible': [('is_company', '=', True)]}"> |
|||
<field name="can_call"/> |
|||
</group> |
|||
</xpath> |
|||
</field> |
|||
</record> |
|||
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue