Merge pull request #1277 from shariquerik/backport-1125

fix: Bulk Delete "Reference Doctype must be set first" Error (backport #1125)
This commit is contained in:
Shariq Ansari 2025-09-22 16:12:16 +05:30 committed by GitHub
commit 4989dc0921
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 121 additions and 42 deletions

View File

@ -750,7 +750,11 @@ def getCounts(d, doctype):
@frappe.whitelist() @frappe.whitelist()
def get_linked_docs_of_document(doctype, docname): def get_linked_docs_of_document(doctype, docname):
doc = frappe.get_doc(doctype, docname) try:
doc = frappe.get_doc(doctype, docname)
except frappe.DoesNotExistError:
return []
linked_docs = get_linked_docs(doc) linked_docs = get_linked_docs(doc)
dynamic_linked_docs = get_dynamic_linked_docs(doc) dynamic_linked_docs = get_dynamic_linked_docs(doc)
@ -759,7 +763,14 @@ def get_linked_docs_of_document(doctype, docname):
docs_data = [] docs_data = []
for doc in linked_docs: for doc in linked_docs:
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"]) if not doc.get("reference_doctype") or not doc.get("reference_docname"):
continue
try:
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
except (frappe.DoesNotExistError, frappe.ValidationError):
continue
title = data.get("title") title = data.get("title")
if data.doctype == "CRM Call Log": if data.doctype == "CRM Call Log":
title = f"Call from {data.get('from')} to {data.get('to')}" title = f"Call from {data.get('from')} to {data.get('to')}"
@ -767,6 +778,9 @@ def get_linked_docs_of_document(doctype, docname):
if data.doctype == "CRM Deal": if data.doctype == "CRM Deal":
title = data.get("organization") title = data.get("organization")
if data.doctype == "CRM Notification":
title = data.get("message")
docs_data.append( docs_data.append(
{ {
"doc": data.doctype, "doc": data.doctype,
@ -779,25 +793,51 @@ def get_linked_docs_of_document(doctype, docname):
def remove_doc_link(doctype, docname): def remove_doc_link(doctype, docname):
linked_doc_data = frappe.get_doc(doctype, docname) if not doctype or not docname:
linked_doc_data.update( return
{
"reference_doctype": None, try:
"reference_docname": None, linked_doc_data = frappe.get_doc(doctype, docname)
} if doctype == "CRM Notification":
) delete_notification_type = {
linked_doc_data.save(ignore_permissions=True) "notification_type_doctype": "",
"notification_type_doc": "",
}
delete_references = {
"reference_doctype": "",
"reference_name": "",
}
if linked_doc_data.get("notification_type_doctype") == linked_doc_data.get("reference_doctype"):
delete_references.update(delete_notification_type)
linked_doc_data.update(delete_references)
else:
linked_doc_data.update(
{
"reference_doctype": "",
"reference_docname": "",
}
)
linked_doc_data.save(ignore_permissions=True)
except (frappe.DoesNotExistError, frappe.ValidationError):
pass
def remove_contact_link(doctype, docname): def remove_contact_link(doctype, docname):
linked_doc_data = frappe.get_doc(doctype, docname) if not doctype or not docname:
linked_doc_data.update( return
{
"contact": None, try:
"contacts": [], linked_doc_data = frappe.get_doc(doctype, docname)
} linked_doc_data.update(
) {
linked_doc_data.save(ignore_permissions=True) "contact": None,
"contacts": [],
}
)
linked_doc_data.save(ignore_permissions=True)
except (frappe.DoesNotExistError, frappe.ValidationError):
pass
@frappe.whitelist() @frappe.whitelist()
@ -806,13 +846,19 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
items = frappe.parse_json(items) items = frappe.parse_json(items)
for item in items: for item in items:
if remove_contact: if not item.get("doctype") or not item.get("docname"):
remove_contact_link(item["doctype"], item["docname"]) continue
else:
remove_doc_link(item["doctype"], item["docname"])
if delete: try:
frappe.delete_doc(item["doctype"], item["docname"]) if remove_contact:
remove_contact_link(item["doctype"], item["docname"])
else:
remove_doc_link(item["doctype"], item["docname"])
if delete:
frappe.delete_doc(item["doctype"], item["docname"])
except (frappe.DoesNotExistError, frappe.ValidationError):
# Skip if document doesn't exist or has validation errors
continue
return "success" return "success"
@ -821,19 +867,40 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
def delete_bulk_docs(doctype, items, delete_linked=False): def delete_bulk_docs(doctype, items, delete_linked=False):
from frappe.desk.reportview import delete_bulk from frappe.desk.reportview import delete_bulk
if not doctype:
frappe.throw("Doctype is required")
if not items:
frappe.throw("Items are required")
items = frappe.parse_json(items) items = frappe.parse_json(items)
if not isinstance(items, list):
frappe.throw("Items must be a list")
for doc in items: for doc in items:
linked_docs = get_linked_docs_of_document(doctype, doc) try:
for linked_doc in linked_docs: if not frappe.db.exists(doctype, doc):
remove_linked_doc_reference( frappe.log_error(f"Document {doctype} {doc} does not exist", "Bulk Delete Error")
[ continue
{
"doctype": linked_doc["reference_doctype"], linked_docs = get_linked_docs_of_document(doctype, doc)
"docname": linked_doc["reference_docname"], for linked_doc in linked_docs:
} if not linked_doc.get("reference_doctype") or not linked_doc.get("reference_docname"):
], continue
remove_contact=doctype == "Contact",
delete=delete_linked, remove_linked_doc_reference(
[
{
"doctype": linked_doc["reference_doctype"],
"docname": linked_doc["reference_docname"],
}
],
remove_contact=doctype == "Contact",
delete=delete_linked,
)
except Exception as e:
frappe.log_error(
f"Error processing linked docs for {doctype} {doc}: {str(e)}", "Bulk Delete Error"
) )
if len(items) > 10: if len(items) > 10:

View File

@ -13,7 +13,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="text-ink-gray-5"> <div class="text-ink-gray-5 text-base">
{{ {{
__('Are you sure you want to delete {0} items?', [ __('Are you sure you want to delete {0} items?', [
props.items?.length, props.items?.length,
@ -53,7 +53,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="text-ink-gray-5"> <div class="text-ink-gray-5 text-base">
{{ {{
confirmDeleteInfo.delete confirmDeleteInfo.delete
? __( ? __(

View File

@ -2,7 +2,7 @@
<Dialog v-model="show" :options="{ size: 'xl' }"> <Dialog v-model="show" :options="{ size: 'xl' }">
<template #body v-if="!confirmDeleteInfo.show"> <template #body v-if="!confirmDeleteInfo.show">
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6"> <div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
<div class="mb-4 flex items-center justify-between"> <div class="mb-6 flex items-center justify-between">
<div> <div>
<h3 class="text-2xl leading-6 text-ink-gray-9 font-semibold"> <h3 class="text-2xl leading-6 text-ink-gray-9 font-semibold">
{{ {{
@ -32,11 +32,12 @@
{ {
label: 'Document', label: 'Document',
key: 'title', key: 'title',
width: '19rem',
}, },
{ {
label: 'Master', label: 'Master',
key: 'reference_doctype', key: 'reference_doctype',
width: '30%', width: '12rem',
}, },
]" ]"
@selectionsChanged=" @selectionsChanged="

View File

@ -26,13 +26,14 @@
<ListRowItem <ListRowItem
:item="item" :item="item"
@click="listViewRef.toggleRow(row['reference_docname'])" @click="listViewRef.toggleRow(row['reference_docname'])"
class="!w-full"
> >
<template #default="{ label }"> <template #default="{ label }">
<div <div
v-if="column.key === 'title'" v-if="column.key === 'title'"
class="truncate text-base flex gap-2" class="truncate text-base flex gap-2 w-full"
> >
<span> <span class="max-w-[90%] truncate">
{{ label }} {{ label }}
</span> </span>
<FeatherIcon <FeatherIcon
@ -102,6 +103,7 @@ const listViewRef = ref(null)
const viewLinkedDoc = (doc) => { const viewLinkedDoc = (doc) => {
let page = '' let page = ''
let id = '' let id = ''
let openDesk = false
switch (doc.reference_doctype) { switch (doc.reference_doctype) {
case 'CRM Lead': case 'CRM Lead':
page = 'leads' page = 'leads'
@ -123,6 +125,11 @@ const viewLinkedDoc = (doc) => {
page = 'organizations' page = 'organizations'
id = doc.reference_docname id = doc.reference_docname
break break
case 'CRM Notification':
page = 'crm-notification'
id = doc.reference_docname
openDesk = true
break
case 'FCRM Note': case 'FCRM Note':
page = 'notes' page = 'notes'
id = `view?open=${doc.reference_docname}` id = `view?open=${doc.reference_docname}`
@ -130,7 +137,11 @@ const viewLinkedDoc = (doc) => {
default: default:
break break
} }
window.open(`/crm/${page}/${id}`) let base = '/crm'
if (openDesk) {
base = '/app'
}
window.open(`${base}/${page}/${id}`)
} }
const getDoctypeName = (doctype) => { const getDoctypeName = (doctype) => {