Skip to content
This repository was archived by the owner on Jun 17, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions next_crm/api/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 22 additions & 11 deletions next_crm/doc_events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down