mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-18 00:55:02 +00:00
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 <ruthra@erpnext.com>
(cherry picked from commit 01fcd98c84)
# Conflicts:
# erpnext/accounts/report/customer_ledger_summary/test_customer_ledger_summary.py
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user