From 8a5f6596813755fb8750667f1a5cf365137666af Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 21 Jun 2026 14:05:49 +0530 Subject: [PATCH] test(postgres): make test-helper SQL Postgres-valid across the suite The repo-wide query audit fixed runtime/source queries, but test files carry their own raw SQL helpers that were never swept and only fail when the suite runs on Postgres. Port the staging branch's already-green fixes for them: - timestamp(posting_date, posting_time) (raw + qb Timestamp) -> posting_datetime / CombineDatetime (test_stock_ledger_entry, test_stock_balance, test_utils) - HAVING -> qb .having() (test_asset_capitalization, test_purchase_order) - capital-cased identifiers ("Status", "Name") -> lowercase (test_delivery_note, test_purchase_order, test_employee) - raw GL/SLE select helpers -> frappe.get_all / qb, with order-independent comparisons where account ordering is collation-dependent across engines (test_purchase_invoice, test_sales_invoice, test_payment_entry, test_asset, test_purchase_receipt, test_payment_request, test_repost_accounting_ledger, test_journal_entry) All changes are test-only and behaviour-identical on MariaDB (lowercase column names resolve the same; posting_datetime == timestamp(posting_date, posting_time); HAVING on the expression is the same computation). Verified: the heavy modules pass on both MariaDB and Postgres, and MariaDB output is unchanged. --- .../journal_entry/test_journal_entry.py | 63 +-- .../payment_entry/test_payment_entry.py | 66 ++- .../payment_request/test_payment_request.py | 11 +- .../purchase_invoice/test_purchase_invoice.py | 393 ++++++++++-------- .../test_repost_accounting_ledger.py | 2 + .../sales_invoice/test_sales_invoice.py | 303 ++++++++------ erpnext/assets/doctype/asset/test_asset.py | 36 +- .../test_asset_capitalization.py | 46 +- .../purchase_order/test_purchase_order.py | 14 +- .../setup/doctype/employee/test_employee.py | 2 +- .../delivery_note/test_delivery_note.py | 17 +- .../purchase_receipt/test_purchase_receipt.py | 15 +- .../test_stock_ledger_entry.py | 69 ++- .../stock_balance/test_stock_balance.py | 23 +- erpnext/stock/tests/test_utils.py | 4 +- 15 files changed, 606 insertions(+), 458 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index a47745a1ed8..17d86dbf06b 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -43,18 +43,18 @@ class TestJournalEntry(ERPNextTestSuite): if test_voucher.doctype == "Journal Entry": self.assertTrue( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where account = %s and docstatus = 1 and parent = %s""", - ("Debtors - _TC", test_voucher.name), + frappe.get_all( + "Journal Entry Account", + filters={"account": "Debtors - _TC", "docstatus": 1, "parent": test_voucher.name}, + pluck="name", ) ) self.assertFalse( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_type = %s and reference_name = %s""", - (test_voucher.doctype, test_voucher.name), + frappe.get_all( + "Journal Entry Account", + filters={"reference_type": test_voucher.doctype, "reference_name": test_voucher.name}, + pluck="name", ) ) @@ -69,10 +69,14 @@ class TestJournalEntry(ERPNextTestSuite): submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name) self.assertTrue( - frappe.db.sql( - f"""select name from `tabJournal Entry Account` - where reference_type = %s and reference_name = %s and {dr_or_cr}=400""", - (submitted_voucher.doctype, submitted_voucher.name), + frappe.get_all( + "Journal Entry Account", + filters={ + "reference_type": submitted_voucher.doctype, + "reference_name": submitted_voucher.name, + dr_or_cr: 400, + }, + pluck="name", ) ) @@ -82,24 +86,20 @@ class TestJournalEntry(ERPNextTestSuite): def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr): # Test advance paid field - advance_paid = frappe.db.sql( - """select advance_paid from `tab{}` - where name={}""".format(test_voucher.doctype, "%s"), - (test_voucher.name), - ) + advance_paid = frappe.db.get_value(test_voucher.doctype, test_voucher.name, "advance_paid") payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr) - self.assertEqual(flt(advance_paid[0][0]), flt(payment_against_order)) + self.assertEqual(flt(advance_paid), flt(payment_against_order)) def cancel_against_voucher_testcase(self, test_voucher): if test_voucher.doctype == "Journal Entry": # if test_voucher is a Journal Entry, test cancellation of test_voucher test_voucher.cancel() self.assertFalse( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_type='Journal Entry' and reference_name=%s""", - test_voucher.name, + frappe.get_all( + "Journal Entry Account", + filters={"reference_type": "Journal Entry", "reference_name": test_voucher.name}, + pluck="name", ) ) @@ -202,10 +202,10 @@ class TestJournalEntry(ERPNextTestSuite): # cancel jv.cancel() - gle = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - jv.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": jv.name}, + pluck="name", ) self.assertFalse(gle) @@ -526,9 +526,16 @@ class TestJournalEntry(ERPNextTestSuite): gl_entries = query.run(as_dict=True) - for i in range(len(self.expected_gle)): + # MariaDB and Postgres collate `account` differently, so the DB ordering isn't portable; + # sort both sides identically before the positional comparison. + def _key(row): + return tuple(str(row[f]) for f in self.fields) + + gl_entries = sorted(gl_entries, key=_key) + expected_gle = sorted(self.expected_gle, key=_key) + for i in range(len(expected_gle)): for field in self.fields: - self.assertEqual(self.expected_gle[i][field], gl_entries[i][field]) + self.assertEqual(expected_gle[i][field], gl_entries[i][field]) def test_negative_debit_and_credit_with_same_account_head(self): from erpnext.accounts.general_ledger import process_gl_map diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 4dafd031fcc..7cd6e084562 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -818,12 +818,11 @@ class TestPaymentEntry(ERPNextTestSuite): self.assertEqual(expected_gle[gle.account][3], gle.against_voucher) def get_gle(self, voucher_no): - return frappe.db.sql( - """select account, debit, credit, against_voucher - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", - voucher_no, - as_dict=1, + return frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": voucher_no}, + fields=["account", "debit", "credit", "against_voucher"], + order_by="account asc", ) def test_payment_entry_write_off_difference(self): @@ -918,13 +917,19 @@ class TestPaymentEntry(ERPNextTestSuite): "Debtors - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", - pe.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": pe.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -955,13 +960,19 @@ class TestPaymentEntry(ERPNextTestSuite): "Creditors - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", - pe.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": pe.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1770,9 +1781,18 @@ class TestPaymentEntry(ERPNextTestSuite): .where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0)) .orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc) ).run(as_dict=True) - for row in range(len(self.expected_gle)): - for field in ["account", "debit", "credit"]: - self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) + # MariaDB and Postgres collate `account` differently, so the DB ordering isn't portable; + # sort both sides identically before the positional comparison. + fields = ["account", "debit", "credit"] + + def _key(row): + return tuple(str(row[f]) for f in fields) + + gl_entries = sorted(gl_entries, key=_key) + expected_gle = sorted(self.expected_gle, key=_key) + for row in range(len(expected_gle)): + for field in fields: + self.assertEqual(expected_gle[row][field], gl_entries[row][field]) def test_reverse_payment_reconciliation(self): customer = create_customer(frappe.generate_hash(length=10), "INR") diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index a561ebfd73b..f09b9b6a626 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -347,12 +347,11 @@ class TestPaymentRequest(ERPNextTestSuite): ] ) - gl_entries = frappe.db.sql( - """select account, debit, credit, against_voucher - from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", - pe.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": pe.name}, + fields=["account", "debit", "credit", "against_voucher"], + order_by="account asc", ) self.assertTrue(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6ead96438a3..0527f518a65 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -3,6 +3,7 @@ import frappe +from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, flt, getdate, nowdate, today import erpnext @@ -123,11 +124,10 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): "_Test Account Discount - _TC": [0, 168.03], "Round Off - _TC": [0, 0.3], } - gl_entries = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type = 'Purchase Invoice' and voucher_no = %s""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=["account", "debit", "credit"], ) for d in gl_entries: self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) @@ -317,12 +317,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): self.check_gle_for_pi(pi.name) def check_gle_for_pi(self, pi): - gl_entries = frappe.db.sql( - """select account, sum(debit) as debit, sum(credit) as credit - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - group by account""", - pi, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi}, + fields=["account", {"SUM": "debit", "as": "debit"}, {"SUM": "credit", "as": "credit"}], + group_by="account", ) self.assertTrue(gl_entries) @@ -461,12 +460,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): self.assertTrue(pi.status, "Unpaid") - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -475,10 +473,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): ["Creditors - TCP1", 0, 250], ] - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[i][0], gle.account) - self.assertEqual(expected_values[i][1], gle.debit) - self.assertEqual(expected_values[i][2], gle.credit) + # DB account collation isn't portable across MariaDB/Postgres; compare order-independently. + self.assertEqual( + sorted((gle.account, gle.debit, gle.credit) for gle in gl_entries), + sorted((e[0], e[1], e[2]) for e in expected_values), + ) def test_purchase_invoice_calculation(self): pi = frappe.copy_doc(self.globalTestRecords["Purchase Invoice"][0]) @@ -546,21 +545,24 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): pi.load_from_db() self.assertTrue( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_type='Purchase Invoice' - and reference_name=%s and debit_in_account_currency=300""", - pi.name, + frappe.get_all( + "Journal Entry Account", + filters={ + "reference_type": "Purchase Invoice", + "reference_name": pi.name, + "debit_in_account_currency": 300, + }, + pluck="name", ) ) pi.cancel() self.assertFalse( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_type='Purchase Invoice' and reference_name=%s""", - pi.name, + frappe.get_all( + "Journal Entry Account", + filters={"reference_type": "Purchase Invoice", "reference_name": pi.name}, + pluck="name", ) ) @@ -604,10 +606,14 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): pi.load_from_db() self.assertTrue( - frappe.db.sql( - "select name from `tabJournal Entry Account` where reference_type='Purchase Invoice' and " - "reference_name=%s and debit_in_account_currency=300", - pi.name, + frappe.get_all( + "Journal Entry Account", + filters={ + "reference_type": "Purchase Invoice", + "reference_name": pi.name, + "debit_in_account_currency": 300, + }, + pluck="name", ) ) @@ -616,10 +622,10 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): pi.cancel() self.assertFalse( - frappe.db.sql( - "select name from `tabJournal Entry Account` where reference_type='Purchase Invoice' and " - "reference_name=%s", - pi.name, + frappe.get_all( + "Journal Entry Account", + filters={"reference_type": "Purchase Invoice", "reference_name": pi.name}, + pluck="name", ) ) @@ -629,13 +635,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): else: project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"}) - existing_purchase_cost = frappe.db.sql( - f"""select sum(base_net_amount) - from `tabPurchase Invoice Item` - where project = '{project.name}' - and docstatus=1""" + existing_purchase_cost = frappe.get_all( + "Purchase Invoice Item", + filters={"project": project.name, "docstatus": 1}, + fields=[{"SUM": "base_net_amount", "as": "base_net_amount"}], ) - existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 + existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0].base_net_amount or 0 pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name) self.assertEqual( @@ -679,12 +684,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): ) # check gl entries for return - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account desc""", - ("Purchase Invoice", return_pi.name), - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": return_pi.name}, + fields=["account", "debit", "credit"], + order_by="account desc", ) self.assertTrue(gl_entries) @@ -773,13 +777,18 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): conversion_rate=50, ) - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -821,10 +830,10 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): # cancel pi.cancel() - gle = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - pi.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": pi.name}, + pluck="name", ) self.assertFalse(gle) @@ -842,13 +851,18 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): expense_account="_Test Account Cost for Goods Sold - TCP1", ) - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -877,13 +891,16 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): expense_account="_Test Account Cost for Goods Sold - TCP1", ) - gl_entries = frappe.db.sql( - """select account, account_currency, sum(debit) as debit, - sum(credit) as credit, debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - group by account, voucher_no order by account asc;""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + {"SUM": "debit", "as": "debit"}, + {"SUM": "credit", "as": "credit"}, + ], + group_by="account, voucher_no", + order_by="account asc", ) stock_in_hand_account = get_inventory_account(pi.company, pi.get("items")[0].warehouse) @@ -1145,13 +1162,19 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): "_Test Account Cost for Goods Sold - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1168,13 +1191,19 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): "_Test Account Cost for Goods Sold - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1209,13 +1238,20 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): "_Test Account Cost for Goods Sold - _TC": {"project": item_project.name}, } - gl_entries = frappe.db.sql( - """select account, cost_center, project, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=[ + "account", + "cost_center", + "project", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1269,13 +1305,15 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): [deferred_account, 23.07, 0.0, "2019-03-15"], ] - gl_entries = gl_entries = frappe.db.sql( - """select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s - order by posting_date asc, account asc""", - (pi.items[0].name, pi.posting_date), - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Journal Entry", + "voucher_detail_no": pi.items[0].name, + "posting_date": ["<=", pi.posting_date], + }, + fields=["account", "debit", "credit", "posting_date"], + order_by="posting_date asc, account asc", ) for i, gle in enumerate(gl_entries): @@ -1350,14 +1388,14 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): ["_Test Payable USD - _TC", -37500.0], ] - gl_entries = frappe.db.sql( - """ - select account, sum(debit - credit) as balance from `tabGL Entry` - where voucher_no=%s - group by account - order by account asc""", - (pi.name), - as_dict=1, + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select(gle.account, Sum(gle.debit - gle.credit).as_("balance")) + .where(gle.voucher_no == pi.name) + .groupby(gle.account) + .orderby(gle.account) + .run(as_dict=1) ) for i, gle in enumerate(gl_entries): @@ -1421,13 +1459,14 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): ["_Test Payable USD - _TC", -36500.0], ] - gl_entries = frappe.db.sql( - """ - select account, sum(debit - credit) as balance from `tabGL Entry` - where voucher_no=%s - group by account order by account asc""", - (pi_2.name), - as_dict=1, + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select(gle.account, Sum(gle.debit - gle.credit).as_("balance")) + .where(gle.voucher_no == pi_2.name) + .groupby(gle.account) + .orderby(gle.account) + .run(as_dict=1) ) for i, gle in enumerate(gl_entries): @@ -1436,18 +1475,21 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): expected_gle = [["_Test Payable USD - _TC", 70000.0], ["Cash - _TC", -70000.0]] - gl_entries = frappe.db.sql( - """ - select account, sum(debit - credit) as balance from `tabGL Entry` - where voucher_no=%s and is_cancelled=0 - group by account order by account asc""", - (pay.name), - as_dict=1, + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select(gle.account, Sum(gle.debit - gle.credit).as_("balance")) + .where((gle.voucher_no == pay.name) & (gle.is_cancelled == 0)) + .groupby(gle.account) + .orderby(gle.account) + .run(as_dict=1) ) - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.balance) + # DB account collation isn't portable across MariaDB/Postgres; compare order-independently. + self.assertEqual( + sorted((gle.account, gle.balance) for gle in gl_entries), + sorted((e[0], e[1]) for e in expected_gle), + ) total_debit_amount = frappe.db.get_all( "Journal Entry Account", @@ -1546,19 +1588,18 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): [tds_account, 0, 3000], ] - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` - where voucher_type='Payment Entry' and voucher_no=%s - order by account asc""", - (payment_entry.name), - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": payment_entry.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) + # DB account collation isn't portable across MariaDB/Postgres; compare order-independently. + self.assertEqual( + sorted((gle.account, gle.debit, gle.credit) for gle in gl_entries), + sorted((e[0], e[1], e[2]) for e in expected_gle), + ) # Create Purchase Invoice against Purchase Order purchase_invoice = get_mapped_purchase_invoice(po.name) @@ -1572,19 +1613,21 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): # Zero net effect on final TDS payable on invoice expected_gle = [["_Test Account Cost for Goods Sold - _TC", 30000], ["Creditors - _TC", -30000]] - gl_entries = frappe.db.sql( - """select account, sum(debit - credit) as amount - from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no=%s - group by account - order by account asc""", - (purchase_invoice.name), - as_dict=1, + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select(gle.account, Sum(gle.debit - gle.credit).as_("amount")) + .where((gle.voucher_type == "Purchase Invoice") & (gle.voucher_no == purchase_invoice.name)) + .groupby(gle.account) + .orderby(gle.account) + .run(as_dict=1) ) - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.amount) + # DB account collation isn't portable across MariaDB/Postgres; compare order-independently. + self.assertEqual( + sorted((gle.account, gle.amount) for gle in gl_entries), + sorted((e[0], e[1]) for e in expected_gle), + ) payment_entry.load_from_db() tax_allocated = sum( @@ -2476,12 +2519,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): pi.insert() pi.submit() - pr_gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s - order by account asc""", - pr.name, - as_dict=1, + pr_gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) pr_expected_values = [ @@ -2494,12 +2536,11 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin): self.assertEqual(pr_expected_values[i][1], gle.debit) self.assertEqual(pr_expected_values[i][2], gle.credit) - pi_gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s - order by account asc""", - pi.name, - as_dict=1, + pi_gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": pi.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) pi_expected_values = [ ["Asset Received But Not Billed - _TC", 5000, 0], @@ -3047,17 +3088,25 @@ def check_gl_entries( gl_entries = query.run(as_dict=True) - for i, gle in enumerate(gl_entries): - doc.assertEqual(expected_gle[i][0], gle.account) - doc.assertEqual(expected_gle[i][1], gle.debit) - doc.assertEqual(expected_gle[i][2], gle.credit) - doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) + # MariaDB and Postgres collate `account` differently, so the DB row order isn't portable. + # Match each actual GL row against the expected set instead of comparing positionally; like the + # original loop (which iterated the actual rows), extra expected rows are tolerated. + cols = additional_columns or [] - if additional_columns: - j = 4 - for col in additional_columns: - doc.assertEqual(expected_gle[i][j], gle[col]) - j += 1 + def _key(account, debit, credit, posting_date, extras): + return (account, flt(debit), flt(credit), getdate(posting_date), *(str(v) for v in extras)) + + remaining = {} + for e in expected_gle: + k = _key(e[0], e[1], e[2], e[3], e[4 : 4 + len(cols)]) + remaining[k] = remaining.get(k, 0) + 1 + + for gle in gl_entries: + k = _key(gle.account, gle.debit, gle.credit, gle.posting_date, [gle[c] for c in cols]) + doc.assertGreater( + remaining.get(k, 0), 0, msg=f"Unexpected GL entry {k}; expected one of {list(remaining)}" + ) + remaining[k] -= 1 def create_tax_witholding_category(category_name, company, account): diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 935047e2e35..fe1f4c2379d 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -69,6 +69,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite): qb.from_(gl) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) .where((gl.voucher_no == si.name) & (gl.is_cancelled == 0)) + .groupby(gl.voucher_no) .run() ) @@ -82,6 +83,7 @@ class TestRepostAccountingLedger(ERPNextTestSuite): qb.from_(gl) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) .where((gl.voucher_no == si.name) & (gl.is_cancelled == 0)) + .groupby(gl.voucher_no) .run() ) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6f8599d6627..57cfcddbd98 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -745,12 +745,11 @@ class TestSalesInvoice(ERPNextTestSuite): si.insert() si.submit() - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -781,10 +780,10 @@ class TestSalesInvoice(ERPNextTestSuite): # cancel si.cancel() - gle = frappe.db.sql( - """select * from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - si.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["*"], ) self.assertTrue(gle) @@ -1201,12 +1200,11 @@ class TestSalesInvoice(ERPNextTestSuite): si.insert() si.submit() - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1229,10 +1227,10 @@ class TestSalesInvoice(ERPNextTestSuite): # cancel si.cancel() - gle = frappe.db.sql( - """select * from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - si.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["*"], ) self.assertTrue(gle) @@ -1651,11 +1649,10 @@ class TestSalesInvoice(ERPNextTestSuite): cash_amount -= pos.change_amount # check stock ledger entries - sle = frappe.db.sql( - """select * from `tabStock Ledger Entry` - where voucher_type = 'Sales Invoice' and voucher_no = %s""", - si.name, - as_dict=1, + sle = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["*"], )[0] self.assertTrue(sle) self.assertEqual( @@ -1663,12 +1660,11 @@ class TestSalesInvoice(ERPNextTestSuite): ) # check gl entries - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc, debit asc, credit asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc, debit asc, credit asc", ) self.assertTrue(gl_entries) @@ -1695,15 +1691,15 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(expected_gl_entries[i][2], gle.credit) si.cancel() - gle = frappe.db.sql( - """select * from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - si.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["*"], ) self.assertTrue(gle) - frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.delete("POS Profile") def test_bin_details_of_packed_item(self): from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle @@ -1770,12 +1766,11 @@ class TestSalesInvoice(ERPNextTestSuite): si.insert() si.submit() - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1790,12 +1785,11 @@ class TestSalesInvoice(ERPNextTestSuite): def test_sales_invoice_gl_entry_with_perpetual_inventory_non_stock_item(self): si = create_sales_invoice(item="_Test Non Stock Item") - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -1849,18 +1843,18 @@ class TestSalesInvoice(ERPNextTestSuite): si.load_from_db() self.assertTrue( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_name=%s""", - si.name, + frappe.get_all( + "Journal Entry Account", + filters={"reference_name": si.name}, + pluck="name", ) ) self.assertTrue( - frappe.db.sql( - """select name from `tabJournal Entry Account` - where reference_name=%s and credit_in_account_currency=300""", - si.name, + frappe.get_all( + "Journal Entry Account", + filters={"reference_name": si.name, "credit_in_account_currency": 300}, + pluck="name", ) ) @@ -2172,13 +2166,18 @@ class TestSalesInvoice(ERPNextTestSuite): conversion_rate=50, ) - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=[ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -2213,10 +2212,10 @@ class TestSalesInvoice(ERPNextTestSuite): # cancel si.cancel() - gle = frappe.db.sql( - """select name from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s""", - si.name, + gle = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + pluck="name", ) self.assertTrue(gle) @@ -2243,14 +2242,16 @@ class TestSalesInvoice(ERPNextTestSuite): ) si.submit() - gl_entries = frappe.db.sql( - """select transaction_currency, transaction_exchange_rate, - debit_in_transaction_currency, credit_in_transaction_currency - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC' - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name, "account": "Sales - _TC"}, + fields=[ + "transaction_currency", + "transaction_exchange_rate", + "debit_in_transaction_currency", + "credit_in_transaction_currency", + ], + order_by="account asc", ) expected_gle = { @@ -2595,12 +2596,11 @@ class TestSalesInvoice(ERPNextTestSuite): ] ) - gl_entries = frappe.db.sql( - """select account, debit, credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", "debit", "credit"], + order_by="account asc", ) for gle in gl_entries: @@ -2652,13 +2652,12 @@ class TestSalesInvoice(ERPNextTestSuite): "Sales - _TC": [0.0, 1272.20], } - gl_entries = frappe.db.sql( - """select account, sum(debit) as debit, sum(credit) as credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - group by account - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", {"SUM": "debit", "as": "debit"}, {"SUM": "credit", "as": "credit"}], + group_by="account", + order_by="account asc", ) for gle in gl_entries: @@ -2719,13 +2718,12 @@ class TestSalesInvoice(ERPNextTestSuite): ] ) - gl_entries = frappe.db.sql( - """select account, sum(debit) as debit, sum(credit) as credit - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - group by account - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=["account", {"SUM": "debit", "as": "debit"}, {"SUM": "credit", "as": "credit"}], + group_by="account", + order_by="account asc", ) debit_credit_diff = 0 @@ -2735,7 +2733,9 @@ class TestSalesInvoice(ERPNextTestSuite): self.assertEqual(expected_values[gle.account][2], gle.credit) debit_credit_diff += gle.debit - gle.credit - self.assertEqual(debit_credit_diff, 0) + # Postgres returns DECIMAL columns as float (DEC2FLOAT), so a debit-credit sum carries a + # tiny FP residue where MariaDB's DECIMAL arithmetic is exact; assert it's ~0. + self.assertAlmostEqual(debit_credit_diff, 0) round_off_gle = frappe.db.get_value( "GL Entry", @@ -2819,13 +2819,19 @@ class TestSalesInvoice(ERPNextTestSuite): "Sales - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -2862,13 +2868,20 @@ class TestSalesInvoice(ERPNextTestSuite): "Sales - _TC": {"project": item_project.name}, } - gl_entries = frappe.db.sql( - """select account, cost_center, project, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - sales_invoice.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": sales_invoice.name}, + fields=[ + "account", + "cost_center", + "project", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -2885,13 +2898,19 @@ class TestSalesInvoice(ERPNextTestSuite): "Sales - _TC": {"cost_center": cost_center}, } - gl_entries = frappe.db.sql( - """select account, cost_center, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc""", - si.name, - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Sales Invoice", "voucher_no": si.name}, + fields=[ + "account", + "cost_center", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ], + order_by="account asc", ) self.assertTrue(gl_entries) @@ -4169,13 +4188,15 @@ class TestSalesInvoice(ERPNextTestSuite): [deferred_account, 2022.47, 0.0, "2019-03-15"], ] - gl_entries = frappe.db.sql( - """select account, debit, credit, posting_date - from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s - order by posting_date asc, account asc""", - (si.items[0].name, si.posting_date), - as_dict=1, + gl_entries = frappe.get_all( + "GL Entry", + filters={ + "voucher_type": "Journal Entry", + "voucher_detail_no": si.items[0].name, + "posting_date": ["<=", si.posting_date], + }, + fields=["account", "debit", "credit", "posting_date"], + order_by="posting_date asc, account asc", ) for i, gle in enumerate(gl_entries): @@ -4817,7 +4838,8 @@ class TestSalesInvoice(ERPNextTestSuite): {"account": "Temporary Opening - _TC", "debit": 0.0, "credit": 138.09, "is_opening": "Yes"}, ] self.assertEqual(len(actual), 4) - self.assertEqual(expected, actual) + # DB account collation isn't portable across MariaDB/Postgres; compare order-independently. + self.assertCountEqual(actual, expected) @ERPNextTestSuite.change_settings("Accounts Settings", {"enable_common_party_accounting": True}) def test_common_party_with_foreign_currency_jv(self): @@ -5122,7 +5144,7 @@ class TestSalesInvoice(ERPNextTestSuite): def test_pos_sales_invoice_creation_during_pos_invoice_mode(self): # Deleting all opening entry - frappe.db.sql("delete from `tabPOS Opening Entry`") + frappe.db.delete("POS Opening Entry") with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}): pos_profile = make_pos_profile() @@ -5492,6 +5514,11 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type=" doc.assertGreater(len(gl_entries), 0) + # MariaDB and Postgres collate `account` differently, so the DB ordering isn't portable; + # sort both sides identically (by the compared values) before the positional check. + gl_entries = sorted(gl_entries, key=lambda g: (g.account, g.debit, g.credit)) + expected_gle = sorted(expected_gle, key=lambda e: (e[0], e[1], e[2])) + for i, gle in enumerate(gl_entries): doc.assertEqual(expected_gle[i][0], gle.account) doc.assertEqual(expected_gle[i][1], gle.debit) @@ -5639,17 +5666,21 @@ def create_sales_invoice_against_cost_center(**args): def get_outstanding_amount(against_voucher_type, against_voucher, account, party, party_type): - bal = flt( - frappe.db.sql( - """ - select sum(debit_in_account_currency) - sum(credit_in_account_currency) - from `tabGL Entry` - where against_voucher_type=%s and against_voucher=%s - and account = %s and party = %s and party_type = %s""", - (against_voucher_type, against_voucher, account, party, party_type), - )[0][0] - or 0.0 + balance = frappe.get_all( + "GL Entry", + filters={ + "against_voucher_type": against_voucher_type, + "against_voucher": against_voucher, + "account": account, + "party": party, + "party_type": party_type, + }, + fields=[ + {"SUM": "debit_in_account_currency", "as": "debit"}, + {"SUM": "credit_in_account_currency", "as": "credit"}, + ], ) + bal = flt(balance[0].debit) - flt(balance[0].credit) if against_voucher_type == "Purchase Invoice": bal = bal * -1 diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index da14169787b..6e67ffba7fa 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -85,8 +85,8 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) def test_validate_item(self): - asset = create_asset(item_code="MacBook Pro", do_not_save=1) - item = frappe.get_doc("Item", "MacBook Pro") + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + item = frappe.get_doc("Item", "Macbook Pro") item.disabled = 1 item.save() @@ -140,7 +140,7 @@ class TestAsset(AssetSetup): ) gle = get_gl_entries("Purchase Invoice", pi.name) - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) pi.cancel() asset.cancel() @@ -283,7 +283,7 @@ class TestAsset(AssetSetup): ) gle = get_gl_entries("Journal Entry", asset.journal_entry_for_scrap) - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) restore_asset(asset.name) second_asset_depr_schedule.load_from_db() @@ -362,7 +362,7 @@ class TestAsset(AssetSetup): ("Debtors - _TC", 25000.0, 0.0), ) gle = get_gl_entries("Sales Invoice", si.name) - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") @@ -436,7 +436,7 @@ class TestAsset(AssetSetup): ) gle = get_gl_entries("Sales Invoice", si.name) - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) def test_asset_with_maintenance_required_status_after_sale(self): asset = create_asset( @@ -577,7 +577,7 @@ class TestAsset(AssetSetup): ) pr_gle = get_gl_entries("Purchase Receipt", pr.name) - self.assertSequenceEqual(pr_gle, expected_gle) + self.assertCountEqual(pr_gle, expected_gle) pi = make_invoice(pr.name) pi.submit() @@ -590,7 +590,7 @@ class TestAsset(AssetSetup): ) pi_gle = get_gl_entries("Purchase Invoice", pi.name) - self.assertSequenceEqual(pi_gle, expected_gle) + self.assertCountEqual(pi_gle, expected_gle) asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name") @@ -617,7 +617,7 @@ class TestAsset(AssetSetup): expected_gle = (("_Test Fixed Asset - _TC", 5250.0, 0.0), ("CWIP Account - _TC", 0.0, 5250.0)) gle = get_gl_entries("Asset", asset_doc.name) - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") @@ -1732,14 +1732,18 @@ class TestDepreciationBasics(AssetSetup): ("_Test Depreciations - _TC", 30000.0, 0.0), ) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where against_voucher_type='Asset' and against_voucher = %s - order by account""", - asset.name, - ) + gle = [ + tuple(row) + for row in frappe.get_all( + "GL Entry", + filters={"against_voucher_type": "Asset", "against_voucher": asset.name}, + fields=["account", "debit", "credit"], + order_by="account", + as_list=True, + ) + ] - self.assertSequenceEqual(gle, expected_gle) + self.assertCountEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 70000) def test_expected_value_change(self): diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 78266e31b76..531ed374615 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -2,6 +2,7 @@ # See license.txt import frappe +from frappe.query_builder.functions import Sum from frappe.utils import cint, flt, now_datetime from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries @@ -549,34 +550,33 @@ def create_depreciation_asset(**args): def get_actual_gle_dict(name): + gle = frappe.qb.DocType("GL Entry") + diff = Sum(gle.debit - gle.credit) return dict( - frappe.db.sql( - """ - select account, sum(debit-credit) as diff - from `tabGL Entry` - where voucher_type = 'Asset Capitalization' and voucher_no = %s - group by account - having diff != 0 - """, - name, - ) + frappe.qb.from_(gle) + .select(gle.account, diff.as_("diff")) + .where((gle.voucher_type == "Asset Capitalization") & (gle.voucher_no == name)) + .groupby(gle.account) + .having(diff != 0) + .run() ) def get_actual_sle_dict(name): - sles = frappe.db.sql( - """ - select - item_code, warehouse, - sum(actual_qty) as actual_qty, - sum(stock_value_difference) as stock_value_difference - from `tabStock Ledger Entry` - where voucher_type = 'Asset Capitalization' and voucher_no = %s - group by item_code, warehouse - having actual_qty != 0 - """, - name, - as_dict=1, + sle = frappe.qb.DocType("Stock Ledger Entry") + actual_qty = Sum(sle.actual_qty) + sles = ( + frappe.qb.from_(sle) + .select( + sle.item_code, + sle.warehouse, + actual_qty.as_("actual_qty"), + Sum(sle.stock_value_difference).as_("stock_value_difference"), + ) + .where((sle.voucher_type == "Asset Capitalization") & (sle.voucher_no == name)) + .groupby(sle.item_code, sle.warehouse) + .having(actual_qty != 0) + .run(as_dict=1) ) sle_dict = {} diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index fe406d6ad3a..39c618e975e 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -477,10 +477,8 @@ class TestPurchaseOrder(ERPNextTestSuite): item_doc.save() else: # update valid from - frappe.db.sql( - """UPDATE `tabItem Tax` set valid_from = CURRENT_DATE - where parent = %(item)s and item_tax_template = %(tax)s""", - {"item": item, "tax": tax_template}, + frappe.db.set_value( + "Item Tax", {"parent": item, "item_tax_template": tax_template}, "valid_from", nowdate() ) po = create_purchase_order(item_code=item, qty=1, do_not_save=1) @@ -527,10 +525,8 @@ class TestPurchaseOrder(ERPNextTestSuite): self.assertEqual(po.taxes[1].total, 840) # teardown - frappe.db.sql( - """UPDATE `tabItem Tax` set valid_from = NULL - where parent = %(item)s and item_tax_template = %(tax)s""", - {"item": item, "tax": tax_template}, + frappe.db.set_value( + "Item Tax", {"parent": item, "item_tax_template": tax_template}, "valid_from", None ) po.cancel() po.delete() @@ -652,7 +648,7 @@ class TestPurchaseOrder(ERPNextTestSuite): def test_purchase_order_on_hold(self): po = create_purchase_order(item_code="_Test Product Bundle Item") - po.db_set("Status", "On Hold") + po.db_set("status", "On Hold") pi = make_pi_from_po(po.name) pr = make_purchase_receipt(po.name) self.assertRaises(frappe.ValidationError, pr.submit) diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index c1616aa0d58..9af1c88bb0f 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -56,7 +56,7 @@ class TestEmployee(ERPNextTestSuite): frappe.qb.from_(Employee) .select(Employee.name) .where(Criterion.all(build_qb_match_conditions("Employee"))) - .orderby(Employee.Name) + .orderby(Employee.name) ).run(pluck=Employee.name) employee_list = frappe.db.get_list("Employee", pluck="name", order_by="name") diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 68cb91a979c..0d30a693edb 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -924,12 +924,15 @@ class TestDeliveryNote(ERPNextTestSuite): self.assertTrue(gl_entries) stock_value_difference = abs( - frappe.db.sql( - """select sum(stock_value_difference) - from `tabStock Ledger Entry` where voucher_type='Delivery Note' and voucher_no=%s - and warehouse='Stores - TCP1'""", - dn.name, - )[0][0] + frappe.get_all( + "Stock Ledger Entry", + filters={ + "voucher_type": "Delivery Note", + "voucher_no": dn.name, + "warehouse": "Stores - TCP1", + }, + fields=[{"SUM": "stock_value_difference", "as": "svd"}], + )[0].svd ) expected_values = { @@ -955,7 +958,7 @@ class TestDeliveryNote(ERPNextTestSuite): dn.submit() update_delivery_note_status(dn.name, "Closed") - self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed") + self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "status"), "Closed") # Check cancelling closed delivery note dn.load_from_db() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 68c360f9396..959f2a1756e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1267,6 +1267,10 @@ class TestPurchaseReceipt(ERPNextTestSuite): for sle in sl_entries: self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) + # MariaDB and Postgres collate `account` differently, so the DB ordering isn't portable; + # sort both sides identically (by the compared values) before the positional check. + gl_entries = sorted(gl_entries, key=lambda g: (g.account, g.debit, g.credit)) + expected_gle = sorted(expected_gle, key=lambda e: (e[0], e[1], e[2])) for i, gle in enumerate(gl_entries): self.assertEqual(gle.account, expected_gle[i][0]) self.assertEqual(gle.debit, expected_gle[i][1]) @@ -6099,12 +6103,11 @@ def prepare_data_for_internal_transfer(): def get_sl_entries(voucher_type, voucher_no): - return frappe.db.sql( - """ select actual_qty, warehouse, stock_value_difference - from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s - order by posting_time desc""", - (voucher_type, voucher_no), - as_dict=1, + return frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["actual_qty", "warehouse", "stock_value_difference"], + order_by="posting_time desc", ) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 75199928605..57d1b32d978 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -7,7 +7,7 @@ from uuid import uuid4 import frappe from frappe.core.page.permission_manager.permission_manager import reset -from frappe.query_builder.functions import Timestamp +from frappe.query_builder.functions import CombineDatetime from frappe.utils import add_days, add_to_date, flt, today from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -34,6 +34,54 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): create_items() reset("Stock Entry") + def test_incoming_value_for_transferred_serial_no_is_deterministic(self): + """get_incoming_value_for_serial_nos picks the latest SLE (posting_date desc, limit 1) for a + serial transferred to another company. posting_date alone is non-total, so two same-date SLEs + with different incoming_rate could be resolved differently on MariaDB vs Postgres. creation/name + tie-breaks make the latest SLE win identically on both engines.""" + from erpnext.stock.stock_ledger import update_entries_after + + item = "_Test Serialized Item" + serial = "_Test SN Tie 9" + company_a, company_b = "_Test Company", "_Test Company 1" + if frappe.db.exists("Serial No", serial): + frappe.delete_doc("Serial No", serial, force=1) + frappe.get_doc( + {"doctype": "Serial No", "serial_no": serial, "item_code": item, "company": company_b} + ).insert(ignore_permissions=True) + + def mk_sle(name, rate): + if frappe.db.exists("Stock Ledger Entry", name): + frappe.delete_doc("Stock Ledger Entry", name, force=1) + doc = frappe.get_doc( + { + "doctype": "Stock Ledger Entry", + "item_code": item, + "warehouse": "_Test Warehouse - _TC", + "company": company_a, + "posting_date": "2026-06-01", + "posting_time": "10:00:00", + "actual_qty": 1, + "incoming_rate": rate, + "is_cancelled": 0, + "serial_no": serial, + "voucher_type": "Stock Entry", + "voucher_no": "TEST-TIE", + } + ) + doc.name = name + doc.flags.name_set = True + doc.db_insert() + + mk_sle("MAT-SLE-TIE-A", 100) + mk_sle("MAT-SLE-TIE-B", 200) # later/larger name -> deterministic winner + + value = update_entries_after.get_incoming_value_for_serial_nos( + None, frappe._dict(company=company_a), [serial] + ) + # the latest (creation/name desc) same-date SLE wins -> 200 on both engines + self.assertEqual(value, 200.0) + def test_item_cost_reposting(self): company = "_Test Company" @@ -1275,7 +1323,7 @@ class TestStockLedgerEntry(ERPNextTestSuite, StockTestMixin): .where(sle.voucher_no == transfer.name) .where(sle.voucher_type == transfer.doctype) .where(sle.is_cancelled == 0) - .orderby(Timestamp(sle.posting_date, sle.posting_time)) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) .orderby(sle.creation) .run(as_dict=True) ) @@ -1530,17 +1578,12 @@ def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list def fetch_sle_details_for_doc_list(doc_list, columns, as_dict=1): - return frappe.db.sql( - f""" - SELECT { ', '.join(columns)} - FROM `tabStock Ledger Entry` - WHERE - voucher_no IN %(voucher_nos)s - and docstatus = 1 - ORDER BY timestamp(posting_date, posting_time) ASC, CREATION ASC - """, - dict(voucher_nos=[doc.name for doc in doc_list]), - as_dict=as_dict, + return frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": ["in", [doc.name for doc in doc_list]], "docstatus": 1}, + fields=columns, + order_by="posting_datetime asc, creation asc", + as_list=not as_dict, ) diff --git a/erpnext/stock/report/stock_balance/test_stock_balance.py b/erpnext/stock/report/stock_balance/test_stock_balance.py index 2a504f637e0..ba603873234 100644 --- a/erpnext/stock/report/stock_balance/test_stock_balance.py +++ b/erpnext/stock/report/stock_balance/test_stock_balance.py @@ -40,24 +40,15 @@ class TestStockBalance(ERPNextTestSuite): make_stock_entry(item_code=item_code, **movement) def assertInvariants(self, rows): - last_balance = frappe.db.sql( - """ - WITH last_balances AS ( - SELECT item_code, warehouse, - stock_value, qty_after_transaction, - ROW_NUMBER() OVER (PARTITION BY item_code, warehouse - ORDER BY timestamp(posting_date, posting_time) desc, creation desc) - AS rn - FROM `tabStock Ledger Entry` - where is_cancelled=0 - ) - SELECT * FROM last_balances WHERE rn = 1""", - as_dict=True, - ) - item_wh_stock = _dict() - for line in last_balance: + # Latest balance per (item_code, warehouse): first row wins because of the desc ordering. + for line in frappe.get_all( + "Stock Ledger Entry", + filters={"is_cancelled": 0}, + fields=["item_code", "warehouse", "stock_value", "qty_after_transaction"], + order_by="posting_datetime desc, creation desc", + ): item_wh_stock.setdefault((line.item_code, line.warehouse), line) for row in rows: diff --git a/erpnext/stock/tests/test_utils.py b/erpnext/stock/tests/test_utils.py index 1644b9a2488..7736fe49bae 100644 --- a/erpnext/stock/tests/test_utils.py +++ b/erpnext/stock/tests/test_utils.py @@ -1,7 +1,7 @@ import json import frappe -from frappe.query_builder.functions import Timestamp +from frappe.query_builder.functions import CombineDatetime from erpnext.stock.utils import scan_barcode from erpnext.tests.utils import ERPNextTestSuite @@ -35,7 +35,7 @@ class StockTestMixin: query = query.where(sle[key] == value) sles = ( - query.orderby(Timestamp(sle.posting_date, sle.posting_time)) + query.orderby(CombineDatetime(sle.posting_date, sle.posting_time)) .orderby(sle.creation) .run(as_dict=True) )