From d61720c3e2e4c91cab558b6aba571824d0b8d536 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 19 Jun 2026 22:11:25 +0530 Subject: [PATCH] refactor(stock): convert Job Card reference update to qb Replace the raw f-string UPDATE (name + production_item match) that links a Quality Inspection back to its Job Card with frappe.qb.update. Same result on MariaDB; valid under Postgres. Tests: the Job Card reference update and its production_item scoping. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../quality_inspection/quality_inspection.py | 15 +++-- .../test_quality_inspection.py | 59 +++++++++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index dac3ca21038..7a99553aea0 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -215,14 +215,13 @@ class QualityInspection(Document): if self.reference_type == "Job Card": if self.reference_name: - frappe.db.sql( - f""" - UPDATE `tab{self.reference_type}` - SET quality_inspection = %s, modified = %s - WHERE name = %s and production_item = %s - """, - (quality_inspection, self.modified, self.reference_name, self.item_code), - ) + ref = frappe.qb.DocType(self.reference_type) + ( + frappe.qb.update(ref) + .set(ref.quality_inspection, quality_inspection) + .set(ref.modified, self.modified) + .where((ref.name == self.reference_name) & (ref.production_item == self.item_code)) + ).run() else: doctype = self.reference_type + " Item" diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 34b140515f2..9445e5da94f 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -279,6 +279,65 @@ class TestQualityInspection(ERPNextTestSuite): se.delete() + def test_qi_updates_job_card_reference(self): + """Submitting a QI with reference_type 'Job Card' writes its name onto the + Job Card's quality_inspection field (the Job Card branch of + QualityInspection.update_qc_reference).""" + create_item("_Test Item") + + # Job Card whose production_item matches the QI item_code -> must be updated. + matching_jc = make_minimal_job_card(production_item="_Test Item") + # Job Card with a different production_item -> the production_item filter must + # keep it untouched. + other_item = create_item("_Test Item for QC " + frappe.utils.random_string(6)).name + non_matching_jc = make_minimal_job_card(production_item=other_item) + + qa = create_quality_inspection( + item_code="_Test Item", + reference_type="Job Card", + reference_name=matching_jc, + ) + + # The converted UPDATE wrote the QI name onto the matching Job Card. + self.assertEqual( + frappe.db.get_value("Job Card", matching_jc, "quality_inspection"), + qa.name, + ) + # The production_item filter excluded the Job Card with a different item. + self.assertFalse(frappe.db.get_value("Job Card", non_matching_jc, "quality_inspection")) + + def test_qi_job_card_reference_respects_production_item(self): + """A QI referencing a Job Card by name but whose item_code does not match the + Job Card's production_item must NOT update that Job Card.""" + create_item("_Test Item") + mismatch_item = create_item("_Test Item Mismatch QC " + frappe.utils.random_string(6)).name + + # Job Card produces a different item than the QI's item_code. + jc = make_minimal_job_card(production_item=mismatch_item) + + create_quality_inspection( + item_code="_Test Item", + reference_type="Job Card", + reference_name=jc, + ) + + # name matches but production_item != item_code, so the row is left untouched. + self.assertFalse(frappe.db.get_value("Job Card", jc, "quality_inspection")) + + +def make_minimal_job_card(production_item): + """db_insert a minimal submitted Job Card row carrying only the columns the + converted UPDATE reads (name, production_item, quality_inspection, modified).""" + jc = frappe.new_doc("Job Card") + jc.name = "_T-Job Card-" + frappe.utils.random_string(10) + jc.flags.name_set = True + jc.production_item = production_item + jc.company = "_Test Company" + jc.for_quantity = 1 + jc.docstatus = 1 + jc.db_insert() + return jc.name + def create_quality_inspection(**args): args = frappe._dict(args)