mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-19 09:35:03 +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":
|
if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes":
|
||||||
self.party_data[gle.party].opening_balance += amount
|
self.party_data[gle.party].opening_balance += amount
|
||||||
else:
|
else:
|
||||||
if amount > 0:
|
# Cache the party data reference to avoid repeated dictionary lookups
|
||||||
self.party_data[gle.party].invoiced_amount += amount
|
party_data = self.party_data[gle.party]
|
||||||
elif gle.voucher_no in self.return_invoices:
|
|
||||||
self.party_data[gle.party].return_amount -= amount
|
# 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:
|
else:
|
||||||
self.party_data[gle.party].paid_amount -= amount
|
if amount > 0:
|
||||||
|
party_data.invoiced_amount += amount
|
||||||
|
else:
|
||||||
|
party_data.paid_amount -= amount
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for party, row in self.party_data.items():
|
for party, row in self.party_data.items():
|
||||||
@@ -289,7 +302,7 @@ class PartyLedgerSummaryReport:
|
|||||||
or row.invoiced_amount
|
or row.invoiced_amount
|
||||||
or row.paid_amount
|
or row.paid_amount
|
||||||
or row.return_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(
|
total_party_adjustment = sum(
|
||||||
amount for amount in self.party_adjustment_details.get(party, {}).values()
|
amount for amount in self.party_adjustment_details.get(party, {}).values()
|
||||||
@@ -313,6 +326,7 @@ class PartyLedgerSummaryReport:
|
|||||||
gle.party,
|
gle.party,
|
||||||
gle.voucher_type,
|
gle.voucher_type,
|
||||||
gle.voucher_no,
|
gle.voucher_no,
|
||||||
|
gle.against_voucher, # For handling returned invoices (Credit/Debit Notes)
|
||||||
gle.debit,
|
gle.debit,
|
||||||
gle.credit,
|
gle.credit,
|
||||||
gle.is_opening,
|
gle.is_opening,
|
||||||
|
|||||||
@@ -150,3 +150,157 @@ class TestCustomerLedgerSummary(FrappeTestCase, AccountsTestMixin):
|
|||||||
for field in expected_after_cr_and_payment:
|
for field in expected_after_cr_and_payment:
|
||||||
with self.subTest(field=field):
|
with self.subTest(field=field):
|
||||||
self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(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