fix(accounts): resolve subscription plans for any reference doctype in Payment Request (#58438)

fix(accounts): resolve subscription plans for any reference doctype and require read permission

get_subscription_details() was hardcoded to only resolve plans for
Sales Invoice, but is_a_subscription in make_payment_request() was set
for any reference doctype with a `subscription` field. Since Purchase
Invoice also has this field (supplier-side subscriptions), creating a
Payment Request against a subscription-linked Purchase Invoice set
is_a_subscription=1 with an empty subscription_plans table.

get_subscription_details() is also whitelisted with no permission
check, letting any logged-in user query which Subscription/plan/qty is
linked to an arbitrary Sales Invoice or Purchase Invoice.

Make plan resolution generic (guarded by Meta.has_field so doctypes
without a subscription field never hit a nonexistent column), derive
is_a_subscription from the resolved plans so the two can't disagree,
and add a frappe.has_permission read check before returning any data.

(cherry picked from commit 4cfa42921f)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request.py
#	erpnext/accounts/doctype/payment_request/test_payment_request.py
This commit is contained in:
Jatin3128
2026-08-26 16:16:40 +05:30
committed by Mergify
parent 2cd865fffc
commit 7b8d432032
2 changed files with 1197 additions and 1 deletions

View File

@@ -711,6 +711,10 @@ def make_payment_request(**args):
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
party_account_currency = get_account_currency(party_account)
<<<<<<< HEAD
=======
subscription_plans = get_subscription_details(ref_doc.doctype, ref_doc.name)
>>>>>>> 4cfa429 (fix(accounts): resolve subscription plans for any reference doctype in Payment Request (#58438))
pr.update(
{
"payment_gateway_account": gateway_account.get("name"),
@@ -742,12 +746,30 @@ def make_payment_request(**args):
or gateway_account.get("payment_channel", "Email") != "Email"
),
"phone_number": args.get("phone_number") if args.get("phone_number") else None,
<<<<<<< HEAD
=======
"is_a_subscription": 1 if subscription_plans else 0,
>>>>>>> 4cfa429 (fix(accounts): resolve subscription plans for any reference doctype in Payment Request (#58438))
}
)
if selected_payment_schedules:
apply_payment_references(pr, payment_reference)
<<<<<<< HEAD
=======
if subscription_plans:
pr.set(
"subscription_plans",
[
{
"plan": row.plan,
"qty": row.qty,
}
for row in subscription_plans
],
)
>>>>>>> 4cfa429 (fix(accounts): resolve subscription plans for any reference doctype in Payment Request (#58438))
# Dimensions
pr.update(
{
@@ -1061,6 +1083,7 @@ def get_dummy_message(doc):
@frappe.whitelist()
<<<<<<< HEAD
def get_subscription_details(reference_doctype, reference_name):
if reference_doctype == "Sales Invoice":
subscriptions = frappe.db.sql(
@@ -1074,6 +1097,27 @@ def get_subscription_details(reference_doctype, reference_name):
for plan in plans:
subscription_plans.append(plan)
return subscription_plans
=======
def get_subscription_details(reference_doctype: str, reference_name: str) -> list[dict]:
frappe.has_permission(reference_doctype, "read", reference_name, throw=True)
if not frappe.get_meta(reference_doctype).has_field("subscription"):
return []
subscription = frappe.db.get_value(reference_doctype, reference_name, "subscription")
if not subscription:
return []
return frappe.get_all(
"Subscription Plan Detail",
filters={"parent": subscription, "parenttype": "Subscription", "parentfield": "plans"},
fields=[
"plan",
"qty",
],
)
>>>>>>> 4cfa429 (fix(accounts): resolve subscription plans for any reference doctype in Payment Request (#58438))
@frappe.whitelist()