mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-15 04:45:09 +00:00
fix: correct payment request amount
(cherry picked from commit 913c60d77b)
# Conflicts:
# erpnext/accounts/doctype/payment_request/payment_request.py
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.functions import Abs, Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
|
|
||||||
@@ -12,7 +12,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||||
get_company_defaults,
|
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
@@ -120,16 +119,14 @@ class PaymentRequest(Document):
|
|||||||
title=_("Invalid Amount"),
|
title=_("Invalid Amount"),
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_payment_request_amount = flt(
|
|
||||||
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
|
if not hasattr(ref_doc, "order_type") or ref_doc.order_type != "Shopping Cart":
|
||||||
ref_amount = get_amount(ref_doc, self.payment_account)
|
ref_amount = get_amount(ref_doc, self.payment_account)
|
||||||
if not ref_amount:
|
if not ref_amount:
|
||||||
frappe.throw(_("Payment Entry is already created"))
|
frappe.throw(_("Payment Entry is already created"))
|
||||||
|
|
||||||
|
existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc))
|
||||||
|
|
||||||
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
|
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Total Payment Request amount cannot be greater than {0} amount").format(
|
_("Total Payment Request amount cannot be greater than {0} amount").format(
|
||||||
@@ -558,6 +555,7 @@ def make_payment_request(**args):
|
|||||||
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
||||||
grand_total = grand_total - loyalty_amount
|
grand_total = grand_total - loyalty_amount
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
bank_account = (
|
bank_account = (
|
||||||
get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
|
get_party_bank_account(args.get("party_type"), args.get("party")) if args.get("party_type") else ""
|
||||||
)
|
)
|
||||||
@@ -567,10 +565,10 @@ def make_payment_request(**args):
|
|||||||
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
|
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 913c60d77b (fix: correct payment request amount)
|
||||||
# fetches existing payment request `grand_total` amount
|
# fetches existing payment request `grand_total` amount
|
||||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
|
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
|
||||||
|
|
||||||
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):
|
def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount):
|
||||||
grand_total -= existing_payment_request_amount
|
grand_total -= existing_payment_request_amount
|
||||||
@@ -582,7 +580,7 @@ def make_payment_request(**args):
|
|||||||
if args.order_type == "Shopping Cart":
|
if args.order_type == "Shopping Cart":
|
||||||
# If Payment Request is in an advanced stage, then create for remaining amount.
|
# If Payment Request is in an advanced stage, then create for remaining amount.
|
||||||
if get_existing_payment_request_amount(
|
if get_existing_payment_request_amount(
|
||||||
ref_doc.doctype, ref_doc.name, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
|
ref_doc, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"]
|
||||||
):
|
):
|
||||||
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
||||||
else:
|
else:
|
||||||
@@ -591,14 +589,10 @@ def make_payment_request(**args):
|
|||||||
else:
|
else:
|
||||||
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
||||||
|
|
||||||
if existing_paid_amount:
|
draft_payment_request = frappe.db.get_value(
|
||||||
if ref_doc.party_account_currency == ref_doc.currency:
|
"Payment Request",
|
||||||
if ref_doc.conversion_rate:
|
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||||
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:
|
if draft_payment_request:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
@@ -606,6 +600,11 @@ def make_payment_request(**args):
|
|||||||
)
|
)
|
||||||
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||||
else:
|
else:
|
||||||
|
bank_account = (
|
||||||
|
get_party_bank_account(args.get("party_type"), args.get("party"))
|
||||||
|
if args.get("party_type")
|
||||||
|
else ""
|
||||||
|
)
|
||||||
pr = frappe.new_doc("Payment Request")
|
pr = frappe.new_doc("Payment Request")
|
||||||
|
|
||||||
if not args.get("payment_request_type"):
|
if not args.get("payment_request_type"):
|
||||||
@@ -679,22 +678,35 @@ def make_payment_request(**args):
|
|||||||
|
|
||||||
def get_amount(ref_doc, payment_account=None):
|
def get_amount(ref_doc, payment_account=None):
|
||||||
"""get amount based on doctype"""
|
"""get amount based on doctype"""
|
||||||
|
grand_total = 0
|
||||||
|
|
||||||
dt = ref_doc.doctype
|
dt = ref_doc.doctype
|
||||||
if dt in ["Sales Order", "Purchase Order"]:
|
if dt in ["Sales Order", "Purchase Order"]:
|
||||||
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
|
grand_total = (flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)) - ref_doc.advance_paid
|
||||||
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
if not ref_doc.get("is_pos"):
|
if (
|
||||||
|
dt == "Sales Invoice"
|
||||||
|
and ref_doc.is_pos
|
||||||
|
and ref_doc.payments
|
||||||
|
and any(
|
||||||
|
[
|
||||||
|
payment.type == "Phone" and payment.account == payment_account
|
||||||
|
for payment in ref_doc.payments
|
||||||
|
]
|
||||||
|
)
|
||||||
|
):
|
||||||
|
grand_total = sum(
|
||||||
|
[
|
||||||
|
payment.amount
|
||||||
|
for payment in ref_doc.payments
|
||||||
|
if payment.type == "Phone" and payment.account == payment_account
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
if ref_doc.party_account_currency == ref_doc.currency:
|
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:
|
else:
|
||||||
grand_total = flt(
|
grand_total = flt(flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate)
|
||||||
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:
|
|
||||||
grand_total = pay.amount
|
|
||||||
break
|
|
||||||
elif dt == "POS Invoice":
|
elif dt == "POS Invoice":
|
||||||
for pay in ref_doc.payments:
|
for pay in ref_doc.payments:
|
||||||
if pay.type == "Phone" and pay.account == payment_account:
|
if pay.type == "Phone" and pay.account == payment_account:
|
||||||
@@ -703,10 +715,7 @@ def get_amount(ref_doc, payment_account=None):
|
|||||||
elif dt == "Fees":
|
elif dt == "Fees":
|
||||||
grand_total = ref_doc.outstanding_amount
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
if grand_total > 0:
|
return flt(grand_total, get_currency_precision()) if grand_total > 0 else 0
|
||||||
return flt(grand_total, get_currency_precision())
|
|
||||||
else:
|
|
||||||
frappe.throw(_("Payment Entry is already created"))
|
|
||||||
|
|
||||||
|
|
||||||
def get_irequest_status(payment_requests: None | list = None) -> list:
|
def get_irequest_status(payment_requests: None | list = None) -> list:
|
||||||
@@ -749,7 +758,7 @@ def cancel_old_payment_requests(ref_dt, ref_dn):
|
|||||||
frappe.db.set_value("Integration Request", ireq.name, "status", "Cancelled")
|
frappe.db.set_value("Integration Request", ireq.name, "status", "Cancelled")
|
||||||
|
|
||||||
|
|
||||||
def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None = None) -> list:
|
def get_existing_payment_request_amount(ref_doc, statuses: list | None = None) -> list:
|
||||||
"""
|
"""
|
||||||
Return the total amount of Payment Requests against a reference document.
|
Return the total amount of Payment Requests against a reference document.
|
||||||
"""
|
"""
|
||||||
@@ -757,9 +766,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
|
|||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(PR)
|
frappe.qb.from_(PR)
|
||||||
.select(Sum(PR.grand_total))
|
.select(Sum(PR.outstanding_amount))
|
||||||
.where(PR.reference_doctype == ref_dt)
|
.where(PR.reference_doctype == ref_doc.doctype)
|
||||||
.where(PR.reference_name == ref_dn)
|
.where(PR.reference_name == ref_doc.name)
|
||||||
.where(PR.docstatus == 1)
|
.where(PR.docstatus == 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -768,43 +777,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, statuses: list | None =
|
|||||||
|
|
||||||
response = query.run()
|
response = query.run()
|
||||||
|
|
||||||
return response[0][0] if response[0] else 0
|
os_amount_in_transaction_currency = flt(response[0][0] if response[0] else 0)
|
||||||
|
|
||||||
|
if ref_doc.currency != ref_doc.party_account_currency:
|
||||||
|
os_amount_in_transaction_currency = flt(os_amount_in_transaction_currency / ref_doc.conversion_rate)
|
||||||
|
|
||||||
def get_existing_paid_amount(doctype, name):
|
return os_amount_in_transaction_currency
|
||||||
PLE = frappe.qb.DocType("Payment Ledger Entry")
|
|
||||||
PER = frappe.qb.DocType("Payment Entry Reference")
|
|
||||||
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(PLE)
|
|
||||||
.left_join(PER)
|
|
||||||
.on(
|
|
||||||
(PLE.against_voucher_type == PER.reference_doctype)
|
|
||||||
& (PLE.against_voucher_no == PER.reference_name)
|
|
||||||
& (PLE.voucher_type == PER.parenttype)
|
|
||||||
& (PLE.voucher_no == PER.parent)
|
|
||||||
)
|
|
||||||
.select(
|
|
||||||
Abs(Sum(PLE.amount)).as_("total_amount"),
|
|
||||||
Abs(Sum(frappe.qb.terms.Case().when(PER.payment_request.isnotnull(), PLE.amount).else_(0))).as_(
|
|
||||||
"request_paid_amount"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
(PLE.voucher_type.isin([doctype, "Journal Entry", "Payment Entry"]))
|
|
||||||
& (PLE.against_voucher_type == doctype)
|
|
||||||
& (PLE.against_voucher_no == name)
|
|
||||||
& (PLE.delinked == 0)
|
|
||||||
& (PLE.docstatus == 1)
|
|
||||||
& (PLE.amount < 0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = query.run()
|
|
||||||
ledger_amount = flt(result[0][0]) if result else 0
|
|
||||||
request_paid_amount = flt(result[0][1]) if result else 0
|
|
||||||
|
|
||||||
return ledger_amount - request_paid_amount
|
|
||||||
|
|
||||||
|
|
||||||
def get_gateway_details(args): # nosemgrep
|
def get_gateway_details(args): # nosemgrep
|
||||||
|
|||||||
@@ -313,6 +313,16 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
self.assertEqual(pr.outstanding_amount, 800)
|
self.assertEqual(pr.outstanding_amount, 800)
|
||||||
self.assertEqual(pr.grand_total, 1000)
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.exceptions.ValidationError,
|
||||||
|
re.compile(r"Payment Request is already created"),
|
||||||
|
make_payment_request,
|
||||||
|
dt="Sales Order",
|
||||||
|
dn=so.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
# complete payment
|
# complete payment
|
||||||
pe = pr.create_payment_entry()
|
pe = pr.create_payment_entry()
|
||||||
|
|
||||||
@@ -331,7 +341,7 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
# creating a more payment Request must not allowed
|
# creating a more payment Request must not allowed
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
frappe.exceptions.ValidationError,
|
frappe.exceptions.ValidationError,
|
||||||
re.compile(r"Payment Request is already created"),
|
re.compile(r"Payment Entry is already created"),
|
||||||
make_payment_request,
|
make_payment_request,
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
@@ -361,6 +371,17 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
self.assertEqual(pr.party_account_currency, "INR")
|
self.assertEqual(pr.party_account_currency, "INR")
|
||||||
self.assertEqual(pr.status, "Initiated")
|
self.assertEqual(pr.status, "Initiated")
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.exceptions.ValidationError,
|
||||||
|
re.compile(r"Payment Request is already created"),
|
||||||
|
make_payment_request,
|
||||||
|
dt="Purchase Invoice",
|
||||||
|
dn=pi.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
# to make partial payment
|
# to make partial payment
|
||||||
pe = pr.create_payment_entry(submit=False)
|
pe = pr.create_payment_entry(submit=False)
|
||||||
pe.paid_amount = 2000
|
pe.paid_amount = 2000
|
||||||
@@ -389,7 +410,7 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
# creating a more payment Request must not allowed
|
# creating a more payment Request must not allowed
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
frappe.exceptions.ValidationError,
|
frappe.exceptions.ValidationError,
|
||||||
re.compile(r"Payment Request is already created"),
|
re.compile(r"Payment Entry is already created"),
|
||||||
make_payment_request,
|
make_payment_request,
|
||||||
dt="Purchase Invoice",
|
dt="Purchase Invoice",
|
||||||
dn=pi.name,
|
dn=pi.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user