Merge pull request #48440 from frappe/mergify/bp/version-15-hotfix/pr-48324

fix: update payment request outstanding on unreconciliation (backport #48324)
This commit is contained in:
ruthra kumar
2025-07-08 10:33:36 +05:30
committed by GitHub
3 changed files with 116 additions and 67 deletions

View File

@@ -829,8 +829,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
if not references: if not references:
return return
precision = references[0].precision("allocated_amount") precision = frappe.get_precision("Payment Entry Reference", "allocated_amount")
referenced_payment_requests = frappe.get_all( referenced_payment_requests = frappe.get_all(
"Payment Request", "Payment Request",
filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]}, filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]},

View File

@@ -630,29 +630,58 @@ class TestPaymentRequest(FrappeTestCase):
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1) pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
self.assertEqual(pr.grand_total, si.outstanding_amount) self.assertEqual(pr.grand_total, si.outstanding_amount)
def test_partial_paid_invoice_with_submitted_payment_entry(self):
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
pi.save()
pi.submit()
def test_partial_paid_invoice_with_submitted_payment_entry(self): pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000) pe.reference_no = "PURINV0001"
pi.save() pe.reference_date = frappe.utils.nowdate()
pi.submit() pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pe.cancel()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "PURINV0001" pe.reference_no = "PURINV0002"
pe.reference_date = frappe.utils.nowdate() pe.reference_date = frappe.utils.nowdate()
pe.paid_amount = 2500 pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500 pe.references[0].allocated_amount = 2500
pe.save() pe.save()
pe.submit() pe.submit()
pe.cancel()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pi.load_from_db()
pe.reference_no = "PURINV0002" pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
pe.reference_date = frappe.utils.nowdate() self.assertEqual(pr.grand_total, pi.outstanding_amount)
pe.paid_amount = 2500
pe.references[0].allocated_amount = 2500
pe.save()
pe.submit()
pi.load_from_db() def test_payment_request_on_unreconcile(self):
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1) pi = make_purchase_invoice(currency="INR", qty=1, rate=500)
self.assertEqual(pr.grand_total, pi.outstanding_amount) pi.submit()
pr = make_payment_request(
dt=pi.doctype,
dn=pi.name,
mute_email=1,
submit_doc=True,
return_doc=True,
)
self.assertEqual(pr.grand_total, pi.outstanding_amount)
pe = pr.create_payment_entry()
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": pe.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
unreconcile.submit()
pi.load_from_db()
pr.load_from_db()
self.assertEqual(pr.grand_total, pi.outstanding_amount)

View File

@@ -997,58 +997,79 @@ def remove_ref_doc_link_from_pe(
per = qb.DocType("Payment Entry Reference") per = qb.DocType("Payment Entry Reference")
pay = qb.DocType("Payment Entry") pay = qb.DocType("Payment Entry")
linked_pe = ( query = (
qb.from_(per) qb.from_(per)
.select(per.parent) .select("*")
.where((per.reference_doctype == ref_type) & (per.reference_name == ref_no) & (per.docstatus.lt(2))) .where(
.run(as_list=1) (per.reference_doctype == ref_type)
) & (per.reference_name == ref_no)
linked_pe = convert_to_list(linked_pe) & (per.docstatus.lt(2))
# remove reference only from specified payment & (per.parenttype == "Payment Entry")
linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe
if linked_pe:
update_query = (
qb.update(per)
.set(per.allocated_amount, 0)
.set(per.modified, now())
.set(per.modified_by, frappe.session.user)
.where(per.docstatus.lt(2) & (per.reference_doctype == ref_type) & (per.reference_name == ref_no))
) )
)
if payment_name: # update reference only from specified payment
update_query = update_query.where(per.parent == payment_name) if payment_name:
query = query.where(per.parent == payment_name)
update_query.run() reference_rows = query.run(as_dict=True)
for pe in linked_pe: if not reference_rows:
try: return
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_amounts()
# Call cancel on only removed reference linked_pe = set()
references = [ row_names = set()
x
for x in pe_doc.references
if x.reference_doctype == ref_type and x.reference_name == ref_no
]
[pe_doc.make_advance_gl_entries(x, cancel=1) for x in references]
pe_doc.clear_unallocated_reference_document_rows() for row in reference_rows:
pe_doc.validate_payment_type_with_outstanding() linked_pe.add(row.parent)
except Exception: row_names.add(row.name)
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
msg += "<br>"
msg += _("Please cancel payment entry manually first")
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
qb.update(pay).set(pay.total_allocated_amount, pe_doc.total_allocated_amount).set( from erpnext.accounts.doctype.payment_request.payment_request import (
pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount update_payment_requests_as_per_pe_references,
).set(pay.unallocated_amount, pe_doc.unallocated_amount).set(pay.modified, now()).set( )
pay.modified_by, frappe.session.user
).where(pay.name == pe).run()
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe))) # Update payment request amount
update_payment_requests_as_per_pe_references(reference_rows, cancel=True)
# Update allocated amounts and modified fields in one go
(
qb.update(per)
.set(per.allocated_amount, 0)
.set(per.modified, now())
.set(per.modified_by, frappe.session.user)
.where(per.name.isin(row_names))
.where(per.parenttype == "Payment Entry")
.run()
)
for pe in linked_pe:
try:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_amounts()
# Call cancel on only removed reference
references = [x for x in pe_doc.references if x.name in row_names]
[pe_doc.make_advance_gl_entries(x, cancel=1) for x in references]
pe_doc.clear_unallocated_reference_document_rows()
pe_doc.validate_payment_type_with_outstanding()
except Exception:
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
msg += "<br>"
msg += _("Please cancel payment entry manually first")
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
(
qb.update(pay)
.set(pay.total_allocated_amount, pe_doc.total_allocated_amount)
.set(pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount)
.set(pay.unallocated_amount, pe_doc.unallocated_amount)
.set(pay.modified, now())
.set(pay.modified_by, frappe.session.user)
.where(pay.name == pe)
.run()
)
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
@frappe.whitelist() @frappe.whitelist()