From b843b9345413ace6264da299e56569fbc3872c88 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 24 Jun 2025 11:44:13 +0530 Subject: [PATCH 01/34] fix: user with System Manager role is admin (cherry picked from commit b5ed9692dff4997335ae389ac4f836bedc3e7898) --- frontend/src/components/Settings/Users.vue | 2 +- frontend/src/stores/users.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Settings/Users.vue b/frontend/src/components/Settings/Users.vue index 6ea15809..dc07ac30 100644 --- a/frontend/src/components/Settings/Users.vue +++ b/frontend/src/components/Settings/Users.vue @@ -150,7 +150,7 @@ import { usersStore } from '@/stores/users' import { Avatar, TextInput, toast, call } from 'frappe-ui' import { ref, computed, h, onMounted } from 'vue' -const { users, getUserRole, isAdmin, isManager } = usersStore() +const { users, isAdmin, isManager } = usersStore() const showAddExistingModal = ref(false) const searchRef = ref(null) diff --git a/frontend/src/stores/users.js b/frontend/src/stores/users.js index 3614bda5..e0122f8a 100644 --- a/frontend/src/stores/users.js +++ b/frontend/src/stores/users.js @@ -50,7 +50,7 @@ export const usersStore = defineStore('crm-users', () => { } function isAdmin(email) { - return getUser(email).is_admin + return getUser(email).role === 'System Manager' || getUser(email).is_admin } function isManager(email) { From af6994f8fcecfb779829462c484f2c1fae66e82a Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 24 Jun 2025 12:11:39 +0530 Subject: [PATCH 02/34] fix: make header sticky (cherry picked from commit 3c1ce1fe2741a5d2f4faa4a0a5be71f188439f07) --- frontend/src/components/Settings/Users.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Settings/Users.vue b/frontend/src/components/Settings/Users.vue index dc07ac30..0d4670ed 100644 --- a/frontend/src/components/Settings/Users.vue +++ b/frontend/src/components/Settings/Users.vue @@ -59,7 +59,10 @@ -
+
-
    +
      + diff --git a/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue b/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue index f41b4d5d..e0ed3e07 100644 --- a/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue +++ b/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue @@ -6,7 +6,7 @@ /> @@ -19,7 +19,7 @@ import { createListResource } from 'frappe-ui' import { provide, ref } from 'vue' const step = ref('template-list') -const templateData = ref(null) +const template = ref(null) const templates = createListResource({ type: 'list', @@ -46,6 +46,6 @@ provide('templates', templates) function updateStep(newStep, data) { step.value = newStep - templateData.value = data + template.value = data } diff --git a/frontend/src/components/Settings/EmailTemplate/EmailTemplates.vue b/frontend/src/components/Settings/EmailTemplate/EmailTemplates.vue index 4eb9c622..682d31e9 100644 --- a/frontend/src/components/Settings/EmailTemplate/EmailTemplates.vue +++ b/frontend/src/components/Settings/EmailTemplate/EmailTemplates.vue @@ -143,6 +143,7 @@ import { Switch, Dropdown, FeatherIcon, + toast, } from 'frappe-ui' import { ref, computed, inject, h } from 'vue' @@ -172,10 +173,26 @@ const templatesList = computed(() => { }) function toggleEmailTemplate(template) { - templates.setValue.submit({ - name: template.name, - enabled: template.enabled ? 1 : 0, - }) + templates.setValue.submit( + { + name: template.name, + enabled: template.enabled ? 1 : 0, + }, + { + onSuccess: () => { + toast.success( + template.enabled + ? __('Template enabled successfully') + : __('Template disabled successfully'), + ) + }, + onError: (error) => { + toast.error(error.messages[0] || __('Failed to update template')) + // Revert the change if there was an error + template.enabled = !template.enabled + }, + }, + ) } function deleteTemplate(template) { From 3f51da59add80bad77da0aac90a754fcc8f7372c Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 24 Jun 2025 19:46:27 +0530 Subject: [PATCH 08/34] fix: Duplicate email template (cherry picked from commit 7e6d5c3e544b784b07ae0975b4ae784fb9f9ee46) --- .../EmailTemplate/EmailTemplatePage.vue | 6 ++++- .../Settings/EmailTemplate/EmailTemplates.vue | 15 +++++++---- .../EmailTemplate/NewEmailTemplate.vue | 25 ++++++++++++++++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue b/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue index e0ed3e07..c148a6dc 100644 --- a/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue +++ b/frontend/src/components/Settings/EmailTemplate/EmailTemplatePage.vue @@ -1,5 +1,9 @@ diff --git a/frontend/src/components/Settings/Users.vue b/frontend/src/components/Settings/Users.vue index a4b07a03..127c035b 100644 --- a/frontend/src/components/Settings/Users.vue +++ b/frontend/src/components/Settings/Users.vue @@ -1,7 +1,7 @@ -
+ +
+ +
+
From 369292f5234cb592f50728d7804c05160f32b437 Mon Sep 17 00:00:00 2001 From: Pratik Date: Thu, 29 May 2025 06:02:35 +0000 Subject: [PATCH 24/34] refactor: internationalization & code clean up --- frontend/src/components/BulkDeleteLinkedDocModal.vue | 2 -- frontend/src/components/DeleteLinkedDocModal.vue | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/BulkDeleteLinkedDocModal.vue b/frontend/src/components/BulkDeleteLinkedDocModal.vue index 5365c298..6f28cebc 100644 --- a/frontend/src/components/BulkDeleteLinkedDocModal.vue +++ b/frontend/src/components/BulkDeleteLinkedDocModal.vue @@ -22,7 +22,6 @@ ]) }} -
@@ -72,7 +71,6 @@ ) }}
-
diff --git a/frontend/src/components/DeleteLinkedDocModal.vue b/frontend/src/components/DeleteLinkedDocModal.vue index 6341c983..81f6f2e3 100644 --- a/frontend/src/components/DeleteLinkedDocModal.vue +++ b/frontend/src/components/DeleteLinkedDocModal.vue @@ -52,7 +52,6 @@ ]) }}
-
@@ -66,10 +65,10 @@
- Delete and unlink + {{ __('Delete and unlink') }} {{ viewControls?.selections?.length == 0 - ? 'all' + ? __('all') : `${viewControls?.selections?.length} item(s)` }} @@ -83,10 +82,10 @@
- Unlink + {{ __('Unlink') }} {{ viewControls?.selections?.length == 0 - ? 'all' + ? __('all') : `${viewControls?.selections?.length} item(s)` }} From 2ef3ccafc7bda52a8ec8638688e31d64cc15e25d Mon Sep 17 00:00:00 2001 From: Pratik Date: Mon, 23 Jun 2025 05:15:40 +0000 Subject: [PATCH 25/34] refactor: change labels & function names --- crm/api/doc.py | 89 ++++++++++--------- frontend/components.d.ts | 1 - .../components/BulkDeleteLinkedDocModal.vue | 4 +- .../src/components/DeleteLinkedDocModal.vue | 24 +++-- .../ListViews/LinkedDocsListView.vue | 37 +++++--- 5 files changed, 92 insertions(+), 63 deletions(-) diff --git a/crm/api/doc.py b/crm/api/doc.py index 28ffb873..1369a952 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -677,6 +677,7 @@ def remove_assignments(doctype, name, assignees, ignore_permissions=False): ignore_permissions=ignore_permissions, ) + @frappe.whitelist() def get_assigned_users(doctype, name, default_assigned_to=None): assigned_users = frappe.get_all( @@ -748,85 +749,93 @@ def getCounts(d, doctype): @frappe.whitelist() -def getLinkedDocs(doctype, docname): +def get_linked_docs_of_document(doctype, docname): doc = frappe.get_doc(doctype, docname) linked_docs = get_linked_docs(doc) dynamic_linked_docs = get_dynamic_linked_docs(doc) - + linked_docs.extend(dynamic_linked_docs) linked_docs = list({doc["reference_docname"]: doc for doc in linked_docs}.values()) - + docs_data = [] for doc in linked_docs: data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"]) title = data.get("title") if data.doctype == "CRM Call Log": - title = f"CRM Call Log - from {data.get('from')} to {data.get('to')}" - + title = f"Call from {data.get('from')} to {data.get('to')}" + if data.doctype == "CRM Deal": title = data.get("organization") - - docs_data.append({ - "doc": data.doctype, - "title": title or data.get("name"), - "reference_docname": doc["reference_docname"], - "reference_doctype": doc["reference_doctype"], - }) + + docs_data.append( + { + "doc": data.doctype, + "title": title or data.get("name"), + "reference_docname": doc["reference_docname"], + "reference_doctype": doc["reference_doctype"], + } + ) return docs_data -def removeDocLink(doctype, docname): +def remove_doc_link(doctype, docname): linked_doc_data = frappe.get_doc(doctype, docname) - linked_doc_data.update({ - "reference_doctype": None, - "reference_docname": None, - }) + linked_doc_data.update( + { + "reference_doctype": None, + "reference_docname": None, + } + ) linked_doc_data.save(ignore_permissions=True) -def removeContactLink(doctype, docname): + +def remove_contact_link(doctype, docname): linked_doc_data = frappe.get_doc(doctype, docname) - linked_doc_data.update({ - "contact": None, - "contacts": [], - }) + linked_doc_data.update( + { + "contact": None, + "contacts": [], + } + ) linked_doc_data.save(ignore_permissions=True) + @frappe.whitelist() -def removeLinkedDocReference(items, removeContact=None, delete=False): - +def remove_linked_doc_reference(items, remove_contact=None, delete=False): if isinstance(items, str): items = frappe.parse_json(items) for item in items: - if removeContact: - removeContactLink(item["doctype"], item["docname"]) + if remove_contact: + remove_contact_link(item["doctype"], item["docname"]) else: - removeDocLink(item["doctype"], item["docname"]) - + remove_doc_link(item["doctype"], item["docname"]) + if delete: frappe.delete_doc(item["doctype"], item["docname"]) return "success" - + @frappe.whitelist() -def deleteBulkDocs(doctype, items, deleteLinked=False): +def delete_bulk_docs(doctype, items, delete_linked=False): from frappe.desk.reportview import delete_bulk items = frappe.parse_json(items) for doc in items: - linked_docs = getLinkedDocs(doctype, doc) + linked_docs = get_linked_docs_of_document(doctype, doc) for linked_doc in linked_docs: - removeLinkedDocReference([ - { - "doctype": linked_doc["reference_doctype"], - "docname": linked_doc["reference_docname"], - } - ] , - removeContact=doctype=="Contact", - delete=deleteLinked + remove_linked_doc_reference( + [ + { + "doctype": linked_doc["reference_doctype"], + "docname": linked_doc["reference_docname"], + } + ], + remove_contact=doctype == "Contact", + delete=delete_linked, ) - + if len(items) > 10: frappe.enqueue("frappe.desk.reportview.delete_bulk", doctype=doctype, items=items) else: diff --git a/frontend/components.d.ts b/frontend/components.d.ts index a9378705..9ee5166f 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -56,7 +56,6 @@ declare module 'vue' { ContactsIcon: typeof import('./src/components/Icons/ContactsIcon.vue')['default'] ContactsListView: typeof import('./src/components/ListViews/ContactsListView.vue')['default'] ConvertIcon: typeof import('./src/components/Icons/ConvertIcon.vue')['default'] - copy: typeof import('./src/components/DeleteLinkedDocModal copy.vue')['default'] CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default'] CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default'] CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default'] diff --git a/frontend/src/components/BulkDeleteLinkedDocModal.vue b/frontend/src/components/BulkDeleteLinkedDocModal.vue index 6f28cebc..40b6cbac 100644 --- a/frontend/src/components/BulkDeleteLinkedDocModal.vue +++ b/frontend/src/components/BulkDeleteLinkedDocModal.vue @@ -149,10 +149,10 @@ const confirmUnlink = () => { } const deleteDocs = () => { - call('crm.api.doc.deleteBulkDocs', { + call('crm.api.doc.delete_bulk_docs', { items: props.items, doctype: props.doctype, - deleteLinked: confirmDeleteInfo.value.delete, + delete_linked: confirmDeleteInfo.value.delete, }).then(() => { confirmDeleteInfo.value = { show: false, diff --git a/frontend/src/components/DeleteLinkedDocModal.vue b/frontend/src/components/DeleteLinkedDocModal.vue index 81f6f2e3..224ee9b9 100644 --- a/frontend/src/components/DeleteLinkedDocModal.vue +++ b/frontend/src/components/DeleteLinkedDocModal.vue @@ -5,7 +5,11 @@

- {{ __('Delete') }} + {{ + linkedDocs?.length == 0 + ? __('Delete') + : __('Delete or unlink linked documents') + }}

@@ -32,7 +36,7 @@ key: 'title', }, { - label: 'Doctype', + label: 'Master', key: 'reference_doctype', width: '30%', }, @@ -65,7 +69,7 @@
- {{ __('Delete and unlink') }} + {{ __('Delete') }} {{ viewControls?.selections?.length == 0 ? __('all') @@ -76,7 +80,8 @@
@@ -50,6 +41,12 @@ @click.stop="viewLinkedDoc(row)" />
+ + {{ getDoctypeName(row.reference_doctype) }} + @@ -58,7 +55,6 @@ From 6292824f4158af14eaf57aee08532956cc071442 Mon Sep 17 00:00:00 2001 From: Pratik Date: Mon, 23 Jun 2025 07:35:15 +0000 Subject: [PATCH 26/34] refactor: remove unnecessary functions & components --- .../src/components/DeleteLinkedDocModal.vue | 15 ++++++++++----- .../components/ListViews/LinkedDocsListView.vue | 17 +---------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/DeleteLinkedDocModal.vue b/frontend/src/components/DeleteLinkedDocModal.vue index 224ee9b9..eb5e4694 100644 --- a/frontend/src/components/DeleteLinkedDocModal.vue +++ b/frontend/src/components/DeleteLinkedDocModal.vue @@ -2,7 +2,7 @@ @@ -87,10 +88,12 @@ function showNote(n) { // Call Logs const showCallLogModal = ref(false) const callLog = ref({}) +const referenceDoc = ref({}) function createCallLog() { let doctype = props.doctype let docname = props.doc.data?.name + referenceDoc.value = { ...props.doc.data } callLog.value = { reference_doctype: doctype, reference_docname: docname, diff --git a/frontend/src/components/Modals/CallLogModal.vue b/frontend/src/components/Modals/CallLogModal.vue index 9b1cbcb0..1c90541f 100644 --- a/frontend/src/components/Modals/CallLogModal.vue +++ b/frontend/src/components/Modals/CallLogModal.vue @@ -69,6 +69,10 @@ const props = defineProps({ type: Object, default: () => ({}), }, + referenceDoc: { + type: Object, + default: () => ({}), + }, options: { type: Object, default: { @@ -141,7 +145,7 @@ async function createCallLog() { telephony_medium: 'Manual', }) - await triggerOnBeforeCreate?.() + await triggerOnBeforeCreate?.(props.referenceDoc) await _createCallLog.submit({ doc: callLog.doc, })