From 0f58d895bc7beda67a6f1fc3ddc35a88e309ad99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:51:06 +0000 Subject: [PATCH 1/3] Initial plan From 91d0b0012d548cdf65e6f4aa81f33e014671abc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:54:35 +0000 Subject: [PATCH 2/3] Optimize delete_attachment and delete_attachments_from_crm_notes to use direct SQL queries Co-authored-by: mrrobot47 <25586785+mrrobot47@users.noreply.github.com> --- next_crm/api/activities.py | 35 +++++++++++++++++------------------ next_crm/doc_events/utils.py | 33 ++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/next_crm/api/activities.py b/next_crm/api/activities.py index 4d6c2e37c1..70e981c80f 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 + for ref in attachment_refs: + frappe.db.delete("NCRM Attachments", {"name": ref["name"]}) + 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) From 5e3868cc596443ddee60636a09cde6eb27c8e9f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:55:45 +0000 Subject: [PATCH 3/3] Use batch delete operation for attachment references Co-authored-by: mrrobot47 <25586785+mrrobot47@users.noreply.github.com> --- next_crm/api/activities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/next_crm/api/activities.py b/next_crm/api/activities.py index 70e981c80f..adbf90df95 100644 --- a/next_crm/api/activities.py +++ b/next_crm/api/activities.py @@ -694,9 +694,9 @@ def delete_attachment(filename, doctype=None, docname=None): ) if attachment_refs: - # Delete the attachment references directly from child table - for ref in attachment_refs: - frappe.db.delete("NCRM Attachments", {"name": ref["name"]}) + # 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: