From bc5c53e6529446c00df3e0e8f6571f2ba287082a Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 14:16:24 +0530 Subject: [PATCH 01/10] refactor: renamed exotel agent to telephony agent will maintain agents details for twilio and exotel --- .../__init__.py | 0 .../crm_telephony_agent.js} | 2 +- .../crm_telephony_agent.json} | 64 ++++++++++++++++--- .../crm_telephony_agent.py | 34 ++++++++++ .../test_crm_telephony_agent.py} | 9 ++- .../doctype/crm_telephony_phone/__init__.py | 0 .../crm_telephony_phone.json | 40 ++++++++++++ .../crm_telephony_phone.py} | 2 +- 8 files changed, 134 insertions(+), 17 deletions(-) rename crm/fcrm/doctype/{crm_exotel_agent => crm_telephony_agent}/__init__.py (100%) rename crm/fcrm/doctype/{crm_exotel_agent/crm_exotel_agent.js => crm_telephony_agent/crm_telephony_agent.js} (77%) rename crm/fcrm/doctype/{crm_exotel_agent/crm_exotel_agent.json => crm_telephony_agent/crm_telephony_agent.json} (54%) create mode 100644 crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py rename crm/fcrm/doctype/{crm_exotel_agent/test_crm_exotel_agent.py => crm_telephony_agent/test_crm_telephony_agent.py} (77%) create mode 100644 crm/fcrm/doctype/crm_telephony_phone/__init__.py create mode 100644 crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.json rename crm/fcrm/doctype/{crm_exotel_agent/crm_exotel_agent.py => crm_telephony_phone/crm_telephony_phone.py} (84%) diff --git a/crm/fcrm/doctype/crm_exotel_agent/__init__.py b/crm/fcrm/doctype/crm_telephony_agent/__init__.py similarity index 100% rename from crm/fcrm/doctype/crm_exotel_agent/__init__.py rename to crm/fcrm/doctype/crm_telephony_agent/__init__.py diff --git a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.js b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.js similarity index 77% rename from crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.js rename to crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.js index 78f3c9bbf..b1bea1732 100644 --- a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.js +++ b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.js @@ -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) { // }, diff --git a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.json b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json similarity index 54% rename from crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.json rename to crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json index c9baa7851..083e4d914 100644 --- a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.json +++ b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json @@ -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,14 @@ "user_name", "column_break_hdec", "mobile_no", - "exotel_number" + "section_break_ozjn", + "twilio", + "twilio_number", + "column_break_aydj", + "exotel", + "exotel_number", + "section_break_phlq", + "phone_nos" ], "fields": [ { @@ -20,7 +27,8 @@ "in_standard_filter": 1, "label": "User", "options": "User", - "reqd": 1 + "reqd": 1, + "unique": 1 }, { "fieldname": "column_break_hdec", @@ -32,8 +40,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 +51,56 @@ "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" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-15 20:03:31.162162", + "modified": "2025-01-19 14:12:51.596987", "modified_by": "Administrator", "module": "FCRM", - "name": "CRM Exotel Agent", + "name": "CRM Telephony Agent", "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ diff --git a/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py new file mode 100644 index 000000000..ff1f85e9b --- /dev/null +++ b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.py @@ -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 = "" diff --git a/crm/fcrm/doctype/crm_exotel_agent/test_crm_exotel_agent.py b/crm/fcrm/doctype/crm_telephony_agent/test_crm_telephony_agent.py similarity index 77% rename from crm/fcrm/doctype/crm_exotel_agent/test_crm_exotel_agent.py rename to crm/fcrm/doctype/crm_telephony_agent/test_crm_telephony_agent.py index 9fe610ce7..63e0f2fb7 100644 --- a/crm/fcrm/doctype/crm_exotel_agent/test_crm_exotel_agent.py +++ b/crm/fcrm/doctype/crm_telephony_agent/test_crm_telephony_agent.py @@ -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 @@ 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. """ diff --git a/crm/fcrm/doctype/crm_telephony_phone/__init__.py b/crm/fcrm/doctype/crm_telephony_phone/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.json b/crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.json new file mode 100644 index 000000000..6450a96a8 --- /dev/null +++ b/crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.json @@ -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": [] +} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.py b/crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.py similarity index 84% rename from crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.py rename to crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.py index 05fa7ee74..5522b84db 100644 --- a/crm/fcrm/doctype/crm_exotel_agent/crm_exotel_agent.py +++ b/crm/fcrm/doctype/crm_telephony_phone/crm_telephony_phone.py @@ -5,5 +5,5 @@ from frappe.model.document import Document -class CRMExotelAgent(Document): +class CRMTelephonyPhone(Document): pass From 0f6876de2752b7101e1cc47e8844fe5d17a7273f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 14:17:52 +0530 Subject: [PATCH 02/10] fix: also maintain agent level default medium --- .../doctype/crm_telephony_agent/crm_telephony_agent.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json index 083e4d914..042632f69 100644 --- a/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json +++ b/crm/fcrm/doctype/crm_telephony_agent/crm_telephony_agent.json @@ -10,6 +10,7 @@ "user_name", "column_break_hdec", "mobile_no", + "default_medium", "section_break_ozjn", "twilio", "twilio_number", @@ -93,11 +94,17 @@ "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-19 14:12:51.596987", + "modified": "2025-01-19 14:17:12.880185", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Telephony Agent", From 03b1bab00dab724f6f7c80a108120b26eb58c110 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 14:22:14 +0530 Subject: [PATCH 03/10] fix: use Telephony Agent to get exotel_number & phone_number --- crm/integrations/exotel/handler.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crm/integrations/exotel/handler.py b/crm/integrations/exotel/handler.py index 1f01837c7..7d3301a8c 100644 --- a/crm/integrations/exotel/handler.py +++ b/crm/integrations/exotel/handler.py @@ -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") From 5cd2f2bd39b3d87917bcbd497d43448c84cb996e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 14:28:36 +0530 Subject: [PATCH 04/10] fix: get and set default medium via telephony agent --- .../doctype/fcrm_settings/fcrm_settings.json | 17 ++--------- crm/integrations/api.py | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json index 0a902c992..f445541d1 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json @@ -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", diff --git a/crm/integrations/api.py b/crm/integrations/api.py index 145bf03a0..f2f554439 100644 --- a/crm/integrations/api.py +++ b/crm/integrations/api.py @@ -9,18 +9,40 @@ def is_call_integration_enabled(): twilio_enabled = frappe.db.get_single_value("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() From 346efbbe785164a5ee5f0ea8c7b33dea00044482 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 14:41:17 +0530 Subject: [PATCH 05/10] fix: allow agent to set default medium from settings --- crm/api/session.py | 34 +++++++++++-------- frontend/src/components/Settings/Settings.vue | 8 +++-- .../components/Settings/TelephonySettings.vue | 15 ++++++-- frontend/src/stores/users.js | 5 +++ 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/crm/api/session.py b/crm/api/session.py index 2e2b93758..746de854e 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -5,7 +5,16 @@ 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) diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue index 2f1c981db..fde939388 100644 --- a/frontend/src/components/Settings/Settings.vue +++ b/frontend/src/components/Settings/Settings.vue @@ -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(), }, ] diff --git a/frontend/src/components/Settings/TelephonySettings.vue b/frontend/src/components/Settings/TelephonySettings.vue index 8134df82d..a4fb0555e 100644 --- a/frontend/src/components/Settings/TelephonySettings.vue +++ b/frontend/src/components/Settings/TelephonySettings.vue @@ -19,17 +19,18 @@ -
+
{{ __('Twilio') }} @@ -42,7 +43,7 @@
-
+
{{ __('Exotel') }} @@ -85,9 +86,12 @@ 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'], @@ -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 diff --git a/frontend/src/stores/users.js b/frontend/src/stores/users.js index bee56d562..5965c537e 100644 --- a/frontend/src/stores/users.js +++ b/frontend/src/stores/users.js @@ -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, } }) From 89dd09325fb26d93d349c51f5b8620a3369e55e5 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 16:12:01 +0530 Subject: [PATCH 06/10] fix: renamed Twilio Settings to CRM Twilio Settings --- .../__init__.py | 0 .../crm_twilio_settings.js} | 2 +- .../crm_twilio_settings.json} | 2 +- .../crm_twilio_settings.py} | 44 +++++------ .../test_crm_twilio_settings.py} | 2 +- .../doctype/twilio_agents/twilio_agents.json | 78 ------------------- .../doctype/twilio_agents/twilio_agents.py | 9 --- crm/fcrm/doctype/twilio_settings/__init__.py | 0 .../twilio_settings/test_twilio_settings.py | 9 --- .../twilio_settings/twilio_settings.js | 8 -- 10 files changed, 22 insertions(+), 132 deletions(-) rename crm/fcrm/doctype/{twilio_agents => crm_twilio_settings}/__init__.py (100%) rename crm/fcrm/doctype/{twilio_agents/twilio_agents.js => crm_twilio_settings/crm_twilio_settings.js} (77%) rename crm/fcrm/doctype/{twilio_settings/twilio_settings.json => crm_twilio_settings/crm_twilio_settings.json} (98%) rename crm/fcrm/doctype/{twilio_settings/twilio_settings.py => crm_twilio_settings/crm_twilio_settings.py} (76%) rename crm/fcrm/doctype/{twilio_agents/test_twilio_agents.py => crm_twilio_settings/test_crm_twilio_settings.py} (77%) delete mode 100644 crm/fcrm/doctype/twilio_agents/twilio_agents.json delete mode 100644 crm/fcrm/doctype/twilio_agents/twilio_agents.py delete mode 100644 crm/fcrm/doctype/twilio_settings/__init__.py delete mode 100644 crm/fcrm/doctype/twilio_settings/test_twilio_settings.py delete mode 100644 crm/fcrm/doctype/twilio_settings/twilio_settings.js diff --git a/crm/fcrm/doctype/twilio_agents/__init__.py b/crm/fcrm/doctype/crm_twilio_settings/__init__.py similarity index 100% rename from crm/fcrm/doctype/twilio_agents/__init__.py rename to crm/fcrm/doctype/crm_twilio_settings/__init__.py diff --git a/crm/fcrm/doctype/twilio_agents/twilio_agents.js b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.js similarity index 77% rename from crm/fcrm/doctype/twilio_agents/twilio_agents.js rename to crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.js index 8d8bf2944..1bfa5e446 100644 --- a/crm/fcrm/doctype/twilio_agents/twilio_agents.js +++ b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.js @@ -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 Agents", { +// frappe.ui.form.on("CRM Twilio Settings", { // refresh(frm) { // }, diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.json b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.json similarity index 98% rename from crm/fcrm/doctype/twilio_settings/twilio_settings.json rename to crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.json index 17e92cfbd..d898b631e 100644 --- a/crm/fcrm/doctype/twilio_settings/twilio_settings.json +++ b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.json @@ -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": [ { diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.py b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py similarity index 76% rename from crm/fcrm/doctype/twilio_settings/twilio_settings.py rename to crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py index 1d3a20b63..811e50ea1 100644 --- a/crm/fcrm/doctype/twilio_settings/twilio_settings.py +++ b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py @@ -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,24 @@ def validate_twilio_account(self): 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( + "Twilio Settings", "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("Twilio Settings", "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 +62,21 @@ def get_twilio_voice_url(self): 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 \ No newline at end of file + + return get_url().split(":8", 1)[0] + path diff --git a/crm/fcrm/doctype/twilio_agents/test_twilio_agents.py b/crm/fcrm/doctype/crm_twilio_settings/test_crm_twilio_settings.py similarity index 77% rename from crm/fcrm/doctype/twilio_agents/test_twilio_agents.py rename to crm/fcrm/doctype/crm_twilio_settings/test_crm_twilio_settings.py index 29ecc305a..531076afc 100644 --- a/crm/fcrm/doctype/twilio_agents/test_twilio_agents.py +++ b/crm/fcrm/doctype/crm_twilio_settings/test_crm_twilio_settings.py @@ -5,5 +5,5 @@ from frappe.tests import UnitTestCase -class TestTwilioAgents(UnitTestCase): +class TestCRMTwilioSettings(UnitTestCase): pass diff --git a/crm/fcrm/doctype/twilio_agents/twilio_agents.json b/crm/fcrm/doctype/twilio_agents/twilio_agents.json deleted file mode 100644 index 652511304..000000000 --- a/crm/fcrm/doctype/twilio_agents/twilio_agents.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/crm/fcrm/doctype/twilio_agents/twilio_agents.py b/crm/fcrm/doctype/twilio_agents/twilio_agents.py deleted file mode 100644 index fb660dd87..000000000 --- a/crm/fcrm/doctype/twilio_agents/twilio_agents.py +++ /dev/null @@ -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 diff --git a/crm/fcrm/doctype/twilio_settings/__init__.py b/crm/fcrm/doctype/twilio_settings/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/crm/fcrm/doctype/twilio_settings/test_twilio_settings.py b/crm/fcrm/doctype/twilio_settings/test_twilio_settings.py deleted file mode 100644 index 21b038410..000000000 --- a/crm/fcrm/doctype/twilio_settings/test_twilio_settings.py +++ /dev/null @@ -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 diff --git a/crm/fcrm/doctype/twilio_settings/twilio_settings.js b/crm/fcrm/doctype/twilio_settings/twilio_settings.js deleted file mode 100644 index 9837c18d4..000000000 --- a/crm/fcrm/doctype/twilio_settings/twilio_settings.js +++ /dev/null @@ -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 Settings", { -// refresh(frm) { - -// }, -// }); From 97b67cfed5df2f40c119cd766fdb497f79cf6733 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 16:13:18 +0530 Subject: [PATCH 07/10] patch: Rename Twilio Settings to CRM Twilio Settings Move Twilio Agent to Telephony Agent --- crm/patches.txt | 4 ++- .../move_twilio_agent_to_telephony_agent.py | 27 +++++++++++++++++++ ..._twilio_settings_to_crm_twilio_settings.py | 11 ++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py create mode 100644 crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py diff --git a/crm/patches.txt b/crm/patches.txt index f964d13fb..f8fbb4c6d 100644 --- a/crm/patches.txt +++ b/crm/patches.txt @@ -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 \ No newline at end of file +crm.patches.v1_0.update_layouts_to_new_format +crm.patches.v1_0.move_twilio_agent_to_telephony_agent \ No newline at end of file diff --git a/crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py b/crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py new file mode 100644 index 000000000..7049ab628 --- /dev/null +++ b/crm/patches/v1_0/move_twilio_agent_to_telephony_agent.py @@ -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() diff --git a/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py new file mode 100644 index 000000000..0a12973df --- /dev/null +++ b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py @@ -0,0 +1,11 @@ +import frappe +from frappe.model.rename_doc import rename_doc + + +def execute(): + if frappe.db.table_exists("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) From e7c89fdc2a85da9d67d028d79c988b1ae0147abf Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 16:33:02 +0530 Subject: [PATCH 08/10] fix: rename Twilio Settings to CRM Twilio Settings in code --- .../doctype/crm_twilio_settings/crm_twilio_settings.py | 6 ++++-- crm/integrations/api.py | 2 +- crm/integrations/twilio/api.py | 2 +- crm/integrations/twilio/twilio_handler.py | 8 ++++---- frontend/src/components/Settings/TelephonySettings.vue | 10 +++++----- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py index 811e50ea1..f2737c6ce 100644 --- a/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py +++ b/crm/fcrm/doctype/crm_twilio_settings/crm_twilio_settings.py @@ -40,14 +40,16 @@ def set_api_credentials(self, 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} + "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.""" 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.""" diff --git a/crm/integrations/api.py b/crm/integrations/api.py index f2f554439..7bb15f898 100644 --- a/crm/integrations/api.py +++ b/crm/integrations/api.py @@ -7,7 +7,7 @@ @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") return { diff --git a/crm/integrations/twilio/api.py b/crm/integrations/twilio/api.py index e78e0214b..0df049ae6 100644 --- a/crm/integrations/twilio/api.py +++ b/crm/integrations/twilio/api.py @@ -11,7 +11,7 @@ @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() diff --git a/crm/integrations/twilio/twilio_handler.py b/crm/integrations/twilio/twilio_handler.py index b24e0e9d3..2d6b7df1f 100644 --- a/crm/integrations/twilio/twilio_handler.py +++ b/crm/integrations/twilio/twilio_handler.py @@ -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 @@ def __init__(self, settings): @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 @@ def generate_twilio_client_response(self, client, ring_tone="at"): @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 diff --git a/frontend/src/components/Settings/TelephonySettings.vue b/frontend/src/components/Settings/TelephonySettings.vue index a4fb0555e..b7df8c351 100644 --- a/frontend/src/components/Settings/TelephonySettings.vue +++ b/frontend/src/components/Settings/TelephonySettings.vue @@ -38,7 +38,7 @@ v-if="twilio?.doc && twilioTabs" :tabs="twilioTabs" :data="twilio.doc" - doctype="Twilio Settings" + doctype="CRM Twilio Settings" />
@@ -94,9 +94,9 @@ 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, @@ -113,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: { From dbaaed7ba3e4a7ff59ae4d4bc4f602e0ecf7eae9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 16:58:30 +0530 Subject: [PATCH 09/10] fix: use db.exists instead of db.table_exists --- .../v1_0/rename_twilio_settings_to_crm_twilio_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py index 0a12973df..8b38d7706 100644 --- a/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py +++ b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py @@ -3,7 +3,7 @@ def execute(): - if frappe.db.table_exists("Twilio Settings"): + 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 From 126d59cfee8696143d1527361355e1d139911343 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sun, 19 Jan 2025 17:53:09 +0530 Subject: [PATCH 10/10] fix: update password encryption keys in Auth table --- .../rename_twilio_settings_to_crm_twilio_settings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py index 8b38d7706..349764843 100644 --- a/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py +++ b/crm/patches/v1_0/rename_twilio_settings_to_crm_twilio_settings.py @@ -9,3 +9,12 @@ def execute(): 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()