fix: Payment against credit notes will be considered as payment against parent invoice in Accounts Receivable/Payable report (#35642)
* fix: payment against credit note should be linked to parent invoice
* test: AR/AP report for payment against cr note scenario
* fix: cr_note shows up as outstanding invoice
Payment made against cr_note causes it be reported as outstanding invoice
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index cc2b942..081fe70 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -6,7 +6,6 @@
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
-from frappe.query_builder.functions import IfNull
from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
import erpnext
@@ -127,12 +126,29 @@
return list(journal_entries)
+ def get_return_invoices(self):
+ voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
+ doc = qb.DocType(voucher_type)
+ self.return_invoices = (
+ qb.from_(doc)
+ .select(
+ ConstantColumn(voucher_type).as_("voucher_type"),
+ doc.name.as_("voucher_no"),
+ doc.return_against,
+ )
+ .where(
+ (doc.docstatus == 1)
+ & (doc[frappe.scrub(self.party_type)] == self.party)
+ & (doc.is_return == 1)
+ )
+ .run(as_dict=True)
+ )
+
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
- voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if erpnext.get_party_account_type(self.party_type) == "Receivable":
self.common_filter_conditions.append(ple.account_type == "Receivable")
@@ -140,19 +156,10 @@
self.common_filter_conditions.append(ple.account_type == "Payable")
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
- # get return invoices
- doc = qb.DocType(voucher_type)
- return_invoices = (
- qb.from_(doc)
- .select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
- .where(
- (doc.docstatus == 1)
- & (doc[frappe.scrub(self.party_type)] == self.party)
- & (doc.is_return == 1)
- & (IfNull(doc.return_against, "") == "")
- )
- .run(as_dict=True)
- )
+ self.get_return_invoices()
+ return_invoices = [
+ x for x in self.return_invoices if x.return_against == None or x.return_against == ""
+ ]
outstanding_dr_or_cr = []
if return_invoices:
@@ -204,6 +211,9 @@
accounting_dimensions=self.accounting_dimension_filter_conditions,
)
+ cr_dr_notes = [x.voucher_no for x in self.return_invoices]
+ non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes]
+
if self.invoice_limit:
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 11de9a0..30f7fb3 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -181,6 +181,16 @@
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+
+ # If payment is made against credit note
+ # and credit note is made against a Sales Invoice
+ # then consider the payment against original sales invoice.
+ if ple.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
+ if ple.against_voucher_no in self.return_entries:
+ return_against = self.return_entries.get(ple.against_voucher_no)
+ if return_against:
+ key = (ple.against_voucher_type, return_against, ple.party)
+
row = self.voucher_balance.get(key)
if not row:
@@ -610,7 +620,7 @@
def get_return_entries(self):
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
- filters = {"is_return": 1, "docstatus": 1}
+ filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
party_field = scrub(self.filters.party_type)
if self.filters.get(party_field):
filters.update({party_field: self.filters.get(party_field)})
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index afd02a0..6f1889b 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -210,6 +210,67 @@
],
)
+ def test_payment_against_credit_note(self):
+ """
+ Payment against credit/debit note should be considered against the parent invoice
+ """
+ company = "_Test Company 2"
+ customer = "_Test Customer 2"
+
+ si1 = make_sales_invoice()
+
+ pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2")
+ pe.paid_from = "Debtors - _TC2"
+ pe.insert()
+ pe.submit()
+
+ cr_note = make_credit_note(si1.name)
+
+ si2 = make_sales_invoice()
+
+ # manually link cr_note with si2 using journal entry
+ je = frappe.new_doc("Journal Entry")
+ je.company = company
+ je.voucher_type = "Credit Note"
+ je.posting_date = today()
+
+ debit_account = "Debtors - _TC2"
+ debit_entry = {
+ "account": debit_account,
+ "party_type": "Customer",
+ "party": customer,
+ "debit": 100,
+ "debit_in_account_currency": 100,
+ "reference_type": cr_note.doctype,
+ "reference_name": cr_note.name,
+ "cost_center": "Main - _TC2",
+ }
+ credit_entry = {
+ "account": debit_account,
+ "party_type": "Customer",
+ "party": customer,
+ "credit": 100,
+ "credit_in_account_currency": 100,
+ "reference_type": si2.doctype,
+ "reference_name": si2.name,
+ "cost_center": "Main - _TC2",
+ }
+
+ je.append("accounts", debit_entry)
+ je.append("accounts", credit_entry)
+ je = je.save().submit()
+
+ filters = {
+ "company": company,
+ "report_date": today(),
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120,
+ }
+ report = execute(filters)
+ self.assertEqual(report[1], [])
+
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
@@ -256,7 +317,7 @@
def make_credit_note(docname):
- create_sales_invoice(
+ credit_note = create_sales_invoice(
company="_Test Company 2",
customer="_Test Customer 2",
currency="EUR",
@@ -269,3 +330,5 @@
is_return=1,
return_against=docname,
)
+
+ return credit_note