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
This commit is contained in:
ruthra kumar 2023-06-12 17:35:13 +05:30 committed by GitHub
parent 2f24546b21
commit 42f4f80e0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 17 deletions

View File

@ -6,7 +6,6 @@ import frappe
from frappe import _, msgprint, qb from frappe import _, msgprint, qb
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn 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 from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
import erpnext import erpnext
@ -127,12 +126,29 @@ class PaymentReconciliation(Document):
return list(journal_entries) 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): def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True) self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry") 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": if erpnext.get_party_account_type(self.party_type) == "Receivable":
self.common_filter_conditions.append(ple.account_type == "Receivable") self.common_filter_conditions.append(ple.account_type == "Receivable")
@ -140,19 +156,10 @@ class PaymentReconciliation(Document):
self.common_filter_conditions.append(ple.account_type == "Payable") self.common_filter_conditions.append(ple.account_type == "Payable")
self.common_filter_conditions.append(ple.account == self.receivable_payable_account) self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
# get return invoices self.get_return_invoices()
doc = qb.DocType(voucher_type) return_invoices = [
return_invoices = ( x for x in self.return_invoices if x.return_against == None or x.return_against == ""
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)
)
outstanding_dr_or_cr = [] outstanding_dr_or_cr = []
if return_invoices: if return_invoices:
@ -204,6 +211,9 @@ class PaymentReconciliation(Document):
accounting_dimensions=self.accounting_dimension_filter_conditions, 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: if self.invoice_limit:
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit] non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]

View File

@ -181,6 +181,16 @@ class ReceivablePayableReport(object):
return return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) 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) row = self.voucher_balance.get(key)
if not row: if not row:
@ -610,7 +620,7 @@ class ReceivablePayableReport(object):
def get_return_entries(self): def get_return_entries(self):
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" 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) party_field = scrub(self.filters.party_type)
if self.filters.get(party_field): if self.filters.get(party_field):
filters.update({party_field: self.filters.get(party_field)}) filters.update({party_field: self.filters.get(party_field)})

View File

@ -210,6 +210,67 @@ class TestAccountsReceivable(FrappeTestCase):
], ],
) )
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): def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator") frappe.set_user("Administrator")
@ -256,7 +317,7 @@ def make_payment(docname):
def make_credit_note(docname): def make_credit_note(docname):
create_sales_invoice( credit_note = create_sales_invoice(
company="_Test Company 2", company="_Test Company 2",
customer="_Test Customer 2", customer="_Test Customer 2",
currency="EUR", currency="EUR",
@ -269,3 +330,5 @@ def make_credit_note(docname):
is_return=1, is_return=1,
return_against=docname, return_against=docname,
) )
return credit_note