diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a51b27f4b71..23ef9099b6d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -6,7 +6,7 @@ import frappe from frappe import _, bold from frappe.model.document import Document from frappe.model.mapper import map_child_doc, map_doc -from frappe.query_builder.functions import IfNull, Sum +from frappe.query_builder.functions import IfNull, Lower, Sum from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate from frappe.utils.nestedset import get_descendants_of @@ -505,16 +505,20 @@ class POSInvoice(SalesInvoice): if d.get("serial_no"): serial_nos = get_serial_nos(d.serial_no) for sr in serial_nos: - serial_no_exists = frappe.get_all( - "POS Invoice Item", - filters={"parent": self.return_against}, - or_filters=[ - ["serial_no", "=", sr], - ["serial_no", "like", f"{sr}\n%"], - ["serial_no", "like", f"%\n{sr}"], - ["serial_no", "like", f"%\n{sr}\n%"], - ], - limit=1, + POI = frappe.qb.DocType("POS Invoice Item") + s = sr.lower() + serial_no_exists = ( + frappe.qb.from_(POI) + .select(POI.name) + .where(POI.parent == self.return_against) + .where( + (Lower(POI.serial_no) == s) + | Lower(POI.serial_no).like(f"{s}\n%") + | Lower(POI.serial_no).like(f"%\n{s}") + | Lower(POI.serial_no).like(f"%\n{s}\n%") + ) + .limit(1) + .run() ) if not serial_no_exists: diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 2ae16c19d17..1903bc43bcb 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -10,7 +10,7 @@ from frappe import qb, scrub from frappe.desk.reportview import get_filters_cond, get_match_cond from frappe.permissions import has_permission from frappe.query_builder import Case, Criterion, DocType -from frappe.query_builder.functions import Concat, CustomFunction, Length, Locate, Substring, Sum +from frappe.query_builder.functions import Concat, CustomFunction, Length, Locate, Lower, Substring, Sum from frappe.utils import nowdate, today, unique from pypika import Order @@ -313,11 +313,19 @@ def item_query( .where(date_condition) .where(Criterion.any(search_conditions)) .orderby( - Case().when(Locate(txt_no_percent, item.name) > 0, Locate(txt_no_percent, item.name)).else_(99999) + Case() + .when( + Locate(Lower(txt_no_percent), Lower(item.name)) > 0, + Locate(Lower(txt_no_percent), Lower(item.name)), + ) + .else_(99999) ) .orderby( Case() - .when(Locate(txt_no_percent, item.item_name) > 0, Locate(txt_no_percent, item.item_name)) + .when( + Locate(Lower(txt_no_percent), Lower(item.item_name)) > 0, + Locate(Lower(txt_no_percent), Lower(item.item_name)), + ) .else_(99999) ) .orderby(item.idx, order=Order.desc) @@ -406,7 +414,13 @@ def get_project_name( # ordering if txt: # project_name containing search string 'txt' will be given higher precedence - q = q.orderby(ifelse(Locate(txt, proj.project_name) > 0, Locate(txt, proj.project_name), 99999)) + q = q.orderby( + ifelse( + Locate(Lower(txt), Lower(proj.project_name)) > 0, + Locate(Lower(txt), Lower(proj.project_name)), + 99999, + ) + ) q = q.orderby(proj.idx, order=Order.desc).orderby(proj.name) if page_len: diff --git a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py index d7d964d690a..f11b54a0be6 100644 --- a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py +++ b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py @@ -124,13 +124,16 @@ def get_communication_details(filters): FROM `tabCommunication` WHERE - recipients = %s + recipients = %s AND communication_date IS NOT NULL ORDER BY communication_date LIMIT 1 """, (d.contact_email), - )[0][0] + ) + first_contact = first_contact[0][0] if first_contact else None + if not first_contact: + continue duration = flt(date_diff(invoice[0][0], first_contact)) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 2429970dd50..a793d75f6c4 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -9,7 +9,7 @@ import frappe from frappe import _, bold from frappe.model.document import Document from frappe.query_builder import Case -from frappe.query_builder.functions import Coalesce, GroupConcat, Locate, Max, Replace, Sum +from frappe.query_builder.functions import Coalesce, GroupConcat, Locate, Lower, Max, Replace, Sum from frappe.utils import cint, floor, flt, get_link_to_form from frappe.utils.nestedset import get_descendants_of @@ -1301,7 +1301,11 @@ def get_pending_work_orders( & (wo.company == filters.get("company")) & (wo.name.like(f"%{txt}%")) ) - .orderby(Case().when(Locate(txt, wo.name) > 0, Locate(txt, wo.name)).else_(99999)) + .orderby( + Case() + .when(Locate(Lower(txt), Lower(wo.name)) > 0, Locate(Lower(txt), Lower(wo.name))) + .else_(99999) + ) .orderby(wo.name) .limit(cint(page_length)) .offset(start) @@ -1372,7 +1376,10 @@ def get_pick_list_query(doctype: Any, txt: str, searchfield: Any, start: int, pa .where(PICK_LIST.status.isin(["Open", "Partly Delivered"])) .where(PICK_LIST.company == filters.get("company")) .where(SALES_ORDER.customer == filters.get("customer")) - .groupby(PICK_LIST.name) + # customer is from the joined Sales Order, not Pick List's PK, so Postgres rejects it as a bare + # select under GROUP BY pick_list.name; it is pinned to one value by the filter above, so adding + # it to the GROUP BY is valid on Postgres and identical on MariaDB. + .groupby(PICK_LIST.name, SALES_ORDER.customer) ) if filters.get("sales_order"): diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 7ef634cddd6..c442539f1f5 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1964,3 +1964,23 @@ class TestPickList(ERPNextTestSuite): item_codes = [item.item_code for item in doc.items] self.assertIn(item1, item_codes) self.assertIn(item2, item_codes) + + def test_get_pick_list_query_postgres_valid(self): + """get_pick_list_query selects Sales Order.customer (a joined-table column) under + GROUP BY Pick List.name. Postgres rejects that bare column (PK functional dependency does + not cross tables), so the link query raised GroupingError. customer is pinned to one value + by the filter, so adding it to the GROUP BY is identical on MariaDB and valid on Postgres.""" + from erpnext.stock.doctype.pick_list.pick_list import get_pick_list_query + + warehouse = "_Test Warehouse - _TC" + item = make_item().name + make_stock_entry(item=item, to_warehouse=warehouse, qty=100, basic_rate=100) + so = make_sales_order(item_code=item, qty=5, rate=100) + pl = create_pick_list(so.name) + pl.submit() + + # must run without raising on either engine (GroupingError on Postgres before the fix) + result = get_pick_list_query( + "Pick List", "", "name", 0, 20, {"company": so.company, "customer": so.customer} + ) + self.assertIn(pl.name, [row["name"] for row in result])