Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

17.0 add connector phone cloudcti event manager #315

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions connector_phone_cloudcti_event_manager/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:alt: License LGPL-3

======================================
Connector Phone CloudCTI Event Manager
======================================

This module integrates odoo with Phone CloudCTI Event Manager.


Credits
=======

Contributors
------------

* Murtuza Saleh <msaleh@opensourceintegrators.com>
* Balaji Kannan <bkannan@opensourceintegrators.com>
2 changes: 2 additions & 0 deletions connector_phone_cloudcti_event_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import controllers
32 changes: 32 additions & 0 deletions connector_phone_cloudcti_event_manager/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "Connector Phone CloudCTI Event Manager",
"category": "web",
"summary": "This module integrates odoo with Phone Connector CloudCTI.",
"version": "17.0.1.0.0",
"author": "Open Source Integrators, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/connector-telephony",
"depends": [
"base_phone_cdr",
"inputmask_widget",
"sale",
"stock",
"crm",
"sale_crm",
],
"data": [
"views/res_company.xml",
"views/res_users_view.xml",
"views/res_partner_views.xml",
"views/phone_cdr_view.xml",
"views/sale_order_views.xml",
"views/stock_picking_views.xml",
"views/crm_views.xml",
],
"assets": {
"web.assets_backend": [
"connector_phone_cloudcti_event_manager/static/src/services/*.js",
]
},
"license": "AGPL-3",
"external_dependencies": {"python": ["phonenumbers"]},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
157 changes: 157 additions & 0 deletions connector_phone_cloudcti_event_manager/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import json
import logging
import re
from datetime import datetime

import phonenumbers
import pytz

from odoo import http
from odoo.http import Response, request

_logger = logging.getLogger(__name__)


class CloudCTIVOIP(http.Controller):
def map_state(self, instate, currentstate=False):
outstate = "on_hold"
if instate == "ringing":
outstate = "offering"
elif instate == "answered":
outstate = "connected"
elif instate == "ended":
if currentstate == "offering":
outstate = "missed"
elif currentstate == "connected":
outstate = "completed"
else:
outstate = "on_hold"
return outstate

def create_cdr_record(self, user, payload):
startdate = False
if payload.get("starttime"):
startdate = self.convert_into_correct_timezone(
payload.get("starttime"), user
)
vals = {
"guid": payload.get("callid"),
"inbound_flag": payload.get("direction").lower(),
"called_id": payload.get("calledid"),
"called_id_name": user.name,
"caller_id": payload.get("callerid"),
"call_start_time": startdate,
"state": self.map_state(payload.get("state")),
"user_id": user.id,
"partner_ids": payload.get("partner_ids"),
}
return request.env["phone.cdr"].sudo().create(vals)

def convert_into_correct_timezone(self, record_date, user):
# CloudCTI provides date in UTC, so no conversion needed.
return re.sub(r"[TtzZ]", " ", record_date)
record_date = datetime.strptime(record_date, "%Y-%m-%d %H:%M:%S")
timezone = request.env.context.get("tz", False) or user.partner_id.tz
return_date = None
if timezone:
src_tz = pytz.timezone("UTC")
dst_tz = pytz.timezone(timezone)
return_date = dst_tz.localize(record_date).astimezone(src_tz)
return return_date

@http.route("/cloudCTI/statusChange", type="json", auth="public")
def cloudcti_status_change(self, *args, **kw):
# check for data
if kw:
guid = kw.get("CallId")
callednumber = kw.get("CalledNumber")
callernumber = kw.get("CallerNumber")
direction = kw.get("Direction")
state = kw.get("State")
starttime = kw.get("StartTime") or False
endtime = kw.get("EndTime") or False
duration = kw.get("CallDuration") or 0.0
_logger.info("Webhook ---- %s", kw)
else:
return Response(json.dumps({}))
phone = other = False
if direction == "inbound":
phone = callednumber
other = callernumber
create = True if state == "ringing" else False
check = True if state == "answered" else False
elif direction == "outbound":
phone = callernumber
other = callednumber
create = True if state == "ringing" else False
check = True if state == "answered" else False
phone = phonenumbers.format_number(
phonenumbers.parse(phone, "US"), phonenumbers.PhoneNumberFormat.NATIONAL
)
other = phonenumbers.format_number(
phonenumbers.parse(other, "US"), phonenumbers.PhoneNumberFormat.NATIONAL
)
user = request.env["res.users"].sudo().search([("phone", "=", phone)], limit=1)
if not user:
user = request.env["res.users"].sudo().browse(1)
if not user:
return Response(json.dumps({"message": "User Not found.", "status": 404}))
else:
partner = (
request.env["phone.common"].sudo().get_record_from_phone_number(other)
)
if create:
payload = {
"callid": guid,
"callerid": phone if direction == "outbound" else other,
"calledid": phone if direction == "inbound" else other,
"direction": direction,
"state": state,
"starttime": starttime,
"partner_ids": [(6, 0, partner.ids)],
}
_logger.info("CDR Payload ---- %s", payload)
cdr = self.create_cdr_record(user, payload)
# if it is not external call, and incoming, only cdr is needed, exit here
if user.id > 1 and direction.lower() == "inbound" and cdr:
return (
request.env["phone.common"]
.sudo()
.incall_notify_by_login(
other,
[user.login],
calltype="Incoming Call",
)
)
else:
return Response(json.dumps({}))
else:
cdr = (
request.env["phone.cdr"]
.sudo()
.search([("guid", "=", guid)], limit=1)
)
# need to check and create record
if check and not cdr:
payload = {
"callid": guid,
"callerid": phone if direction == "outbound" else other,
"calledid": phone if direction == "inbound" else other,
"direction": direction,
"state": state,
"starttime": starttime,
"partner_ids": [(6, 0, partner.ids)],
}
_logger.info("CDR Payload ---- %s", payload)
cdr = self.create_cdr_record(user, payload)

enddate = False
if endtime:
enddate = self.convert_into_correct_timezone(endtime, user)
payload = {
"state": self.map_state(state, cdr.state),
"call_end_time": enddate,
"call_duration": duration,
}
cdr.sudo().write(payload)
return Response(json.dumps({}))
8 changes: 8 additions & 0 deletions connector_phone_cloudcti_event_manager/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from . import res_users
from . import phone_common
from . import phone_cdr
from . import res_company
from . import res_partner
from . import sale_order
from . import stock_picking
from . import crm_lead
11 changes: 11 additions & 0 deletions connector_phone_cloudcti_event_manager/models/crm_lead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import models


class CRMLead(models.Model):
_inherit = "crm.lead"

def cloudcti_open_outgoing_notification(self):
called_id = self._context.get("call_no")
caller_id = self.env.user.phone
if caller_id and called_id and caller_id != called_id:
self.partner_id.cloudcti_outgoing_call_notification()
12 changes: 12 additions & 0 deletions connector_phone_cloudcti_event_manager/models/phone_cdr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models


class PhoneCDR(models.Model):
_inherit = "phone.cdr"

def cloudcti_open_outgoing_notification(self):
called_id = self._context.get("call_no")
caller_id = self.env.user.phone
if caller_id and called_id and caller_id != called_id:
if self.partner_id:
self.partner_id.cloudcti_outgoing_call_notification()
73 changes: 73 additions & 0 deletions connector_phone_cloudcti_event_manager/models/phone_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging

from odoo import _, api, models

_logger = logging.getLogger(__name__)


class PhoneCommon(models.AbstractModel):
_inherit = "phone.common"

def get_record_from_phone_number(self, presented_number):
_logger.debug(
"Call get_name_from_phone_number with number = %s" % presented_number
)
if not isinstance(presented_number, str):
_logger.warning(
f"Number {presented_number} should be a 'str' but it is a {type(presented_number)} "
)
return False
if not presented_number.isdigit():
_logger.warning(
"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
)
partners = []
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)
]
partners = (
self.env["res.partner"]
.sudo()
.search(
[
"|",
("phone", "ilike", end_number_to_match),
("mobile", "ilike", end_number_to_match),
]
)
)
return partners

def incall_notify_by_login_test(self, number, login_list):
return self.incall_notify_by_login("(582) 126-8105", ["admin"])

@api.model
def incall_notify_by_login(self, number, login_list, calltype="Incoming Call"):
assert isinstance(login_list, list), "login_list must be a list"
partners = self.sudo().get_record_from_phone_number(number)
response = False
if partners:
user = self.env["res.users"].sudo().search([("login", "in", login_list)])
if len(partners.ids) > 1:
name = "Multiple Records"
else:
name = partners[0].name
bus_message = {
"message": _(calltype + " from : " + name),
"title": _(calltype),
"action_link_name": "action_link_name",
"notification": "IncomingNotification",
"id": partners.ids,
"type": "default",
}
self.sudo().env["bus.bus"]._sendone(
self.env.user.partner_id, "web.notify.incoming", [bus_message]
)
_logger.debug("This action has been sent to user ID %d" % (user.id))
response = partners
return response
32 changes: 32 additions & 0 deletions connector_phone_cloudcti_event_manager/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

cloudcti_base_url = fields.Char(
"CloudCTI Base URL", default="https://api.cloudcti.nl/api/v2"
)
cloudcti_out_url = fields.Char(
"CloudCTI Signin URL",
default="https://useraccount.cloudcti.nl/phone/api/callcontrol",
)
cloudcti_signin_url = fields.Char(
"CloudCTI Signin URL", default="https://signin-va.cloudcti.nl/signin/api/token"
)
cloudcti_subscription_url = fields.Char(
"CloudCTI Subscription URL",
default="https://useraccount.cloudcti.nl/phone/api/Subscription",
)
cloudcti_popup_time = fields.Integer(string="Popup Time (Sec)", default=5)

def get_popup_time(self):
return self.sudo().cloudcti_popup_time


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

popup_time = fields.Integer(
related="company_id.cloudcti_popup_time", readonly=False
)
Loading
Loading