From afbaaafd00863d931f5a3bad89e57c8bc540191f Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 19 Jun 2026 18:22:29 +0530 Subject: [PATCH] refactor(postgres): port maintenance_schedule queries to the query builder Co-Authored-By: Claude Opus 4.8 (1M context) --- .../maintenance_schedule.py | 22 +++++---- .../test_maintenance_schedule.py | 46 +++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 104fa8c77dd..2ed918f03bf 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -185,9 +185,7 @@ class MaintenanceSchedule(TransactionBase): else: holiday_list = frappe.get_cached_value("Company", self.company, "default_holiday_list") - holidays = frappe.db.sql_list( - """select holiday_date from `tabHoliday` where parent=%s""", holiday_list - ) + holidays = frappe.get_all("Holiday", filters={"parent": holiday_list}, pluck="holiday_date") if not validated and holidays: # max iterations = len(holidays) @@ -235,16 +233,22 @@ class MaintenanceSchedule(TransactionBase): throw(_("Start date should be less than end date for Item {0}").format(d.item_code)) def validate_sales_order(self): + ms = frappe.qb.DocType("Maintenance Schedule") + msi = frappe.qb.DocType("Maintenance Schedule Item") for d in self.get("items"): if d.sales_order: - chk = frappe.db.sql( - """select ms.name from `tabMaintenance Schedule` ms, - `tabMaintenance Schedule Item` msi where msi.parent=ms.name and - msi.sales_order=%s and ms.docstatus=1""", - d.sales_order, + # filter the parent schedule's docstatus (matches the original ms.docstatus = 1) + chk = ( + frappe.qb.from_(ms) + .inner_join(msi) + .on(msi.parent == ms.name) + .select(ms.name) + .where((msi.sales_order == d.sales_order) & (ms.docstatus == 1)) + .limit(1) + .run(pluck=True) ) if chk: - throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) + throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0], d.sales_order)) def validate_items_table_change(self): doc_before_save = self.get_doc_before_save() diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index ccddc38b12d..208674f0963 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -168,6 +168,51 @@ class TestMaintenanceSchedule(ERPNextTestSuite): ms.save() self.assertEqual(len(ms.schedules), 2) + def test_validate_sales_order_duplicate_throws(self): + # validate_sales_order joins Maintenance Schedule + its item filtering the PARENT schedule's + # docstatus=1; a second schedule against a Sales Order already used by a submitted schedule + # must be rejected. + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + so = make_sales_order() + first = make_maintenance_schedule(sales_order=so.name) + self.assertEqual(first.items[0].sales_order, so.name) + first.submit() + + self.assertRaises(frappe.ValidationError, make_maintenance_schedule, sales_order=so.name) + + def test_validate_schedule_date_skips_holiday(self): + # validate_schedule_date_for_holiday_list reads the holiday list via the converted + # get_all("Holiday", {"parent": }, pluck="holiday_date") and shifts a schedule date + # that lands on a holiday back by a day; a non-holiday date is returned unchanged. + from frappe.utils import getdate + + from erpnext.setup.doctype.holiday_list.test_holiday_list import make_holiday_list + + holiday = add_days(today(), 5) + hl = make_holiday_list( + "_Test MS Holidays " + frappe.generate_hash("", 6), + from_date=today(), + to_date=add_days(today(), 10), + holiday_dates=[{"holiday_date": holiday, "description": "Test Holiday"}], + ) + + ms = make_maintenance_schedule() + # a Sales Person with no linked employee routes to the company-default-holiday-list branch + sp = frappe.get_doc( + {"doctype": "Sales Person", "sales_person_name": "_Test MS SP " + frappe.generate_hash("", 5)} + ).insert(ignore_permissions=True) + frappe.db.set_value("Company", ms.company, "default_holiday_list", hl.name) + + # a date on the holiday is shifted back one day... + shifted = ms.validate_schedule_date_for_holiday_list(getdate(holiday), sp.name) + self.assertEqual(getdate(shifted), getdate(add_days(holiday, -1))) + + # ...a non-holiday date is returned unchanged + non_holiday = add_days(today(), 7) + unchanged = ms.validate_schedule_date_for_holiday_list(getdate(non_holiday), sp.name) + self.assertEqual(getdate(unchanged), getdate(non_holiday)) + def make_serial_item_with_serial(self, item_code): serial_item_doc = create_item(item_code, is_stock_item=1) @@ -202,6 +247,7 @@ def make_maintenance_schedule(**args): "no_of_visits": 4, "serial_no": args.get("serial_no"), "sales_person": "Sales Team", + "sales_order": args.get("sales_order"), }, ) ms.insert(ignore_permissions=True)