Merge pull request #534 from shariquerik/telephony
This commit is contained in:
commit
a242ef162a
@ -5,7 +5,16 @@ import frappe
|
||||
def get_users():
|
||||
users = frappe.qb.get_query(
|
||||
"User",
|
||||
fields=["name", "email", "enabled", "user_image", "first_name", "last_name", "full_name", "user_type"],
|
||||
fields=[
|
||||
"name",
|
||||
"email",
|
||||
"enabled",
|
||||
"user_image",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"full_name",
|
||||
"user_type",
|
||||
],
|
||||
order_by="full_name asc",
|
||||
distinct=True,
|
||||
).run(as_dict=1)
|
||||
@ -14,11 +23,13 @@ def get_users():
|
||||
if frappe.session.user == user.name:
|
||||
user.session_user = True
|
||||
|
||||
user.is_manager = (
|
||||
"Sales Manager" in frappe.get_roles(user.name) or user.name == "Administrator"
|
||||
)
|
||||
user.is_manager = "Sales Manager" in frappe.get_roles(user.name) or user.name == "Administrator"
|
||||
|
||||
user.is_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name})
|
||||
|
||||
return users
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_contacts():
|
||||
contacts = frappe.get_all(
|
||||
@ -37,7 +48,7 @@ def get_contacts():
|
||||
"mobile_no",
|
||||
"phone",
|
||||
"company_name",
|
||||
"modified"
|
||||
"modified",
|
||||
],
|
||||
order_by="first_name asc",
|
||||
distinct=True,
|
||||
@ -58,18 +69,12 @@ def get_contacts():
|
||||
|
||||
return contacts
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_lead_contacts():
|
||||
lead_contacts = frappe.get_all(
|
||||
"CRM Lead",
|
||||
fields=[
|
||||
"name",
|
||||
"lead_name",
|
||||
"mobile_no",
|
||||
"phone",
|
||||
"image",
|
||||
"modified"
|
||||
],
|
||||
fields=["name", "lead_name", "mobile_no", "phone", "image", "modified"],
|
||||
filters={"converted": 0},
|
||||
order_by="lead_name asc",
|
||||
distinct=True,
|
||||
@ -77,11 +82,12 @@ def get_lead_contacts():
|
||||
|
||||
return lead_contacts
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_organizations():
|
||||
organizations = frappe.qb.get_query(
|
||||
"CRM Organization",
|
||||
fields=['*'],
|
||||
fields=["*"],
|
||||
order_by="name asc",
|
||||
distinct=True,
|
||||
).run(as_dict=1)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("CRM Exotel Agent", {
|
||||
// frappe.ui.form.on("CRM Telephony Agent", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:mobile_no",
|
||||
"autoname": "field:user",
|
||||
"creation": "2025-01-11 16:12:46.602782",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
@ -10,7 +10,15 @@
|
||||
"user_name",
|
||||
"column_break_hdec",
|
||||
"mobile_no",
|
||||
"exotel_number"
|
||||
"default_medium",
|
||||
"section_break_ozjn",
|
||||
"twilio",
|
||||
"twilio_number",
|
||||
"column_break_aydj",
|
||||
"exotel",
|
||||
"exotel_number",
|
||||
"section_break_phlq",
|
||||
"phone_nos"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -20,7 +28,8 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hdec",
|
||||
@ -32,8 +41,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Mobile No.",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "user.full_name",
|
||||
@ -44,19 +52,62 @@
|
||||
"label": "User Name"
|
||||
},
|
||||
{
|
||||
"depends_on": "exotel",
|
||||
"fieldname": "exotel_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Exotel Number"
|
||||
"label": "Exotel Number",
|
||||
"mandatory_depends_on": "exotel"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_phlq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ozjn",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_aydj",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "twilio",
|
||||
"fieldname": "twilio_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Twilio Number",
|
||||
"mandatory_depends_on": "twilio"
|
||||
},
|
||||
{
|
||||
"fieldname": "phone_nos",
|
||||
"fieldtype": "Table",
|
||||
"label": "Phone Numbers",
|
||||
"options": "CRM Telephony Phone"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "twilio",
|
||||
"fieldtype": "Check",
|
||||
"label": "Twilio"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "exotel",
|
||||
"fieldtype": "Check",
|
||||
"label": "Exotel"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_medium",
|
||||
"fieldtype": "Select",
|
||||
"label": "Default Medium",
|
||||
"options": "\nTwilio\nExotel"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-15 20:03:31.162162",
|
||||
"modified": "2025-01-19 14:17:12.880185",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Exotel Agent",
|
||||
"name": "CRM Telephony Agent",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
34
crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py
Normal file
34
crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMTelephonyAgent(Document):
|
||||
def validate(self):
|
||||
self.set_primary()
|
||||
|
||||
def set_primary(self):
|
||||
# Used to set primary mobile no.
|
||||
if len(self.phone_nos) == 0:
|
||||
self.mobile_no = ""
|
||||
return
|
||||
|
||||
is_primary = [phone.number for phone in self.phone_nos if phone.get("is_primary")]
|
||||
|
||||
if len(is_primary) > 1:
|
||||
frappe.throw(
|
||||
_("Only one {0} can be set as primary.").format(frappe.bold(frappe.unscrub("mobile_no")))
|
||||
)
|
||||
|
||||
primary_number_exists = False
|
||||
for d in self.phone_nos:
|
||||
if d.get("is_primary") == 1:
|
||||
primary_number_exists = True
|
||||
self.mobile_no = d.number
|
||||
break
|
||||
|
||||
if not primary_number_exists:
|
||||
self.mobile_no = ""
|
||||
@ -4,7 +4,6 @@
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
@ -12,18 +11,18 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class UnitTestCRMExotelAgent(UnitTestCase):
|
||||
class UnitTestCRMTelephonyAgent(UnitTestCase):
|
||||
"""
|
||||
Unit tests for CRMExotelAgent.
|
||||
Unit tests for CRMTelephonyAgent.
|
||||
Use this class for testing individual functions and methods.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IntegrationTestCRMExotelAgent(IntegrationTestCase):
|
||||
class IntegrationTestCRMTelephonyAgent(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for CRMExotelAgent.
|
||||
Integration tests for CRMTelephonyAgent.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-01-19 13:57:01.702519",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"number",
|
||||
"is_primary"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Number",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_primary",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Primary"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-19 13:58:59.063775",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "CRM Telephony Phone",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@ -5,5 +5,5 @@
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CRMExotelAgent(Document):
|
||||
class CRMTelephonyPhone(Document):
|
||||
pass
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Twilio Settings", {
|
||||
// frappe.ui.form.on("CRM Twilio Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
@ -108,7 +108,7 @@
|
||||
"modified": "2025-01-15 19:35:13.406254",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "Twilio Settings",
|
||||
"name": "CRM Twilio Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -2,13 +2,13 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
from frappe.model.document import Document
|
||||
from twilio.rest import Client
|
||||
|
||||
class TwilioSettings(Document):
|
||||
friendly_resource_name = "Frappe CRM" # System creates TwiML app & API keys with this name.
|
||||
|
||||
class CRMTwilioSettings(Document):
|
||||
friendly_resource_name = "Frappe CRM" # System creates TwiML app & API keys with this name.
|
||||
|
||||
def validate(self):
|
||||
self.validate_twilio_account()
|
||||
@ -33,28 +33,26 @@ class TwilioSettings(Document):
|
||||
frappe.throw(_("Invalid Account SID or Auth Token."))
|
||||
|
||||
def set_api_credentials(self, twilio):
|
||||
"""Generate Twilio API credentials if not exist and update them.
|
||||
"""
|
||||
"""Generate Twilio API credentials if not exist and update them."""
|
||||
if self.api_key and self.api_secret:
|
||||
return
|
||||
new_key = self.create_api_key(twilio)
|
||||
self.api_key = new_key.sid
|
||||
self.api_secret = new_key.secret
|
||||
frappe.db.set_value('Twilio Settings', 'Twilio Settings', {
|
||||
'api_key': self.api_key,
|
||||
'api_secret': self.api_secret
|
||||
})
|
||||
frappe.db.set_value(
|
||||
"CRM Twilio Settings",
|
||||
"CRM Twilio Settings",
|
||||
{"api_key": self.api_key, "api_secret": self.api_secret},
|
||||
)
|
||||
|
||||
def set_application_credentials(self, twilio):
|
||||
"""Generate TwiML app credentials if not exist and update them.
|
||||
"""
|
||||
"""Generate TwiML app credentials if not exist and update them."""
|
||||
credentials = self.get_application(twilio) or self.create_application(twilio)
|
||||
self.twiml_sid = credentials.sid
|
||||
frappe.db.set_value('Twilio Settings', 'Twilio Settings', 'twiml_sid', self.twiml_sid)
|
||||
frappe.db.set_value("CRM Twilio Settings", "CRM Twilio Settings", "twiml_sid", self.twiml_sid)
|
||||
|
||||
def create_api_key(self, twilio):
|
||||
"""Create API keys in twilio account.
|
||||
"""
|
||||
"""Create API keys in twilio account."""
|
||||
try:
|
||||
return twilio.new_keys.create(friendly_name=self.friendly_resource_name)
|
||||
except Exception:
|
||||
@ -66,23 +64,21 @@ class TwilioSettings(Document):
|
||||
return get_public_url(url_path)
|
||||
|
||||
def get_application(self, twilio, friendly_name=None):
|
||||
"""Get TwiML App from twilio account if exists.
|
||||
"""
|
||||
"""Get TwiML App from twilio account if exists."""
|
||||
friendly_name = friendly_name or self.friendly_resource_name
|
||||
applications = twilio.applications.list(friendly_name)
|
||||
return applications and applications[0]
|
||||
|
||||
def create_application(self, twilio, friendly_name=None):
|
||||
"""Create TwilML App in twilio account.
|
||||
"""
|
||||
"""Create TwilML App in twilio account."""
|
||||
friendly_name = friendly_name or self.friendly_resource_name
|
||||
application = twilio.applications.create(
|
||||
voice_method='POST',
|
||||
voice_url=self.get_twilio_voice_url(),
|
||||
friendly_name=friendly_name
|
||||
)
|
||||
voice_method="POST", voice_url=self.get_twilio_voice_url(), friendly_name=friendly_name
|
||||
)
|
||||
return application
|
||||
|
||||
def get_public_url(path: str=None):
|
||||
|
||||
def get_public_url(path: str | None = None):
|
||||
from frappe.utils import get_url
|
||||
return get_url().split(":8", 1)[0] + path
|
||||
|
||||
return get_url().split(":8", 1)[0] + path
|
||||
@ -5,5 +5,5 @@
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestTwilioAgents(UnitTestCase):
|
||||
class TestCRMTwilioSettings(UnitTestCase):
|
||||
pass
|
||||
@ -12,9 +12,7 @@
|
||||
"brand_logo",
|
||||
"favicon",
|
||||
"dropdown_items_tab",
|
||||
"dropdown_items",
|
||||
"calling_tab",
|
||||
"default_calling_medium"
|
||||
"dropdown_items"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -58,23 +56,12 @@
|
||||
"fieldname": "favicon",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Favicon"
|
||||
},
|
||||
{
|
||||
"fieldname": "calling_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Calling"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_calling_medium",
|
||||
"fieldtype": "Select",
|
||||
"label": "Default calling medium",
|
||||
"options": "\nTwilio\nExotel"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-15 17:40:32.784762",
|
||||
"modified": "2025-01-19 14:23:05.981355",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "FCRM Settings",
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Twilio Agents", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -1,78 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:user",
|
||||
"creation": "2023-08-17 19:59:56.239729",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user",
|
||||
"user_name",
|
||||
"call_receiving_device",
|
||||
"column_break_ljne",
|
||||
"twilio_number"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ljne",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "twilio_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Twilio Number",
|
||||
"options": "Phone"
|
||||
},
|
||||
{
|
||||
"fetch_from": "user.full_name",
|
||||
"fieldname": "user_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "User Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Computer",
|
||||
"fieldname": "call_receiving_device",
|
||||
"fieldtype": "Select",
|
||||
"label": "Device",
|
||||
"options": "Computer\nPhone"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-19 21:57:18.626669",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "Twilio Agents",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class TwilioAgents(Document):
|
||||
pass
|
||||
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import UnitTestCase
|
||||
|
||||
|
||||
class TestTwilioSettings(UnitTestCase):
|
||||
pass
|
||||
@ -7,20 +7,42 @@ from crm.utils import are_same_phone_number, parse_phone_number
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_call_integration_enabled():
|
||||
twilio_enabled = frappe.db.get_single_value("Twilio Settings", "enabled")
|
||||
twilio_enabled = frappe.db.get_single_value("CRM Twilio Settings", "enabled")
|
||||
exotel_enabled = frappe.db.get_single_value("CRM Exotel Settings", "enabled")
|
||||
default_calling_medium = frappe.db.get_single_value("FCRM Settings", "default_calling_medium")
|
||||
|
||||
return {
|
||||
"twilio_enabled": twilio_enabled,
|
||||
"exotel_enabled": exotel_enabled,
|
||||
"default_calling_medium": default_calling_medium,
|
||||
"default_calling_medium": get_user_default_calling_medium(),
|
||||
}
|
||||
|
||||
|
||||
def get_user_default_calling_medium():
|
||||
if not frappe.db.exists("CRM Telephony Agent", frappe.session.user):
|
||||
return None
|
||||
|
||||
default_medium = frappe.db.get_value("CRM Telephony Agent", frappe.session.user, "default_medium")
|
||||
|
||||
if not default_medium:
|
||||
return None
|
||||
|
||||
return default_medium
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_default_calling_medium(medium):
|
||||
return frappe.db.set_value("FCRM Settings", "FCRM Settings", "default_calling_medium", medium)
|
||||
if not frappe.db.exists("CRM Telephony Agent", frappe.session.user):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Telephony Agent",
|
||||
"agent": frappe.session.user,
|
||||
"default_medium": medium,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
else:
|
||||
frappe.db.set_value("CRM Telephony Agent", frappe.session.user, "default_medium", medium)
|
||||
|
||||
return get_user_default_calling_medium()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@ -67,17 +67,22 @@ def make_a_call(to_number, from_number=None, caller_id=None):
|
||||
endpoint = get_exotel_endpoint("Calls/connect.json?details=true")
|
||||
|
||||
if not from_number:
|
||||
from_number = frappe.get_value("CRM Exotel Agent", {"user": frappe.session.user}, "mobile_no")
|
||||
from_number = frappe.get_value("CRM Telephony Agent", {"user": frappe.session.user}, "mobile_no")
|
||||
|
||||
if not caller_id:
|
||||
caller_id = frappe.get_value("CRM Exotel Agent", {"user": frappe.session.user}, "exotel_number")
|
||||
caller_id = frappe.get_value("CRM Telephony Agent", {"user": frappe.session.user}, "exotel_number")
|
||||
|
||||
if not caller_id:
|
||||
frappe.throw(
|
||||
_("You do not have Exotel Number set in your Telephony Agent"), title=_("Exotel Number Missing")
|
||||
)
|
||||
|
||||
if caller_id and caller_id not in get_all_exophones():
|
||||
frappe.throw(_("Exotel Number {0} is not valid").format(caller_id), title=_("Invalid Exotel Number"))
|
||||
|
||||
if not from_number:
|
||||
frappe.throw(
|
||||
_("You do not have mobile number set in your Exotel Agent"), title=_("Mobile Number Missing")
|
||||
_("You do not have mobile number set in your Telephony Agent"), title=_("Mobile Number Missing")
|
||||
)
|
||||
|
||||
record_call = frappe.db.get_single_value("CRM Exotel Settings", "record_call")
|
||||
|
||||
@ -11,7 +11,7 @@ from .twilio_handler import IncomingCall, Twilio, TwilioCallDetails
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_enabled():
|
||||
return frappe.db.get_single_value("Twilio Settings", "enabled")
|
||||
return frappe.db.get_single_value("CRM Twilio Settings", "enabled")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@ -14,7 +14,7 @@ class Twilio:
|
||||
|
||||
def __init__(self, settings):
|
||||
"""
|
||||
:param settings: `Twilio Settings` doctype
|
||||
:param settings: `CRM Twilio Settings` doctype
|
||||
"""
|
||||
self.settings = settings
|
||||
self.account_sid = settings.account_sid
|
||||
@ -26,7 +26,7 @@ class Twilio:
|
||||
@classmethod
|
||||
def connect(self):
|
||||
"""Make a twilio connection."""
|
||||
settings = frappe.get_doc("Twilio Settings")
|
||||
settings = frappe.get_doc("CRM Twilio Settings")
|
||||
if not (settings and settings.enabled):
|
||||
return
|
||||
return Twilio(settings=settings)
|
||||
@ -114,11 +114,11 @@ class Twilio:
|
||||
|
||||
@classmethod
|
||||
def get_twilio_client(self):
|
||||
twilio_settings = frappe.get_doc("Twilio Settings")
|
||||
twilio_settings = frappe.get_doc("CRM Twilio Settings")
|
||||
if not twilio_settings.enabled:
|
||||
frappe.throw(_("Please enable twilio settings before making a call."))
|
||||
|
||||
auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", "auth_token")
|
||||
auth_token = get_decrypted_password("CRM Twilio Settings", "CRM Twilio Settings", "auth_token")
|
||||
client = TwilioClient(twilio_settings.account_sid, auth_token)
|
||||
|
||||
return client
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
# Patches added in this section will be executed before doctypes are migrated
|
||||
# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
|
||||
crm.patches.v1_0.move_crm_note_data_to_fcrm_note
|
||||
crm.patches.v1_0.rename_twilio_settings_to_crm_twilio_settings
|
||||
|
||||
[post_model_sync]
|
||||
# Patches added in this section will be executed after doctypes are migrated
|
||||
@ -9,4 +10,5 @@ crm.patches.v1_0.create_email_template_custom_fields
|
||||
crm.patches.v1_0.create_default_fields_layout #10/12/2024
|
||||
crm.patches.v1_0.create_default_sidebar_fields_layout
|
||||
crm.patches.v1_0.update_deal_quick_entry_layout
|
||||
crm.patches.v1_0.update_layouts_to_new_format
|
||||
crm.patches.v1_0.update_layouts_to_new_format
|
||||
crm.patches.v1_0.move_twilio_agent_to_telephony_agent
|
||||
27
crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py
Normal file
27
crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py
Normal file
@ -0,0 +1,27 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("DocType", "CRM Telephony Agent"):
|
||||
frappe.reload_doctype("CRM Telephony Agent", force=True)
|
||||
|
||||
if frappe.db.exists("DocType", "Twilio Agents") and frappe.db.count("Twilio Agents") == 0:
|
||||
return
|
||||
|
||||
agents = frappe.db.sql("SELECT * FROM `tabTwilio Agents`", as_dict=True)
|
||||
if agents:
|
||||
for agent in agents:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Telephony Agent",
|
||||
"creation": agent.get("creation"),
|
||||
"modified": agent.get("modified"),
|
||||
"modified_by": agent.get("modified_by"),
|
||||
"owner": agent.get("owner"),
|
||||
"user": agent.get("user"),
|
||||
"twilio_number": agent.get("twilio_number"),
|
||||
"user_name": agent.get("user_name"),
|
||||
"twilio": True,
|
||||
}
|
||||
)
|
||||
doc.db_insert()
|
||||
@ -0,0 +1,20 @@
|
||||
import frappe
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "Twilio Settings"):
|
||||
frappe.flags.ignore_route_conflict_validation = True
|
||||
rename_doc("DocType", "Twilio Settings", "CRM Twilio Settings")
|
||||
frappe.flags.ignore_route_conflict_validation = False
|
||||
|
||||
frappe.reload_doctype("CRM Twilio Settings", force=True)
|
||||
|
||||
if frappe.db.exists("__Auth", {"doctype": "Twilio Settings"}):
|
||||
Auth = frappe.qb.DocType("__Auth")
|
||||
result = frappe.qb.from_(Auth).select("*").where(Auth.doctype == "Twilio Settings").run(as_dict=True)
|
||||
|
||||
for row in result:
|
||||
frappe.qb.into(Auth).insert(
|
||||
"CRM Twilio Settings", "CRM Twilio Settings", row.fieldname, row.password, row.encrypted
|
||||
).run()
|
||||
@ -67,7 +67,7 @@ import {
|
||||
import { Dialog, Button, Avatar } from 'frappe-ui'
|
||||
import { ref, markRaw, computed, watch, h } from 'vue'
|
||||
|
||||
const { isManager, getUser } = usersStore()
|
||||
const { isManager, isAgent, getUser } = usersStore()
|
||||
|
||||
const user = computed(() => getUser() || {})
|
||||
|
||||
@ -108,20 +108,22 @@ const tabs = computed(() => {
|
||||
label: __('Telephony'),
|
||||
icon: PhoneIcon,
|
||||
component: markRaw(TelephonySettings),
|
||||
condition: () => isManager() || isAgent(),
|
||||
},
|
||||
{
|
||||
label: __('WhatsApp'),
|
||||
icon: WhatsAppIcon,
|
||||
component: markRaw(WhatsAppSettings),
|
||||
condition: () => isWhatsappInstalled.value,
|
||||
condition: () => isWhatsappInstalled.value && isManager(),
|
||||
},
|
||||
{
|
||||
label: __('ERPNext'),
|
||||
icon: ERPNextIcon,
|
||||
component: markRaw(ERPNextSettings),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
],
|
||||
condition: () => isManager(),
|
||||
condition: () => isManager() || isAgent(),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@ -19,17 +19,18 @@
|
||||
<FormControl
|
||||
type="select"
|
||||
v-model="defaultCallingMedium"
|
||||
:label="__('Default calling medium')"
|
||||
:label="__('Default medium')"
|
||||
:options="[
|
||||
{ label: __(''), value: '' },
|
||||
{ label: __('Twilio'), value: 'Twilio' },
|
||||
{ label: __('Exotel'), value: 'Exotel' },
|
||||
]"
|
||||
class="w-1/2"
|
||||
:description="__('Default calling medium for logged in user')"
|
||||
/>
|
||||
|
||||
<!-- Twilio -->
|
||||
<div class="flex flex-col justify-between gap-4">
|
||||
<div v-if="isManager()" class="flex flex-col justify-between gap-4">
|
||||
<span class="text-base font-semibold text-ink-gray-9">
|
||||
{{ __('Twilio') }}
|
||||
</span>
|
||||
@ -37,12 +38,12 @@
|
||||
v-if="twilio?.doc && twilioTabs"
|
||||
:tabs="twilioTabs"
|
||||
:data="twilio.doc"
|
||||
doctype="Twilio Settings"
|
||||
doctype="CRM Twilio Settings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Exotel -->
|
||||
<div class="flex flex-col justify-between gap-4">
|
||||
<div v-if="isManager()" class="flex flex-col justify-between gap-4">
|
||||
<span class="text-base font-semibold text-ink-gray-9">
|
||||
{{ __('Exotel') }}
|
||||
</span>
|
||||
@ -85,14 +86,17 @@ import {
|
||||
call,
|
||||
} from 'frappe-ui'
|
||||
import { defaultCallingMedium } from '@/composables/settings'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { createToast, getRandom } from '@/utils'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const { isManager, isAgent } = usersStore()
|
||||
|
||||
const twilioFields = createResource({
|
||||
url: 'crm.api.doc.get_fields',
|
||||
cache: ['fields', 'Twilio Settings'],
|
||||
cache: ['fields', 'CRM Twilio Settings'],
|
||||
params: {
|
||||
doctype: 'Twilio Settings',
|
||||
doctype: 'CRM Twilio Settings',
|
||||
allow_all_fieldtypes: true,
|
||||
},
|
||||
auto: true,
|
||||
@ -109,8 +113,8 @@ const exotelFields = createResource({
|
||||
})
|
||||
|
||||
const twilio = createDocumentResource({
|
||||
doctype: 'Twilio Settings',
|
||||
name: 'Twilio Settings',
|
||||
doctype: 'CRM Twilio Settings',
|
||||
name: 'CRM Twilio Settings',
|
||||
fields: ['*'],
|
||||
auto: true,
|
||||
setValue: {
|
||||
@ -273,6 +277,9 @@ function update() {
|
||||
if (mediumChanged.value) {
|
||||
updateMedium()
|
||||
}
|
||||
|
||||
if (!isManager()) return
|
||||
|
||||
if (twilio.isDirty) {
|
||||
twilio.save.submit()
|
||||
}
|
||||
@ -298,6 +305,8 @@ async function updateMedium() {
|
||||
const error = ref('')
|
||||
|
||||
function validateIfDefaultMediumIsEnabled() {
|
||||
if (isAgent() && !isManager()) return true
|
||||
|
||||
if (defaultCallingMedium.value === 'Twilio' && !twilio.doc.enabled) {
|
||||
error.value = __('Twilio is not enabled')
|
||||
return false
|
||||
|
||||
@ -53,9 +53,14 @@ export const usersStore = defineStore('crm-users', () => {
|
||||
return getUser(email).is_manager
|
||||
}
|
||||
|
||||
function isAgent(email) {
|
||||
return getUser(email).is_agent
|
||||
}
|
||||
|
||||
return {
|
||||
users,
|
||||
getUser,
|
||||
isManager,
|
||||
isAgent,
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user