refactor(postgres): port transaction_base delete_events to the query builder

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-19 21:21:06 +05:30
parent 5548c3a713
commit 1cfae33fb0
2 changed files with 86 additions and 12 deletions

View File

@@ -0,0 +1,77 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.utils import now_datetime, random_string
from erpnext.tests.utils import ERPNextTestSuite
from erpnext.utilities.transaction_base import delete_events
class TestDeleteEvents(ERPNextTestSuite):
def _make_event(self, reference_doctype, reference_docname):
# Insert a bare Event, then attach the Event Participants child row directly.
# reference_docname is a Dynamic Link that would otherwise be validated against a
# real target doc on save; db_insert keeps the test self-contained with arbitrary
# (random, guaranteed-unique) docnames while still populating exactly the columns
# delete_events joins/filters on (parent, reference_doctype, reference_docname).
event = frappe.get_doc(
{
"doctype": "Event",
"subject": "Test Event " + random_string(10),
"starts_on": now_datetime(),
"event_type": "Private",
}
).insert(ignore_permissions=True)
participant = frappe.new_doc("Event Participants")
participant.name = frappe.generate_hash(length=10)
participant.flags.name_set = True
participant.parent = event.name
participant.parenttype = "Event"
participant.parentfield = "event_participants"
participant.idx = 1
participant.reference_doctype = reference_doctype
participant.reference_docname = reference_docname
participant.db_insert()
return event.name
def test_delete_events_removes_matching_and_keeps_others(self):
# Two distinct, real reference_docnames so the filter has something to discriminate on.
match_name = "Match " + random_string(10)
other_name = "Other " + random_string(10)
event_match = self._make_event("Customer", match_name)
event_other = self._make_event("Customer", other_name)
# Sanity: both exist before deletion (otherwise the assertions below are tautological).
self.assertTrue(frappe.db.exists("Event", event_match))
self.assertTrue(frappe.db.exists("Event", event_other))
delete_events("Customer", match_name)
# Only the Event whose participant matches BOTH reference_doctype and
# reference_docname must be deleted.
self.assertFalse(frappe.db.exists("Event", event_match))
self.assertTrue(frappe.db.exists("Event", event_other))
def test_delete_events_no_match_is_noop(self):
# When nothing matches, no Event may be deleted.
event = self._make_event("Customer", "Present " + random_string(10))
self.assertTrue(frappe.db.exists("Event", event))
delete_events("Customer", "Absent " + random_string(10))
self.assertTrue(frappe.db.exists("Event", event))
def test_delete_events_distinguishes_reference_doctype(self):
# Same docname under two different reference_doctypes: only the queried doctype
# is deleted, proving both predicates are ANDed together.
shared_name = "Shared " + random_string(10)
event_customer = self._make_event("Customer", shared_name)
event_supplier = self._make_event("Supplier", shared_name)
delete_events("Customer", shared_name)
self.assertFalse(frappe.db.exists("Event", event_customer))
self.assertTrue(frappe.db.exists("Event", event_supplier))

View File

@@ -582,19 +582,16 @@ class TransactionBase(StatusUpdater):
def delete_events(ref_type, ref_name):
event = frappe.qb.DocType("Event")
participant = frappe.qb.DocType("Event Participants")
events = (
frappe.db.sql_list(
""" SELECT
distinct `tabEvent`.name
from
`tabEvent`, `tabEvent Participants`
where
`tabEvent`.name = `tabEvent Participants`.parent
and `tabEvent Participants`.reference_doctype = %s
and `tabEvent Participants`.reference_docname = %s
""",
(ref_type, ref_name),
)
frappe.qb.from_(event)
.inner_join(participant)
.on(event.name == participant.parent)
.select(event.name)
.distinct()
.where((participant.reference_doctype == ref_type) & (participant.reference_docname == ref_name))
.run(pluck="name")
or []
)