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:
parent
2f24546b21
commit
42f4f80e0c
@ -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]
|
||||||
|
|
||||||
|
@ -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)})
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user