mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 08:54:45 +00:00
Merge pull request #48800 from frappe/mergify/bp/develop/pr-48041
fix: Misclassification of Journal Voucher Entries in Customer Ledger Summary (backport #48041)
This commit is contained in:
@@ -277,12 +277,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():
|
||||||
@@ -291,7 +304,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()
|
||||||
@@ -322,6 +335,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,
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase):
|
|||||||
"customer_name": "_Test Customer",
|
"customer_name": "_Test Customer",
|
||||||
"party_name": "_Test Customer",
|
"party_name": "_Test Customer",
|
||||||
"opening_balance": 0,
|
"opening_balance": 0,
|
||||||
"invoiced_amount": 200.0,
|
"invoiced_amount": 100.0,
|
||||||
"paid_amount": 100.0,
|
"paid_amount": 0.0,
|
||||||
"return_amount": 100.0,
|
"return_amount": 100.0,
|
||||||
"closing_balance": 0.0,
|
"closing_balance": 0.0,
|
||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
@@ -234,3 +234,157 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
self.assertEqual(expected, data[0])
|
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}",
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user