From af2f53bee1c6bde6222b661467e1ef6651dc0170 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 21 Jun 2026 05:47:32 +0530 Subject: [PATCH] refactor(controllers): convert make_variant_item_code lookup to query builder make_variant_item_code used a raw frappe.db.sql left join over Item Attribute / Item Attribute Value. Convert to frappe.qb. The attribute_value comparison casts the param with cstr() so Postgres does not error on `varchar = numeric` for numeric attributes (where that side is irrelevant, since numeric_values == 1 already satisfies the OR). MariaDB-identical. Surgical re-apply: develop's get_attribute_value_renames / update_variant_attribute_values helpers and the Case import are preserved. Covered by test_item_variant on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) --- erpnext/controllers/item_variant.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index b125b15fe55..7f52699a816 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -443,13 +443,21 @@ def make_variant_item_code(template_item_code, template_item_name, variant): abbreviations = [] for attr in variant.attributes: - item_attribute = frappe.db.sql( - """select i.numeric_values, v.abbr - from `tabItem Attribute` i left join `tabItem Attribute Value` v - on (i.name=v.parent) - where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""", - {"attribute": attr.attribute, "attribute_value": attr.attribute_value}, - as_dict=True, + ia = frappe.qb.DocType("Item Attribute") + iav = frappe.qb.DocType("Item Attribute Value") + item_attribute = ( + frappe.qb.from_(ia) + .left_join(iav) + .on(ia.name == iav.parent) + .select(ia.numeric_values, iav.abbr) + .where( + (ia.name == attr.attribute) + # attribute_value is a varchar column; cast the param to str so postgres doesn't choke on + # `varchar = numeric` for numeric attributes (where this side is irrelevant anyway, since + # numeric_values == 1 already satisfies the OR). Non-numeric values are already strings. + & ((iav.attribute_value == cstr(attr.attribute_value)) | (ia.numeric_values == 1)) + ) + .run(as_dict=True) ) if not item_attribute: