From 38e7d0a41e2a55ef98f2c6147c34b64708929854 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Tue, 26 Nov 2024 13:58:52 +0530 Subject: [PATCH 1/3] fix: set outstanding amount while creating payment request for invoices --- erpnext/accounts/doctype/payment_request/payment_request.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 9fe7bbe5e34..ec8e768a147 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -672,11 +672,9 @@ def get_amount(ref_doc, payment_account=None): elif dt in ["Sales Invoice", "Purchase Invoice"]: if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: - grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total) + grand_total = flt(ref_doc.outstanding_amount) else: - grand_total = flt( - flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate - ) + grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate) elif dt == "Sales Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: From bbe3bc95d08950bebc9ede64c11c9660a564a8a6 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Tue, 26 Nov 2024 13:59:45 +0530 Subject: [PATCH 2/3] test: add unit test to validate outstanding amount in payment request --- .../payment_request/test_payment_request.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index f64a8e919c3..45fa7f139e3 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -8,6 +8,7 @@ from unittest.mock import patch import frappe from frappe.tests import IntegrationTestCase, UnitTestCase +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice @@ -689,3 +690,21 @@ class TestPaymentRequest(IntegrationTestCase): so.load_from_db() self.assertEqual(so.advance_payment_status, "Requested") + + def test_partial_paid_invoice_with_payment_request(self): + si = create_sales_invoice(currency="INR", qty=1, rate=5000) + si.save() + si.submit() + + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "PAYEE0002" + pe.reference_date = frappe.utils.nowdate() + pe.paid_amount = 2500 + pe.references[0].allocated_amount = 2500 + pe.save() + pe.submit() + + si.load_from_db() + pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1) + + self.assertEqual(pr.grand_total, si.outstanding_amount) From 82907672d938b2b447f0f7382e8fc4e4c343e708 Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Wed, 27 Nov 2024 17:41:16 +0530 Subject: [PATCH 3/3] fix: reduce paid amount from grand total --- .../payment_request/payment_request.py | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index ec8e768a147..6f0972eb3c6 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -3,7 +3,7 @@ import json import frappe from frappe import _, qb from frappe.model.document import Document -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Abs, Sum from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue @@ -563,6 +563,8 @@ def make_payment_request(**args): # fetches existing payment request `grand_total` amount existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name) + existing_paid_amount = get_existing_paid_amount(ref_doc.doctype, ref_doc.name) + def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount): grand_total -= existing_payment_request_amount if not grand_total: @@ -582,6 +584,15 @@ def make_payment_request(**args): else: grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount) + if existing_paid_amount: + if ref_doc.party_account_currency == ref_doc.currency: + if ref_doc.conversion_rate: + grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate) + else: + grand_total -= flt(existing_paid_amount) + else: + grand_total -= flt(existing_paid_amount / ref_doc.conversion_rate) + if draft_payment_request: frappe.db.set_value( "Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False @@ -672,9 +683,11 @@ def get_amount(ref_doc, payment_account=None): elif dt in ["Sales Invoice", "Purchase Invoice"]: if not ref_doc.get("is_pos"): if ref_doc.party_account_currency == ref_doc.currency: - grand_total = flt(ref_doc.outstanding_amount) + grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total) else: - grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate) + grand_total = flt( + flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate + ) elif dt == "Sales Invoice": for pay in ref_doc.payments: if pay.type == "Phone" and pay.account == payment_account: @@ -756,6 +769,27 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = return response[0][0] if response[0] else 0 +def get_existing_paid_amount(doctype, name): + PL = frappe.qb.DocType("Payment Ledger Entry") + PER = frappe.qb.DocType("Payment Entry Reference") + + query = ( + frappe.qb.from_(PL) + .left_join(PER) + .on( + (PER.reference_doctype == PL.against_voucher_type) & (PER.reference_name == PL.against_voucher_no) + ) + .select(Abs(Sum(PL.amount)).as_("total_paid_amount")) + .where(PL.against_voucher_type.eq(doctype)) + .where(PL.against_voucher_no.eq(name)) + .where(PL.amount < 0) + .where(PER.payment_request.isnull()) + ) + response = query.run() + + return response[0][0] if response[0] else 0 + + def get_gateway_details(args): # nosemgrep """ Return gateway and payment account of default payment gateway