From 39a419889fa01d85554ab667600cc4a2866717e6 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 8 Jul 2025 12:58:46 +0530 Subject: [PATCH] fix: only show sales user filter to manager (cherry picked from commit f747e076ab6b1bc58eb4aa14fe01628317c0b2eb) --- crm/api/dashboard.py | 7 +++ crm/api/session.py | 5 +- crm/utils/__init__.py | 50 +++++++++++++++++-- frontend/components.d.ts | 1 + frontend/src/components/Settings/Settings.vue | 6 +-- .../components/Settings/TelephonySettings.vue | 4 +- frontend/src/pages/Dashboard.vue | 3 +- frontend/src/stores/users.js | 15 ++++-- 8 files changed, 73 insertions(+), 18 deletions(-) diff --git a/crm/api/dashboard.py b/crm/api/dashboard.py index de7f86ec..3ada6a04 100644 --- a/crm/api/dashboard.py +++ b/crm/api/dashboard.py @@ -1,8 +1,11 @@ import frappe from frappe import _ +from crm.utils import sales_user_only + @frappe.whitelist() +@sales_user_only def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_conds=""): """ Get number card data for the dashboard. @@ -11,6 +14,10 @@ def get_number_card_data(from_date="", to_date="", user="", lead_conds="", deal_ from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate()) to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate()) + is_sales_user = "Sales User" in frappe.get_roles(frappe.session.user) + if is_sales_user and not user: + user = frappe.session.user + lead_chart_data = get_lead_count(from_date, to_date, user, lead_conds) deal_chart_data = get_deal_count(from_date, to_date, user, deal_conds) get_won_deal_count_data = get_won_deal_count(from_date, to_date, user, deal_conds) diff --git a/crm/api/session.py b/crm/api/session.py index add14c37..b45ac43d 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -23,9 +23,6 @@ def get_users(): if frappe.session.user == user.name: user.session_user = True - user.is_manager = "Sales Manager" in frappe.get_roles(user.name) - user.is_admin = user.name == "Administrator" - user.roles = frappe.get_roles(user.name) user.role = "" @@ -42,7 +39,7 @@ def get_users(): if frappe.session.user == user.name: user.session_user = True - user.is_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name}) + user.is_telephony_agent = frappe.db.exists("CRM Telephony Agent", {"user": user.name}) crm_users = [] diff --git a/crm/utils/__init__.py b/crm/utils/__init__.py index 9b954859..15cbae7c 100644 --- a/crm/utils/__init__.py +++ b/crm/utils/__init__.py @@ -1,10 +1,13 @@ -from frappe import frappe +import functools + +import frappe import phonenumbers +from frappe import _ +from frappe.model.docstatus import DocStatus +from frappe.model.dynamic_links import get_dynamic_link_map from frappe.utils import floor from phonenumbers import NumberParseException from phonenumbers import PhoneNumberFormat as PNF -from frappe.model.docstatus import DocStatus -from frappe.model.dynamic_links import get_dynamic_link_map def parse_phone_number(phone_number, default_country="IN"): @@ -97,6 +100,7 @@ def seconds_to_duration(seconds): else: return "0s" + # Extracted from frappe core frappe/model/delete_doc.py/check_if_doc_is_linked def get_linked_docs(doc, method="Delete"): from frappe.model.rename_doc import get_link_fields @@ -161,6 +165,7 @@ def get_linked_docs(doc, method="Delete"): ) return docs + # Extracted from frappe core frappe/model/delete_doc.py/check_if_doc_is_dynamically_linked def get_dynamic_linked_docs(doc, method="Delete"): docs = [] @@ -222,3 +227,42 @@ def get_dynamic_linked_docs(doc, method="Delete"): } ) return docs + + +def is_admin(user: str | None = None) -> bool: + """ + Check whether `user` is an admin + + :param user: User to check against, defaults to current user + :return: Whether `user` is an admin + """ + user = user or frappe.session.user + return user == "Administrator" + + +def is_sales_user(user: str | None = None) -> bool: + """ + Check whether `user` is an agent + + :param user: User to check against, defaults to current user + :return: Whether `user` is an agent + """ + user = user or frappe.session.user + return is_admin() or "Sales Manager" in frappe.get_roles(user) or "Sales User" in frappe.get_roles(user) + + +def sales_user_only(fn): + """Decorator to validate if user is an agent.""" + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + if not is_sales_user(): + frappe.throw( + msg=_("You are not permitted to access this resource."), + title=_("Not Allowed"), + exc=frappe.PermissionError, + ) + + return fn(*args, **kwargs) + + return wrapper diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 63df320c..0c2c052d 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -163,6 +163,7 @@ declare module 'vue' { ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default'] + LucideCalendar: typeof import('~icons/lucide/calendar')['default'] LucideInfo: typeof import('~icons/lucide/info')['default'] LucideMoreHorizontal: typeof import('~icons/lucide/more-horizontal')['default'] LucidePlus: typeof import('~icons/lucide/plus')['default'] diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue index 8a8b4744..174ba62f 100644 --- a/frontend/src/components/Settings/Settings.vue +++ b/frontend/src/components/Settings/Settings.vue @@ -65,7 +65,7 @@ import { import { Dialog, Avatar } from 'frappe-ui' import { ref, markRaw, computed, watch, h } from 'vue' -const { isManager, isAgent, getUser } = usersStore() +const { isManager, isTelephonyAgent, getUser } = usersStore() const user = computed(() => getUser() || {}) @@ -123,7 +123,7 @@ const tabs = computed(() => { label: __('Telephony'), icon: PhoneIcon, component: markRaw(TelephonySettings), - condition: () => isManager() || isAgent(), + condition: () => isManager() || isTelephonyAgent(), }, { label: __('WhatsApp'), @@ -138,7 +138,7 @@ const tabs = computed(() => { condition: () => isManager(), }, ], - condition: () => isManager() || isAgent(), + condition: () => isManager() || isTelephonyAgent(), }, ] diff --git a/frontend/src/components/Settings/TelephonySettings.vue b/frontend/src/components/Settings/TelephonySettings.vue index 08681fff..a1bbce16 100644 --- a/frontend/src/components/Settings/TelephonySettings.vue +++ b/frontend/src/components/Settings/TelephonySettings.vue @@ -93,7 +93,7 @@ import { toast } from 'frappe-ui' import { getRandom } from '@/utils' import { ref, computed, watch } from 'vue' -const { isManager, isAgent } = usersStore() +const { isManager, isTelephonyAgent } = usersStore() const twilioFields = createResource({ url: 'crm.api.doc.get_fields', @@ -283,7 +283,7 @@ async function updateMedium() { const error = ref('') function validateIfDefaultMediumIsEnabled() { - if (isAgent() && !isManager()) return true + if (isTelephonyAgent() && !isManager()) return true if (defaultCallingMedium.value === 'Twilio' && !twilio.doc.enabled) { error.value = __('Twilio is not enabled') diff --git a/frontend/src/pages/Dashboard.vue b/frontend/src/pages/Dashboard.vue index a2e0147e..af17f783 100644 --- a/frontend/src/pages/Dashboard.vue +++ b/frontend/src/pages/Dashboard.vue @@ -52,6 +52,7 @@ { } function isAdmin(email) { - return getUser(email).role === 'System Manager' || getUser(email).is_admin + return getUser(email).role === 'System Manager' } function isManager(email) { - return getUser(email).is_manager + return getUser(email).role === 'Sales Manager' } - function isAgent(email) { - return getUser(email).is_agent + function isSalesUser(email) { + return getUser(email).role === 'Sales User' + } + + function isTelephonyAgent(email) { + return getUser(email).is_telphony_agent } function getUserRole(email) { @@ -74,7 +78,8 @@ export const usersStore = defineStore('crm-users', () => { getUser, isAdmin, isManager, - isAgent, + isSalesUser, + isTelephonyAgent, getUserRole, } })