mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-16 08:05:00 +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>
This commit is contained in:
@@ -275,12 +275,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():
|
||||
@@ -289,7 +302,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()
|
||||
@@ -313,6 +326,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,
|
||||
|
||||
@@ -150,3 +150,157 @@ class TestCustomerLedgerSummary(FrappeTestCase, AccountsTestMixin):
|
||||
for field in expected_after_cr_and_payment:
|
||||
with self.subTest(field=field):
|
||||
self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(field))
|
||||
|
||||
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}",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user