mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-12 14:11:46 +00:00
Merge pull request #56393 from frappe/revert-56239-pg-parity-case-insensitive
Revert "fix: case-insensitive matching match MariaDB on Postgres"
This commit is contained in:
@@ -3,8 +3,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Lower
|
||||
from frappe.utils import cstr
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -65,9 +63,6 @@ def append_filters(query, report_filters, operations, job_card):
|
||||
if report_filters.get(field):
|
||||
if field == "serial_no":
|
||||
query = query.where(job_card[field].like(f"%{report_filters.get(field)}%"))
|
||||
elif field == "batch_no":
|
||||
# Lower() both sides: exact match stays case-insensitive on Postgres as it is on MariaDB
|
||||
query = query.where(Lower(job_card[field]) == cstr(report_filters.get(field)).lower())
|
||||
elif field == "operation":
|
||||
query = query.where(job_card[field].isin(operations))
|
||||
else:
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_to_date, now
|
||||
|
||||
from erpnext.manufacturing.doctype.job_card.mapper import make_corrective_job_card
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
from erpnext.manufacturing.report.cost_of_poor_quality_report.cost_of_poor_quality_report import execute
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestCostOfPoorQualityReport(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
self.load_test_records("BOM")
|
||||
# BOM with operations for _Test FG Item 2, so submitting the work order creates Job Cards
|
||||
bom = frappe.copy_doc(self.globalTestRecords["BOM"][2])
|
||||
bom.set_rate_of_sub_assembly_item_based_on_bom = 0
|
||||
bom.rm_cost_as_per = "Valuation Rate"
|
||||
bom.items[0].uom = "_Test UOM 1"
|
||||
bom.items[0].conversion_factor = 5
|
||||
bom.insert(ignore_if_duplicate=True)
|
||||
|
||||
def test_batch_no_filter_is_case_insensitive(self):
|
||||
# The report's batch_no filter used an exact `==`, which is case-sensitive on Postgres -- a
|
||||
# differently-cased batch_no would miss job cards that MariaDB (case-insensitive collation)
|
||||
# matches. Lower() both sides keeps MariaDB unchanged and makes Postgres match too.
|
||||
wo = make_wo_order_test_record(item="_Test FG Item 2", qty=2, transfer_material_against="Work Order")
|
||||
for item in wo.required_items:
|
||||
make_stock_entry(
|
||||
item_code=item.item_code,
|
||||
target=item.source_warehouse,
|
||||
qty=item.required_qty * 2,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
job_card = frappe.get_last_doc("Job Card", {"work_order": wo.name})
|
||||
job_card.append(
|
||||
"time_logs", {"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2}
|
||||
)
|
||||
job_card.submit()
|
||||
|
||||
corrective_op = frappe.get_doc(
|
||||
doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
|
||||
).insert()
|
||||
corrective_jc = make_corrective_job_card(
|
||||
job_card.name, operation=corrective_op.name, for_operation=job_card.operation
|
||||
)
|
||||
corrective_jc.hour_rate = 100
|
||||
corrective_jc.insert()
|
||||
corrective_jc.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": add_to_date(now(), hours=2),
|
||||
"to_time": add_to_date(now(), hours=2, minutes=30),
|
||||
"completed_qty": 2,
|
||||
},
|
||||
)
|
||||
corrective_jc.submit()
|
||||
# store an uppercase batch_no; the report is then filtered with a lowercase value
|
||||
corrective_jc.db_set("batch_no", "TESTCOPQBATCH")
|
||||
|
||||
_columns, data = execute(frappe._dict({"batch_no": "testcopqbatch"}))
|
||||
self.assertTrue(any(row.get("name") == corrective_jc.name for row in data))
|
||||
@@ -11,7 +11,7 @@ import frappe.query_builder
|
||||
from frappe import _, _dict, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.query_builder.functions import Concat_ws, Lower, Max, Sum
|
||||
from frappe.query_builder.functions import Concat_ws, Max, Sum
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
cstr,
|
||||
@@ -3389,8 +3389,7 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
||||
query.left_join(serial_batch_entry)
|
||||
.on(stock_ledger_entry.serial_and_batch_bundle == serial_batch_entry.parent)
|
||||
.where(
|
||||
# Lower() both sides so serial-no matching is case-insensitive on Postgres as on MariaDB
|
||||
Lower(serial_batch_entry.serial_no).isin([sn.lower() for sn in serial_nos])
|
||||
serial_batch_entry.serial_no.isin(serial_nos)
|
||||
| Concat_ws("", "\n", stock_ledger_entry.serial_no, "\n").regexp(regex_pattern)
|
||||
)
|
||||
.distinct()
|
||||
|
||||
@@ -80,31 +80,6 @@ class TestSerialandBatchBundle(ERPNextTestSuite):
|
||||
|
||||
self.assertFalse(bundle_doc.name.startswith("SABB-"))
|
||||
|
||||
def test_get_stock_ledgers_for_serial_nos_is_case_insensitive(self):
|
||||
# get_stock_ledgers_for_serial_nos matches Serial and Batch Entry.serial_no with isin(), which is
|
||||
# case-sensitive on Postgres -- a differently-cased serial no would miss entries that MariaDB
|
||||
# (case-insensitive collation) matches. Lower() both sides keeps MariaDB unchanged and makes
|
||||
# Postgres match too.
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
get_stock_ledgers_for_serial_nos,
|
||||
)
|
||||
|
||||
item_code = "Test SBB Case-insensitive Serial Item"
|
||||
make_item(
|
||||
item_code,
|
||||
{"has_serial_no": 1, "serial_no_series": "TESTCISER-.#####", "is_stock_item": 1},
|
||||
)
|
||||
pr = make_purchase_receipt(item_code=item_code, warehouse="_Test Warehouse - _TC", qty=1, rate=100)
|
||||
bundle = pr.items[0].serial_and_batch_bundle
|
||||
serial_no = get_serial_nos_from_bundle(bundle)[0]
|
||||
|
||||
# query with a lowercased serial no; the stored Serial and Batch Entry value is uppercase
|
||||
rows = get_stock_ledgers_for_serial_nos(
|
||||
frappe._dict({"item_code": item_code, "serial_nos": [serial_no.lower()]})
|
||||
)
|
||||
self.assertTrue(any(row.serial_and_batch_bundle == bundle for row in rows))
|
||||
|
||||
def test_inward_outward_serial_valuation(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Lower
|
||||
from frappe.utils import cint, cstr, flt, fmt_money
|
||||
|
||||
from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item
|
||||
@@ -134,12 +133,9 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||
frappe.qb.from_(iva)
|
||||
.select(iva.parent)
|
||||
# attribute_value is a varchar column; cast values to str so postgres doesn't choke on
|
||||
# `varchar = numeric` for numeric attributes (stored values are strings on both backends).
|
||||
# Lower() both sides so matching is case-insensitive on Postgres too, matching MariaDB's
|
||||
# default collation (MariaDB result is unchanged -- it already matches case-insensitively).
|
||||
# `varchar = numeric` for numeric attributes (stored values are strings on both backends)
|
||||
.where(
|
||||
(Lower(iva.attribute) == cstr(attribute).lower())
|
||||
& (Lower(iva.attribute_value).isin([cstr(v).lower() for v in attribute_values]))
|
||||
(iva.attribute == attribute) & (iva.attribute_value.isin([cstr(v) for v in attribute_values]))
|
||||
)
|
||||
.where(iva.parent.isin(item_subquery))
|
||||
.groupby(iva.parent)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
from erpnext.utilities.product import get_item_codes_by_attributes
|
||||
|
||||
|
||||
class TestProduct(ERPNextTestSuite):
|
||||
def test_get_item_codes_by_attributes_is_case_insensitive(self):
|
||||
# get_item_codes_by_attributes matches Item Variant Attribute values. A raw equality is
|
||||
# case-sensitive on Postgres, so a differently-cased filter value would miss variants that
|
||||
# MariaDB (case-insensitive collation) matches. Lower() both sides keeps MariaDB unchanged and
|
||||
# makes Postgres match too.
|
||||
template = "_Test Variant Item"
|
||||
variant = create_variant(template, {"Test Size": "Small"})
|
||||
if not frappe.db.exists("Item", variant.name):
|
||||
variant.insert()
|
||||
self.addCleanup(frappe.delete_doc, "Item", variant.name, force=True)
|
||||
|
||||
# stored attribute value is "Small"; query with a different case
|
||||
matches = get_item_codes_by_attributes({"Test Size": ["small"]}, template)
|
||||
self.assertIn(variant.name, matches)
|
||||
Reference in New Issue
Block a user