fix(selling): make POS item-price NULL ordering match across engines (Postgres)

POS get_items keeps Item Price rows with a NULL valid_from (open-ended base
price) alongside dated rows, orders by valid_from DESC, then picks the first
matching UOM positionally via next()/[0]. MariaDB sorts NULL last for DESC, so
a dated override wins; PostgreSQL defaults to NULLS FIRST for DESC, so the
NULL-valid_from base price wins instead — the POS shows a different
price_list_rate/currency on the two engines for an item that has both an
undated standing price and a dated price.

Coalesce(valid_from, "1900-01-01") in the ORDER BY forces the NULL row to sort
last on both engines. MariaDB already placed it last for DESC, so its output is
unchanged; PostgreSQL now picks the same dated override.
This commit is contained in:
Mihir Kandoi
2026-06-23 19:38:25 +05:30
parent 247d574283
commit 20e6a6e149

View File

@@ -6,6 +6,7 @@ import json
import frappe
from frappe.query_builder import Criterion, DocType, Order
from frappe.query_builder.functions import Coalesce
from frappe.utils import cint, get_datetime
from frappe.utils.nestedset import get_root_of
@@ -231,7 +232,10 @@ 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, order=Order.desc)
# Coalesce so a NULL valid_from (open-ended base price) sorts last under DESC on both
# engines: MariaDB already sorts NULL last for DESC, Postgres defaults to NULLS FIRST, which
# would otherwise make the base price win the positional pick over a dated override.
.orderby(Coalesce(ItemPrice.valid_from, "1900-01-01"), order=Order.desc)
).run(as_dict=True)
stock_uom_price = next((d for d in item_prices if d.get("uom") == item.stock_uom), {})