diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 652d45374b79..605d3c6d933a 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -1,7 +1,7 @@ import json import frappe -from frappe import _ +from frappe import _, qb from frappe.model.document import Document from frappe.query_builder.functions import Sum from frappe.utils import flt, nowdate @@ -563,11 +563,24 @@ 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) - if existing_payment_request_amount: + def validate_and_calculate_grand_total(grand_total, existing_payment_request_amount): grand_total -= existing_payment_request_amount - if not grand_total: frappe.throw(_("Payment Request is already created")) + return grand_total + + if existing_payment_request_amount: + if args.order_type == "Shopping Cart": + # If Payment Request is in an advanced stage, then create for remaining amount. + if get_existing_payment_request_amount( + ref_doc.doctype, ref_doc.name, ["Initiated", "Partially Paid", "Payment Ordered", "Paid"] + ): + grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount) + else: + # If PR's are processed, cancel all of them. + cancel_old_payment_requests(ref_doc.doctype, ref_doc.name) + else: + grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount) if draft_payment_request: frappe.db.set_value( @@ -683,21 +696,65 @@ def get_amount(ref_doc, payment_account=None): frappe.throw(_("Payment Entry is already created")) -def get_existing_payment_request_amount(ref_dt, ref_dn): +def get_irequest_status(payment_requests: None | list = None) -> list: + IR = frappe.qb.DocType("Integration Request") + res = [] + if payment_requests: + res = ( + frappe.qb.from_(IR) + .select(IR.name) + .where(IR.reference_doctype.eq("Payment Request")) + .where(IR.reference_docname.isin(payment_requests)) + .where(IR.status.isin(["Authorized", "Completed"])) + .run(as_dict=True) + ) + return res + + +def cancel_old_payment_requests(ref_dt, ref_dn): + PR = frappe.qb.DocType("Payment Request") + + if res := ( + frappe.qb.from_(PR) + .select(PR.name) + .where(PR.reference_doctype == ref_dt) + .where(PR.reference_name == ref_dn) + .where(PR.docstatus == 1) + .where(PR.status.isin(["Draft", "Requested"])) + .run(as_dict=True) + ): + if get_irequest_status([x.name for x in res]): + frappe.throw(_("Another Payment Request is already processed")) + else: + for x in res: + doc = frappe.get_doc("Payment Request", x.name) + doc.flags.ignore_permissions = True + doc.cancel() + + if ireqs := get_irequests_of_payment_request(doc.name): + for ireq in ireqs: + 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: """ Return the total amount of Payment Requests against a reference document. """ PR = frappe.qb.DocType("Payment Request") - response = ( + query = ( frappe.qb.from_(PR) .select(Sum(PR.grand_total)) .where(PR.reference_doctype == ref_dt) .where(PR.reference_name == ref_dn) .where(PR.docstatus == 1) - .run() ) + if statuses: + query = query.where(PR.status.isin(statuses)) + + response = query.run() + return response[0][0] if response[0] else 0 @@ -915,3 +972,17 @@ def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, ) for pr in open_payment_requests ] + + +def get_irequests_of_payment_request(doc: str | None = None) -> list: + res = [] + if doc: + res = frappe.db.get_all( + "Integration Request", + { + "reference_doctype": "Payment Request", + "reference_docname": doc, + "status": "Queued", + }, + ) + return res