From 7f6a234cf7883f360b60fe05932e11ce7da8cf0a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 1 Jul 2026 09:10:16 +0530 Subject: [PATCH 1/4] fix(stock): pin get_item_price tie-break so MariaDB and Postgres agree get_item_price ORDER BYs valid_from/batch_no/uom/party then LIMIT 1 with no unique key. Two Item Price rows tied on all of those but differing price_list_rate would be picked arbitrarily -- MariaDB and Postgres can return a different rate. Append a name tiebreaker; for exact ties MariaDB's pick was already undefined, so its output is preserved. Co-Authored-By: Claude Opus 4.8 --- erpnext/stock/get_item_details.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 33aceab0bf3..aa9750f7523 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1269,6 +1269,10 @@ def get_item_price( & (IfNull(ip.valid_upto, "2500-12-31") >= pctx.transaction_date) ) + # Final unique tiebreaker: rows tied on every sort key above (same valid_from/batch/uom/party) + # would otherwise be picked arbitrarily -- MariaDB and Postgres can differ. Pin the pick. + query = query.orderby(ip.name, order=frappe.qb.desc) + return query.run(as_dict=True) From 813dcca29aadecb77e6bc31eacf0469f147a5f5a Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 1 Jul 2026 09:10:18 +0530 Subject: [PATCH 2/4] fix(accounts): tie-break open Payment Request ordering for cross-engine parity get_open_payment_requests_for_references orders by Coalesce(transaction_date, creation); when transaction_date is set the coalesce never falls back to creation, so PRs sharing a transaction_date have no tiebreaker and MariaDB/Postgres can allocate a different PR first. Append creation, name keys. Co-Authored-By: Claude Opus 4.8 --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b4005436ec0..706644c9819 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2795,6 +2795,9 @@ def get_open_payment_requests_for_references(references=None): .where(PR.docstatus == 1) .where(PR.outstanding_amount > 0) # to avoid old PRs with 0 outstanding amount .orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc) + # unique tiebreaker so PRs sharing a transaction_date allocate in the same order on both engines + .orderby(PR.creation, order=frappe.qb.asc) + .orderby(PR.name, order=frappe.qb.asc) ).run(as_dict=True) if not response: From 7a798dcba9e7c0721ad654334d71bd49999b7166 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 1 Jul 2026 09:10:18 +0530 Subject: [PATCH 3/4] fix(stock): tie-break pick-list lookup in update_packed_item_with_pick_list_info The Pick List Item get_value orders only by qty desc; a pick list can hold multiple rows for the same SO item split across warehouses/batches/serials that tie on qty, so MariaDB and Postgres could stamp a different warehouse/batch/serial onto the packed item. Add a name tiebreaker. Co-Authored-By: Claude Opus 4.8 --- erpnext/stock/doctype/packed_item/packed_item.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 5aff12994c9..9ec908c6b62 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -326,7 +326,8 @@ def update_packed_item_with_pick_list_info(main_item_row, pi_row): }, ["warehouse", "batch_no", "serial_no"], as_dict=True, - order_by="qty desc", + # name tiebreaker: split pick-list rows can tie on qty -> pick the same warehouse/batch/serial on both engines + order_by="qty desc, name asc", ) if not pl_row: From 8f3eb6cb313d864a6e8d63a26e340e54d9d55db5 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 1 Jul 2026 09:10:19 +0530 Subject: [PATCH 4/4] fix(selling): tie-break POS customer contact pick for cross-engine parity The contact lookup orders only by is_primary_contact desc then takes contacts[0]; contacts commonly tie (the no-primary case), so MariaDB and Postgres could pick a different contact. Add a parent (contact name) tiebreaker. Co-Authored-By: Claude Opus 4.8 --- erpnext/selling/page/point_of_sale/point_of_sale.py | 3 +++ 1 file changed, 3 insertions(+) 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 8f0bc136458..bd96ae38cc9 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -464,6 +464,9 @@ def set_customer_info(fieldname: str, customer: str, value: str = ""): & (DynamicLink.link_doctype == "Customer") ) .orderby(Contact.is_primary_contact, order=Order.desc) + # tiebreaker: contacts tie on is_primary_contact (the common no-primary case) -> + # pick the same one on MariaDB and Postgres + .orderby(DynamicLink.parent, order=Order.asc) ) contacts = query.run(pluck=DynamicLink.parent)