From 63ea907881b7dfbe7f36dc3da8bf2055061ccb20 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 29 Jun 2026 16:29:43 +0530 Subject: [PATCH 1/4] fix(accounts): break exchange-rate revaluation GLE ties deterministically calculate_exchange_rate_using_last_gle ordered the latest-GLE lookups by posting_date DESC only. With multiple GL Entries on the latest posting_date the picked row was undefined, so MariaDB and Postgres could choose different vouchers and return a different last_exchange_rate (and revaluation gain/loss). Add gl.name DESC as a tiebreaker so both engines pick the same row; MariaDB row count unchanged. --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 4213d478ce1..0ed30eaee52 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -601,6 +601,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party): .select(gl.voucher_type, gl.voucher_no) .where(Criterion.all(conditions)) .orderby(gl.posting_date, order=Order.desc) + .orderby(gl.name, order=Order.desc) .limit(1) .run()[0] ) @@ -615,6 +616,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party): (gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no) & (gl.account == account) ) .orderby(gl.posting_date, order=Order.desc) + .orderby(gl.name, order=Order.desc) .limit(1) .run()[0][0] ) From 93c186fea797b65e36cf0da116b3ce2289cb9179 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 29 Jun 2026 16:29:44 +0530 Subject: [PATCH 2/4] fix(assets): break latest asset-movement ties deterministically get_latest_location_and_custodian ordered by transaction_date DESC only; equal-dated movements left the current location/custodian engine-dependent. Add asm.name DESC tiebreaker so both engines pick the same movement. --- erpnext/assets/doctype/asset_movement/asset_movement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 674be5c65b3..74ac5e55ee3 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -139,6 +139,7 @@ class AssetMovement(Document): .select(asm_item.target_location, asm_item.to_employee) .where((asm_item.asset == asset) & (asm.company == self.company) & (asm.docstatus == 1)) .orderby(asm.transaction_date, order=frappe.qb.desc) + .orderby(asm.name, order=frappe.qb.desc) .limit(1) .run() ) From e52b9825e398fddef74037bd5ac7c14114aa3836 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 29 Jun 2026 16:29:45 +0530 Subject: [PATCH 3/4] fix(accounts): break last-purchase-rate ties deterministically in Gross Profit get_last_purchase_rate ordered by posting_date DESC only; same-date Purchase Invoices yielded an undefined last_purchase_rate that diverged between MariaDB and Postgres. Add purchase_invoice.name DESC tiebreaker. --- erpnext/accounts/report/gross_profit/gross_profit.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 8785144a8da..61968d603ad 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -895,7 +895,11 @@ class GrossProfitGenerator: if row.cost_center: query = query.where(purchase_invoice_item.cost_center == row.cost_center) - query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1) + query = ( + query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc) + .orderby(purchase_invoice.name, order=frappe.qb.desc) + .limit(1) + ) last_purchase_rate = query.run() return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0 From 70142d147e29943d5e655a049acc1d4a0c6ca8d3 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 29 Jun 2026 16:29:46 +0530 Subject: [PATCH 4/4] fix(selling): break last-sales-amount ties deterministically in Inactive Customers get_last_sales_amt ordered by the sales date DESC only; same-date documents made the reported Last Order Amount engine-dependent. Add name DESC tiebreaker. --- erpnext/selling/report/inactive_customers/inactive_customers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/report/inactive_customers/inactive_customers.py b/erpnext/selling/report/inactive_customers/inactive_customers.py index 2dedb346601..1710566b92a 100644 --- a/erpnext/selling/report/inactive_customers/inactive_customers.py +++ b/erpnext/selling/report/inactive_customers/inactive_customers.py @@ -86,6 +86,7 @@ def get_last_sales_amt(customer, doctype): .select(sales_doctype.base_net_total) .where((sales_doctype.customer == customer) & (sales_doctype.docstatus == 1)) .orderby(date_col, order=frappe.qb.desc) + .orderby(sales_doctype.name, order=frappe.qb.desc) .limit(1) ).run()