diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 44410a26bac..717546b0f93 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -309,17 +309,22 @@ def get_account_columns(invoice_list, include_payments): unrealized_profit_loss_account_columns = [] if invoice_list: - expense_accounts = frappe.get_all( - "Purchase Invoice Item", - filters={ - "docstatus": 1, - "expense_account": ["is", "set"], - "parenttype": "Purchase Invoice", - "parent": ["in", [inv.name for inv in invoice_list]], - }, - pluck="expense_account", - distinct=True, - order_by="expense_account", + # frappe drops ORDER BY for distinct queries on postgres (db_query), so sort in python with + # casefold to keep the generated account-column order deterministic and identical on both + # backends, matching MariaDB's case-insensitive collation (the original ORDER BY). + expense_accounts = sorted( + frappe.get_all( + "Purchase Invoice Item", + filters={ + "docstatus": 1, + "expense_account": ["is", "set"], + "parenttype": "Purchase Invoice", + "parent": ["in", [inv.name for inv in invoice_list]], + }, + pluck="expense_account", + distinct=True, + ), + key=str.casefold, ) purchase_taxes_query = get_taxes_query(invoice_list, "Purchase Taxes and Charges", "Purchase Invoice") @@ -331,16 +336,18 @@ def get_account_columns(invoice_list, include_payments): advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head") tax_accounts = set(tax_accounts + advance_tax_accounts) - unrealized_profit_loss_accounts = frappe.get_all( - "Purchase Invoice", - filters={ - "docstatus": 1, - "name": ["in", [inv.name for inv in invoice_list]], - "unrealized_profit_loss_account": ["is", "set"], - }, - pluck="unrealized_profit_loss_account", - distinct=True, - order_by="unrealized_profit_loss_account", + unrealized_profit_loss_accounts = sorted( + frappe.get_all( + "Purchase Invoice", + filters={ + "docstatus": 1, + "name": ["in", [inv.name for inv in invoice_list]], + "unrealized_profit_loss_account": ["is", "set"], + }, + pluck="unrealized_profit_loss_account", + distinct=True, + ), + key=str.casefold, ) for account in expense_accounts: diff --git a/erpnext/accounts/report/purchase_register/test_purchase_register.py b/erpnext/accounts/report/purchase_register/test_purchase_register.py index 9e0e2002f60..f72035496a7 100644 --- a/erpnext/accounts/report/purchase_register/test_purchase_register.py +++ b/erpnext/accounts/report/purchase_register/test_purchase_register.py @@ -24,6 +24,29 @@ class TestPurchaseRegister(ERPNextTestSuite): self.assertEqual(first_row.total_tax, 100) self.assertEqual(first_row.grand_total, 1100) + def test_expense_account_columns_sorted_case_insensitively(self): + # The dynamic expense-account columns must follow MariaDB's case-insensitive collation order and + # be identical on both engines. frappe drops ORDER BY for distinct queries on postgres, so the + # report sorts in python with casefold; plain sorted() would be case-sensitive ("ZZZ" < "aaa"). + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + + company = "_Test Company" + lower = create_account( + account_name="aaa Test Expense", parent_account="Expenses - _TC", company=company + ) + upper = create_account( + account_name="ZZZ Test Expense", parent_account="Expenses - _TC", company=company + ) + for account in (upper, lower): # submit in non-casefold order + make_purchase_invoice(company=company, expense_account=account) + + filters = frappe._dict(company=company, from_date=add_months(today(), -1), to_date=today()) + columns = execute(filters)[0] + labels = [col["label"] for col in columns if col.get("label") in (lower, upper)] + + self.assertEqual(labels, sorted([lower, upper], key=str.casefold)) + def test_purchase_register_ignores_tax_rows_from_other_doctype(self): filters = frappe._dict(company="_Test Company 6", from_date=add_months(today(), -1), to_date=today())