From fb4d91533cbe5b054c91e40c42e9e970128911d0 Mon Sep 17 00:00:00 2001 From: Jatin3128 Date: Mon, 10 Aug 2026 18:02:34 +0530 Subject: [PATCH] fix: block disabled/frozen suppliers on Request for Quotation Request for Quotation overrides validate() entirely and never calls super().validate(), so it never goes through AccountsController's party validation. Suppliers also sit in a child table, so the shared PartyValidator wouldn't have caught it anyway (it only checks a single top-level party field). A disabled or frozen Supplier could be added to an RFQ and the RFQ submitted without any warning. Also filters the suppliers grid's supplier Link field to disabled=0, matching the same client-side fix applied to Opportunity's party_name. (cherry picked from commit 4bf65ffc1de15e9f39daf77511327ff35fa3cdd6) # Conflicts: # erpnext/buying/doctype/request_for_quotation/request_for_quotation.py # erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py --- .../request_for_quotation.js | 4 ++ .../request_for_quotation.py | 6 +++ .../test_request_for_quotation.py | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 670231653cf..96b197c4a9f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -22,6 +22,10 @@ frappe.ui.form.on("Request for Quotation", { }; }; + frm.set_query("supplier", "suppliers", function () { + return { filters: { disabled: 0 } }; + }); + frm.set_query("warehouse", "items", () => ({ filters: { company: frm.doc.company, diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index c4ada801cd2..e3e517d1b23 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -15,7 +15,11 @@ from frappe.utils import get_url from frappe.utils.print_format import download_pdf from frappe.utils.user import get_user_fullname +<<<<<<< HEAD from erpnext.accounts.party import get_party_account_currency, get_party_details +======= +from erpnext.accounts.party import validate_party_frozen_disabled +>>>>>>> 4bf65ffc1d (fix: block disabled/frozen suppliers on Request for Quotation) from erpnext.buying.utils import validate_for_items from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.material_request.material_request import set_missing_values @@ -123,6 +127,8 @@ class RequestforQuotation(BuyingController): def validate_supplier_list(self): for d in self.suppliers: + validate_party_frozen_disabled(self.company, "Supplier", d.supplier) + prevent_rfqs = frappe.db.get_value("Supplier", d.supplier, "prevent_rfqs") if prevent_rfqs: standing = frappe.db.get_value("Supplier Scorecard", d.supplier, "status") diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index a92d8d95626..448c84efaa3 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -17,6 +17,7 @@ from erpnext.buying.doctype.request_for_quotation.request_for_quotation import ( from erpnext.controllers.accounts_controller import InvalidQtyError from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity +from erpnext.exceptions import PartyDisabled from erpnext.stock.doctype.item.test_item import make_item from erpnext.templates.pages.rfq import check_supplier_has_docname_access @@ -57,6 +58,56 @@ class TestRequestforQuotation(FrappeTestCase): self.assertEqual(rfq.get("suppliers")[0].quote_status, "Received") self.assertEqual(rfq.get("suppliers")[1].quote_status, "Pending") +<<<<<<< HEAD +======= + def test_duplicate_supplier_rejected(self): + rfq = frappe.new_doc("Request for Quotation") + rfq.transaction_date = nowdate() + rfq.company = "_Test Company" + rfq.message_for_supplier = "Please quote" + rfq.append("suppliers", {"supplier": "_Test Supplier"}) + rfq.append("suppliers", {"supplier": "_Test Supplier"}) + rfq.append( + "items", + { + "item_code": "_Test Item", + "qty": 5, + "uom": "_Test UOM", + "stock_uom": "_Test UOM", + "conversion_factor": 1.0, + "warehouse": "_Test Warehouse - _TC", + "schedule_date": nowdate(), + }, + ) + self.assertRaises(frappe.ValidationError, rfq.insert) + + def test_rfq_blocked_for_supplier_with_prevent_rfqs(self): + frappe.db.set_value("Supplier", "_Test Supplier", "prevent_rfqs", 1) + rfq = make_request_for_quotation( + supplier_data=[{"supplier": "_Test Supplier", "supplier_name": "_Test Supplier"}], + do_not_save=True, + ) + self.assertRaises(frappe.ValidationError, rfq.save) + + def test_rfq_blocked_for_disabled_supplier(self): + frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1) + rfq = make_request_for_quotation( + supplier_data=[{"supplier": "_Test Supplier", "supplier_name": "_Test Supplier"}], + do_not_save=True, + ) + self.assertRaises(PartyDisabled, rfq.save) + + frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0) + rfq.save() + + def test_rfq_status_lifecycle(self): + rfq = make_request_for_quotation() + self.assertEqual(rfq.status, "Submitted") + + rfq.cancel() + self.assertEqual(rfq.status, "Cancelled") + +>>>>>>> 4bf65ffc1d (fix: block disabled/frozen suppliers on Request for Quotation) def test_make_supplier_quotation(self): rfq = make_request_for_quotation()