diff --git a/crm/api/doc.py b/crm/api/doc.py index 6abf86e0..28ffb873 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -752,37 +752,28 @@ def getLinkedDocs(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) - - return list({doc["reference_docname"]: doc for doc in linked_docs}.values()) - - -@frappe.whitelist() -def removeLinkedDocReference(doctype=None, docname=None,removeAll=False,removeContact=None): + linked_docs = list({doc["reference_docname"]: doc for doc in linked_docs}.values()) - if (not doctype or not docname) and not removeAll: - return "Invalid doctype or docname" - - if removeAll: - if removeContact: - ref_doc = getLinkedDocs(doctype, docname) - for linked_doc in ref_doc: - removeContactLink(linked_doc["reference_doctype"], linked_doc["reference_docname"]) - return "success" - + 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')}" - linked_docs = getLinkedDocs(doctype, docname) + 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"], + }) + return docs_data - for linked_doc in linked_docs: - removeDocLink(linked_doc["reference_doctype"], linked_doc["reference_docname"]) - return "success" - else: - if removeContact: - removeContactLink(doctype, docname) - return "success" - - removeDocLink(doctype, docname) - return "success" def removeDocLink(doctype, docname): linked_doc_data = frappe.get_doc(doctype, docname) @@ -799,17 +790,45 @@ def removeContactLink(doctype, docname): "contacts": [], }) linked_doc_data.save(ignore_permissions=True) + +@frappe.whitelist() +def removeLinkedDocReference(items, removeContact=None, delete=False): + + if isinstance(items, str): + items = frappe.parse_json(items) + + for item in items: + if removeContact: + removeContactLink(item["doctype"], item["docname"]) + else: + removeDocLink(item["doctype"], item["docname"]) + + if delete: + frappe.delete_doc(item["doctype"], item["docname"]) + + return "success" + @frappe.whitelist() -def deleteBulkDocs(doctype, items): +def deleteBulkDocs(doctype, items, deleteLinked=False): from frappe.desk.reportview import delete_bulk items = frappe.parse_json(items) for doc in items: - removeLinkedDocReference(doctype, doc, removeAll=True,removeContact=doctype=="Contact") + linked_docs = getLinkedDocs(doctype, doc) + for linked_doc in linked_docs: + removeLinkedDocReference([ + { + "doctype": linked_doc["reference_doctype"], + "docname": linked_doc["reference_docname"], + } + ] , + removeContact=doctype=="Contact", + delete=deleteLinked + ) if len(items) > 10: frappe.enqueue("frappe.desk.reportview.delete_bulk", doctype=doctype, items=items) else: delete_bulk(doctype, items) - return "success" \ No newline at end of file + return "success" diff --git a/frontend/components.d.ts b/frontend/components.d.ts index db4334b6..a9378705 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -31,6 +31,7 @@ declare module 'vue' { Autocomplete: typeof import('./src/components/frappe-ui/Autocomplete.vue')['default'] AvatarIcon: typeof import('./src/components/Icons/AvatarIcon.vue')['default'] BrandLogo: typeof import('./src/components/BrandLogo.vue')['default'] + BulkDeleteLinkedDocModal: typeof import('./src/components/BulkDeleteLinkedDocModal.vue')['default'] CalendarIcon: typeof import('./src/components/Icons/CalendarIcon.vue')['default'] CallArea: typeof import('./src/components/Activities/CallArea.vue')['default'] CallLogDetailModal: typeof import('./src/components/Modals/CallLogDetailModal.vue')['default'] @@ -55,6 +56,7 @@ 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'] @@ -150,11 +152,13 @@ declare module 'vue' { LeadsListView: typeof import('./src/components/ListViews/LeadsListView.vue')['default'] LightningIcon: typeof import('./src/components/Icons/LightningIcon.vue')['default'] Link: typeof import('./src/components/Controls/Link.vue')['default'] + LinkedDocsListView: typeof import('./src/components/ListViews/LinkedDocsListView.vue')['default'] LinkIcon: typeof import('./src/components/Icons/LinkIcon.vue')['default'] ListBulkActions: typeof import('./src/components/ListBulkActions.vue')['default'] ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default'] ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] + LucideCalendar: typeof import('~icons/lucide/calendar')['default'] LucideInfo: typeof import('~icons/lucide/info')['default'] LucidePlus: typeof import('~icons/lucide/plus')['default'] LucideSearch: typeof import('~icons/lucide/search')['default'] diff --git a/frontend/src/components/BulkDeleteLinkedDocModal.vue b/frontend/src/components/BulkDeleteLinkedDocModal.vue new file mode 100644 index 00000000..609f4ef2 --- /dev/null +++ b/frontend/src/components/BulkDeleteLinkedDocModal.vue @@ -0,0 +1,167 @@ + + + diff --git a/frontend/src/components/DeleteLinkedDocModal.vue b/frontend/src/components/DeleteLinkedDocModal.vue index 204b900b..f6ccedaf 100644 --- a/frontend/src/components/DeleteLinkedDocModal.vue +++ b/frontend/src/components/DeleteLinkedDocModal.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/pages/CallLogs.vue b/frontend/src/pages/CallLogs.vue index a0e2da95..2f1d6ce6 100644 --- a/frontend/src/pages/CallLogs.vue +++ b/frontend/src/pages/CallLogs.vue @@ -80,7 +80,7 @@ import CallLogDetailModal from '@/components/Modals/CallLogDetailModal.vue' import CallLogModal from '@/components/Modals/CallLogModal.vue' import { getCallLogDetail } from '@/utils/callLog' import { createResource } from 'frappe-ui' -import { computed, ref } from 'vue' +import { computed, ref, onMounted } from 'vue' const callLogsListView = ref(null) const showCallLogModal = ref(false) @@ -124,4 +124,19 @@ function createCallLog() { callLog.value = {} showCallLogModal.value = true } + +const openCallLogFromURL = () => { + const searchParams = new URLSearchParams(window.location.search) + const callLogName = searchParams.get('open') + + if (callLogName) { + showCallLog(callLogName) + searchParams.delete('open') + window.history.replaceState(null, '', window.location.pathname) + } +} + +onMounted(() => { + openCallLogFromURL() +}) diff --git a/frontend/src/pages/Notes.vue b/frontend/src/pages/Notes.vue index 340a8c43..0c111edd 100644 --- a/frontend/src/pages/Notes.vue +++ b/frontend/src/pages/Notes.vue @@ -127,6 +127,7 @@ const viewControls = ref(null) watch( () => notes.value?.data?.page_length_count, (val, old_value) => { + openNoteFromURL() if (!val || val === old_value) return updatedPageCount.value = val }, @@ -152,4 +153,20 @@ async function deleteNote(name) { }) notes.value.reload() } + +const openNoteFromURL = () => { + const searchParams = new URLSearchParams(window.location.search) + const noteName = searchParams.get('open') + + if (noteName && notes.value?.data?.data) { + const foundNote = notes.value.data.data.find( + (note) => note.name === noteName, + ) + if (foundNote) { + editNote(foundNote) + } + searchParams.delete('open') + window.history.replaceState(null, '', window.location.pathname) + } +} diff --git a/frontend/src/pages/Tasks.vue b/frontend/src/pages/Tasks.vue index f73e1804..5a2c2f1c 100644 --- a/frontend/src/pages/Tasks.vue +++ b/frontend/src/pages/Tasks.vue @@ -211,7 +211,7 @@ import { getMeta } from '@/stores/meta' import { usersStore } from '@/stores/users' import { formatDate, timeAgo } from '@/utils' import { Tooltip, Avatar, TextEditor, Dropdown, call } from 'frappe-ui' -import { computed, ref } from 'vue' +import { computed, ref, watch } from 'vue' import { useRouter } from 'vue-router' const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } = @@ -246,6 +246,7 @@ const rows = computed(() => { return getKanbanRows(tasks.value.data.data, tasks.value.data.fields) } + openTaskFromURL() return parseRows(tasks.value?.data.data, tasks.value?.data.columns) }) @@ -391,4 +392,15 @@ function redirect(doctype, docname) { } router.push({ name: name, params: params }) } + +const openTaskFromURL = () => { + const searchParams = new URLSearchParams(window.location.search) + const taskName = searchParams.get('open') + + if (taskName && rows.value?.length) { + showTask(parseInt(taskName)) + searchParams.delete('open') + window.history.replaceState(null, '', window.location.pathname) + } +}