From b10cf2fb652bfba79a7e9ecc5fb533daf2d00993 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 25 Jun 2026 14:39:05 +0530 Subject: [PATCH 1/4] fix(postgres): POS item price ignores NULL valid_from ordering on Postgres get_items orders Item Price by valid_from and picks the first match. MariaDB sorts NULL valid_from last under DESC (so a dated price wins); Postgres sorts it first, so the undated base price wins and POS shows the wrong rate. Order by (valid_from IS NULL) asc, valid_from desc so NULLs sort last on both backends regardless of any real date value (MariaDB unchanged). --- erpnext/selling/page/point_of_sale/point_of_sale.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index a96fc309687..8f0bc136458 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -231,6 +231,7 @@ def get_items( .where(ItemPrice.selling == 1) .where((ItemPrice.valid_from <= current_date) | (ItemPrice.valid_from.isnull())) .where((ItemPrice.valid_upto >= current_date) | (ItemPrice.valid_upto.isnull())) + .orderby(ItemPrice.valid_from.isnull(), order=Order.asc) .orderby(ItemPrice.valid_from, order=Order.desc) ).run(as_dict=True) From 5fb16ca20cc1acbfdde031d07f0d97aeb976e83f Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 25 Jun 2026 14:39:07 +0530 Subject: [PATCH 2/4] fix(postgres): get_item_price picks undated price on Postgres Both Item Price lookups order by valid_from desc and take the first valid row. MariaDB sorts NULL valid_from last; Postgres first, so the undated base price was winning over a dated one. Order by (valid_from IS NULL) asc, valid_from desc (the get_all is converted to qb since its order_by won't take such an expression). MariaDB output is unchanged. --- erpnext/stock/get_item_details.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 11990db26b8..4ec75996608 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1121,17 +1121,20 @@ def insert_item_price(ctx: ItemDetailsCtx): or getdate() ) - item_prices = frappe.get_all( - "Item Price", - filters={ - "item_code": ctx.item_code, - "price_list": ctx.price_list, - "currency": ctx.currency, - "uom": ctx.stock_uom, - }, - fields=["name", "price_list_rate", "valid_from", "valid_upto"], - order_by="valid_from desc, creation desc", - ) + ip = frappe.qb.DocType("Item Price") + item_prices = ( + frappe.qb.from_(ip) + .select(ip.name, ip.price_list_rate, ip.valid_from, ip.valid_upto) + .where( + (ip.item_code == ctx.item_code) + & (ip.price_list == ctx.price_list) + & (ip.currency == ctx.currency) + & (ip.uom == ctx.stock_uom) + ) + .orderby(ip.valid_from.isnull(), order=frappe.qb.asc) + .orderby(ip.valid_from, order=frappe.qb.desc) + .orderby(ip.creation, order=frappe.qb.desc) + ).run(as_dict=True) item_price = next( ( row @@ -1233,6 +1236,7 @@ def get_item_price( & (ip.price_list == pctx.price_list) & (IfNull(ip.uom, "").isin(["", pctx.uom])) ) + .orderby(ip.valid_from.isnull(), order=frappe.qb.asc) .orderby(ip.valid_from, order=frappe.qb.desc) .orderby(IfNull(ip.batch_no, ""), order=frappe.qb.desc) .orderby(ip.uom, order=frappe.qb.desc) From 934b5065fcb5cd0acfab7fc1e610e0d6a2f8c6ff Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 25 Jun 2026 14:39:08 +0530 Subject: [PATCH 3/4] fix(postgres): expiry-based serial selection picks wrong serials on Postgres get_serial_nos_based_on_filters with based_on='Expiry' orders by amc_expiry_date asc and limits to qty. MariaDB sorts NULL (no-AMC) serials first; Postgres last, so a different set of serials is auto-selected. Order by (amc_expiry_date IS NULL) desc, amc_expiry_date so NULLs sort first on both (MariaDB unchanged). --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index cd706b800a5..951023d8bb7 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2432,6 +2432,8 @@ def get_serial_nos_based_on_filters(filters, fields, order_by, kwargs): if kwargs.based_on == "LIFO": query = query.orderby(order_by_column, order=frappe.query_builder.Order.desc) else: + if order_by == "amc_expiry_date": + query = query.orderby(order_by_column.isnull(), order=frappe.query_builder.Order.desc) query = query.orderby(order_by_column) for key, value in filters.items(): From e8e50edbed1373d87ce8e953e58c02e27c843fbb Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 25 Jun 2026 14:39:08 +0530 Subject: [PATCH 4/4] fix(postgres): pricing rule priority order diverges on Postgres _get_pricing_rules orders by priority desc; get_pricing_rules then reads pricing_rules[0].has_priority. priority is a Select (varchar): unset is '' on MariaDB but NULL on Postgres, which sorts to the top under DESC and flips the selection. Order by coalesce(priority, '') desc so the unset value sorts last ('' is the text minimum) on both backends. --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 9fabb2bdc89..1dd6febc0e7 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -152,7 +152,7 @@ def _get_pricing_rules(apply_on, args, values): and {child_doc}.parent = `tabPricing Rule`.name and `tabPricing Rule`.disable = 0 and `tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions} - order by `tabPricing Rule`.priority desc, + order by coalesce(`tabPricing Rule`.priority, '') desc, `tabPricing Rule`.name desc""".format( child_doc=child_doc, apply_on_field=apply_on_field,