From d915c2b404b4be1be86d81e91f5446cf6aa792b2 Mon Sep 17 00:00:00 2001 From: Assem Bahnasy Date: Mon, 28 Jul 2025 12:05:59 +0300 Subject: [PATCH 1/3] fix: Misclassification of Journal Voucher Entries in Customer Ledger Summary (#48041) * fix: miscalculation of Invoiced Amount, Paid Amount, and Credit Amount in Customer Ledger Summary * style: Apply ruff-format to customer_ledger_summary.py and ignore .venv/ * fix: Ensure .venv/ is ignored in .gitignore * chore: removing backportrc line * test: adding test_journal_voucher_against_return_invoice() * fix: fixed test_journal_voucher_against_return_invoice function * Revert .gitignore changes --------- Co-authored-by: ruthra kumar (cherry picked from commit 01fcd98c84f0c5014b36a79bdca7db10aea63132) # Conflicts: # erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py --- .../customer_ledger_summary.py | 26 ++- .../test_customer_ledger_summary.py | 156 ++++++++++++++++++ 2 files changed, 176 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 16cea462652..6762d6d9cf3 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -277,12 +277,25 @@ class PartyLedgerSummaryReport: if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes": self.party_data[gle.party].opening_balance += amount else: - if amount > 0: - self.party_data[gle.party].invoiced_amount += amount - elif gle.voucher_no in self.return_invoices: - self.party_data[gle.party].return_amount -= amount + # Cache the party data reference to avoid repeated dictionary lookups + party_data = self.party_data[gle.party] + + # Check if this is a direct return invoice (most specific condition first) + if gle.voucher_no in self.return_invoices: + party_data.return_amount -= amount + # Check if this entry is against a return invoice + elif gle.against_voucher in self.return_invoices: + # For entries against return invoices, positive amounts are payments + if amount > 0: + party_data.paid_amount -= amount + else: + party_data.invoiced_amount += amount + # Normal transaction logic else: - self.party_data[gle.party].paid_amount -= amount + if amount > 0: + party_data.invoiced_amount += amount + else: + party_data.paid_amount -= amount out = [] for party, row in self.party_data.items(): @@ -291,7 +304,7 @@ class PartyLedgerSummaryReport: or row.invoiced_amount or row.paid_amount or row.return_amount - or row.closing_amount + or row.closing_balance # Fixed typo from closing_amount to closing_balance ): total_party_adjustment = sum( amount for amount in self.party_adjustment_details.get(party, {}).values() @@ -322,6 +335,7 @@ class PartyLedgerSummaryReport: gle.party, gle.voucher_type, gle.voucher_no, + gle.against_voucher, # For handling returned invoices (Credit/Debit Notes) gle.debit, gle.credit, gle.is_opening, diff --git a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py index 76e06f8b8df..077c6bf87b7 100644 --- a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py @@ -152,6 +152,7 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): with self.subTest(field=field): self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(field)) +<<<<<<< HEAD def test_customer_ledger_ignore_cr_dr_filter(self): si = create_sales_invoice() @@ -234,3 +235,158 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): ) self.assertEqual(len(data), 1) self.assertEqual(expected, data[0]) +======= + def test_journal_voucher_against_return_invoice(self): + filters = {"company": self.company, "from_date": today(), "to_date": today()} + + # Create Sales Invoice of 10 qty at rate 100 (Amount: 1000.0) + si1 = self.create_sales_invoice(do_not_submit=True) + si1.save().submit() + + expected = { + "party": "_Test Customer", + "party_name": "_Test Customer", + "opening_balance": 0, + "invoiced_amount": 1000.0, + "paid_amount": 0, + "return_amount": 0, + "closing_balance": 1000.0, + "currency": "INR", + "customer_name": "_Test Customer", + } + + report = execute(filters)[1] + self.assertEqual(len(report), 1) + for field in expected: + with self.subTest(field=field): + actual_value = report[0].get(field) + expected_value = expected.get(field) + self.assertEqual( + actual_value, + expected_value, + f"Field {field} does not match expected value. " + f"Expected: {expected_value}, Got: {actual_value}", + ) + + # Create Payment Entry (Receive) for the first invoice + pe1 = self.create_payment_entry(si1.name, True) + pe1.paid_amount = 1000 # Full payment 1000.0 + pe1.save().submit() + + expected_after_payment = { + "party": "_Test Customer", + "party_name": "_Test Customer", + "opening_balance": 0, + "invoiced_amount": 1000.0, + "paid_amount": 1000.0, + "return_amount": 0, + "closing_balance": 0.0, + "currency": "INR", + "customer_name": "_Test Customer", + } + + report = execute(filters)[1] + self.assertEqual(len(report), 1) + for field in expected_after_payment: + with self.subTest(field=field): + actual_value = report[0].get(field) + expected_value = expected_after_payment.get(field) + self.assertEqual( + actual_value, + expected_value, + f"Field {field} does not match expected value. " + f"Expected: {expected_value}, Got: {actual_value}", + ) + + # Create Credit Note (return invoice) for first invoice (1000.0) + cr_note = self.create_credit_note(si1.name, do_not_submit=True) + cr_note.items[0].qty = -10 # 1 item of qty 10 at rate 100 (Amount: 1000.0) + cr_note.save().submit() + + expected_after_cr_note = { + "party": "_Test Customer", + "party_name": "_Test Customer", + "opening_balance": 0, + "invoiced_amount": 1000.0, + "paid_amount": 1000.0, + "return_amount": 1000.0, + "closing_balance": -1000.0, + "currency": "INR", + "customer_name": "_Test Customer", + } + + report = execute(filters)[1] + self.assertEqual(len(report), 1) + for field in expected_after_cr_note: + with self.subTest(field=field): + actual_value = report[0].get(field) + expected_value = expected_after_cr_note.get(field) + self.assertEqual( + actual_value, + expected_value, + f"Field {field} does not match expected value. " + f"Expected: {expected_value}, Got: {actual_value}", + ) + + # Create Payment Entry for the returned amount (1000.0) - Pay the customer back + pe2 = get_payment_entry("Sales Invoice", cr_note.name, bank_account=self.cash) + pe2.insert().submit() + + expected_after_cr_and_return_payment = { + "party": "_Test Customer", + "party_name": "_Test Customer", + "opening_balance": 0, + "invoiced_amount": 1000.0, + "paid_amount": 0, + "return_amount": 1000.0, + "closing_balance": 0, + "currency": "INR", + } + + report = execute(filters)[1] + self.assertEqual(len(report), 1) + for field in expected_after_cr_and_return_payment: + with self.subTest(field=field): + actual_value = report[0].get(field) + expected_value = expected_after_cr_and_return_payment.get(field) + self.assertEqual( + actual_value, + expected_value, + f"Field {field} does not match expected value. " + f"Expected: {expected_value}, Got: {actual_value}", + ) + + # Create second Sales Invoice of 10 qty at rate 100 (Amount: 1000.0) + si2 = self.create_sales_invoice(do_not_submit=True) + si2.save().submit() + + # Create Payment Entry (Receive) for the second invoice - payment (500.0) + pe3 = self.create_payment_entry(si2.name, True) + pe3.paid_amount = 500 # Partial payment 500.0 + pe3.save().submit() + + expected_after_cr_and_payment = { + "party": "_Test Customer", + "party_name": "_Test Customer", + "opening_balance": 0.0, + "invoiced_amount": 2000.0, + "paid_amount": 500.0, + "return_amount": 1000.0, + "closing_balance": 500.0, + "currency": "INR", + "customer_name": "_Test Customer", + } + + report = execute(filters)[1] + self.assertEqual(len(report), 1) + for field in expected_after_cr_and_payment: + with self.subTest(field=field): + actual_value = report[0].get(field) + expected_value = expected_after_cr_and_payment.get(field) + self.assertEqual( + actual_value, + expected_value, + f"Field {field} does not match expected value. " + f"Expected: {expected_value}, Got: {actual_value}", + ) +>>>>>>> 01fcd98c84 (fix: Misclassification of Journal Voucher Entries in Customer Ledger Summary (#48041)) From 56085fe6a9a18c7f1325e06dbb1e60afc3e7c29e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 28 Jul 2025 15:00:09 +0530 Subject: [PATCH 2/3] chore: resolve conflict --- .../customer_ledger_summary/test_customer_ledger_summary.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py index 077c6bf87b7..cfe627edbbb 100644 --- a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py @@ -152,7 +152,6 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): with self.subTest(field=field): self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(field)) -<<<<<<< HEAD def test_customer_ledger_ignore_cr_dr_filter(self): si = create_sales_invoice() @@ -235,7 +234,7 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): ) self.assertEqual(len(data), 1) self.assertEqual(expected, data[0]) -======= + def test_journal_voucher_against_return_invoice(self): filters = {"company": self.company, "from_date": today(), "to_date": today()} @@ -389,4 +388,3 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): f"Field {field} does not match expected value. " f"Expected: {expected_value}, Got: {actual_value}", ) ->>>>>>> 01fcd98c84 (fix: Misclassification of Journal Voucher Entries in Customer Ledger Summary (#48041)) From 96c59e0435e7c84d12fbc27e8108585781c43eec Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 28 Jul 2025 15:48:41 +0530 Subject: [PATCH 3/3] refactor(test): fix test data; no double counting --- .../customer_ledger_summary/test_customer_ledger_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py index cfe627edbbb..78174b097bd 100644 --- a/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py @@ -188,8 +188,8 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase): "customer_name": "_Test Customer", "party_name": "_Test Customer", "opening_balance": 0, - "invoiced_amount": 200.0, - "paid_amount": 100.0, + "invoiced_amount": 100.0, + "paid_amount": 0.0, "return_amount": 100.0, "closing_balance": 0.0, "currency": "INR",