diff --git a/next_crm/api/activities.py b/next_crm/api/activities.py index 4d6c2e37c1..adbf90df95 100644 --- a/next_crm/api/activities.py +++ b/next_crm/api/activities.py @@ -679,26 +679,25 @@ def delete_attachment(filename, doctype=None, docname=None): deleted = False if doctype and docname: - notes = frappe.get_all( - "CRM Note", - filters={"parenttype": doctype, "parent": docname}, - fields=["name"], + # Use direct SQL query to find attachment references instead of loading full documents + attachment_refs = frappe.db.sql( + """ + SELECT nca.name, nca.parent + FROM `tabNCRM Attachments` nca + INNER JOIN `tabCRM Note` cn ON cn.name = nca.parent + WHERE cn.parenttype = %s + AND cn.parent = %s + AND nca.filename = %s + """, + (doctype, docname, filename), + as_dict=True, ) - for note in notes: - note_doc = frappe.get_doc("CRM Note", note.name) - original_count = len(note_doc.custom_note_attachments) - - updated_attachments = [ - row - for row in note_doc.custom_note_attachments - if row.filename != filename - ] - - if len(updated_attachments) != original_count: - note_doc.set("custom_note_attachments", updated_attachments) - note_doc.save() - deleted = True + if attachment_refs: + # Delete the attachment references directly from child table using batch operation + attachment_ids = [ref["name"] for ref in attachment_refs] + frappe.db.delete("NCRM Attachments", {"name": ("in", attachment_ids)}) + deleted = True try: frappe.delete_doc("File", filename) diff --git a/next_crm/doc_events/utils.py b/next_crm/doc_events/utils.py index b6c394cca3..364c6fe533 100644 --- a/next_crm/doc_events/utils.py +++ b/next_crm/doc_events/utils.py @@ -11,22 +11,33 @@ def delete_attachments_from_crm_notes(doctype, docname): doctype (str): The parent doctype (e.g., "Opportunity") docname (str): The name of the parent doc (e.g., "OPTY-0001") """ - file_names_to_delete = set() - - notes = frappe.get_all( - "CRM Note", filters={"parenttype": doctype, "parent": docname}, fields=["name"] + # Use direct SQL query to get all attachment filenames and their references + # This replaces the N+1 pattern of loading each note document + attachment_data = frappe.db.sql( + """ + SELECT nca.name as attachment_id, nca.filename + FROM `tabNCRM Attachments` nca + INNER JOIN `tabCRM Note` cn ON cn.name = nca.parent + WHERE cn.parenttype = %s + AND cn.parent = %s + AND nca.filename IS NOT NULL + """, + (doctype, docname), + as_dict=True, ) - for note in notes: - note_doc = frappe.get_doc("CRM Note", note.name) + if not attachment_data: + return - for row in note_doc.custom_note_attachments: - if row.filename: - file_names_to_delete.add(row.filename) + # Collect unique filenames for deletion + file_names_to_delete = {row["filename"] for row in attachment_data} - note_doc.set("custom_note_attachments", []) - note_doc.save() + # Delete all attachment references directly from child table + attachment_ids = [row["attachment_id"] for row in attachment_data] + if attachment_ids: + frappe.db.delete("NCRM Attachments", {"name": ("in", attachment_ids)}) + # Delete the actual file documents for file_name in file_names_to_delete: try: frappe.delete_doc("File", file_name)