feat(payment request): create payment request as per payment schedules

(cherry picked from commit e476dff842)
This commit is contained in:
Jatin3128
2026-01-16 05:39:43 +05:30
committed by Mergify
parent 298ea33922
commit 751a081253
10 changed files with 363 additions and 106 deletions

View File

@@ -7,8 +7,8 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"payment_term", "payment_term",
"manually_selected", "column_break_lnjp",
"auto_selected", "payment_schedule",
"section_break_fjhh", "section_break_fjhh",
"description", "description",
"section_break_mjlv", "section_break_mjlv",
@@ -58,25 +58,23 @@
"precision": "2" "precision": "2"
}, },
{ {
"default": "0", "fieldname": "column_break_lnjp",
"fieldname": "manually_selected", "fieldtype": "Column Break"
"fieldtype": "Check",
"hidden": 1,
"label": "Manually Selected"
}, },
{ {
"default": "1", "allow_on_submit": 1,
"fieldname": "auto_selected", "fieldname": "payment_schedule",
"fieldtype": "Check", "fieldtype": "Link",
"hidden": 1, "label": "Payment Schedule",
"label": "Auto Selected" "options": "Payment Schedule",
"read_only": 1
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2025-12-05 11:26:29.877050", "modified": "2026-01-19 02:21:36.455830",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reference", "name": "Payment Reference",

View File

@@ -15,13 +15,12 @@ class PaymentReference(Document):
from frappe.types import DF from frappe.types import DF
amount: DF.Currency amount: DF.Currency
auto_selected: DF.Check
description: DF.SmallText | None description: DF.SmallText | None
due_date: DF.Date | None due_date: DF.Date | None
manually_selected: DF.Check
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data
parenttype: DF.Data parenttype: DF.Data
payment_schedule: DF.Link | None
payment_term: DF.Link | None payment_term: DF.Link | None
# end: auto-generated types # end: auto-generated types

View File

@@ -21,7 +21,6 @@
"reference_name", "reference_name",
"payment_reference_section", "payment_reference_section",
"payment_reference", "payment_reference",
"calculate_total_amount_by_selected_rows",
"transaction_details", "transaction_details",
"grand_total", "grand_total",
"currency", "currency",
@@ -160,6 +159,7 @@
"label": "Amount", "label": "Amount",
"non_negative": 1, "non_negative": 1,
"options": "currency", "options": "currency",
"read_only_depends_on": "eval:doc.payment_reference.length>0",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -465,16 +465,12 @@
"fieldname": "payment_reference_section", "fieldname": "payment_reference_section",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{
"fieldname": "calculate_total_amount_by_selected_rows",
"fieldtype": "Button",
"label": "Calculate Total Amount by Selected Rows"
},
{ {
"fieldname": "payment_reference", "fieldname": "payment_reference",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Payment Reference", "label": "Payment Reference",
"options": "Payment Reference" "options": "Payment Reference",
"read_only": 1
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
@@ -482,7 +478,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-12-05 11:27:51.406257", "modified": "2026-01-13 12:53:00.963274",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Request", "name": "Payment Request",

View File

@@ -111,15 +111,36 @@ class PaymentRequest(Document):
if self.get("__islocal"): if self.get("__islocal"):
self.status = "Draft" self.status = "Draft"
self.validate_reference_document() self.validate_reference_document()
self.validate_against_payment_reference()
self.validate_payment_request_amount() self.validate_payment_request_amount()
# self.validate_currency() # self.validate_currency()
self.validate_subscription_details() self.validate_subscription_details()
def validate_against_payment_reference(self):
if not self.payment_reference:
return
expected = sum(flt(r.amount) for r in self.payment_reference)
if flt(expected, self.precision("grand_total")) != flt(self.grand_total):
frappe.throw(_("Grand Total must match sum of Payment References"))
seen = set()
for r in self.payment_reference:
if not r.payment_schedule:
continue # legacy mode → skip
if r.payment_schedule in seen:
frappe.throw(_("Duplicate Payment Schedule selected"))
seen.add(r.payment_schedule)
def validate_reference_document(self): def validate_reference_document(self):
if not self.reference_doctype or not self.reference_name: if not self.reference_doctype or not self.reference_name:
frappe.throw(_("To create a Payment Request reference document is required")) frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self): def validate_payment_request_amount(self):
if self.payment_reference:
return
if self.grand_total == 0: if self.grand_total == 0:
frappe.throw( frappe.throw(
_("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")), _("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
@@ -554,9 +575,63 @@ def make_payment_request(**args):
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn) ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"): if not args.get("company"):
args.company = ref_doc.company args.company = ref_doc.company
gateway_account = get_gateway_details(args) or frappe._dict() gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account")) # Schedule-based PRs are allowed only if no Payment Entry exists for this document.
# Any existing Payment Entry forces legacy (amount-based) flow.
selected_payment_schedules = json.loads(args.get("schedules")) if args.get("schedules") else []
# Backend guard:
# If any Payment Entry exists, schedule-based PRs are not allowed.
if selected_payment_schedules and get_existing_payment_entry(ref_doc.name):
frappe.throw(
_(
"Payment Schedule based Payment Requests cannot be created because a Payment Entry already exists for this document."
)
)
has_payment_entry = bool(get_existing_payment_entry(ref_doc.name))
payment_reference = []
if selected_payment_schedules:
existing_payment_references = get_existing_payment_references(ref_doc.name)
if existing_payment_references:
existing_ids = {r["payment_schedule"] for r in existing_payment_references}
selected_ids = {r["name"] for r in selected_payment_schedules}
duplicate_ids = existing_ids & selected_ids
if duplicate_ids:
duplicate_schedules = []
for row in selected_payment_schedules:
if row["name"] in duplicate_ids:
existing_ref = next(
(r for r in existing_payment_references if r["payment_schedule"] == row["name"]),
{},
)
existing_pr = existing_ref.get("parent")
duplicate_schedules.append(
f"Payment Term: {row.get('payment_term')}, "
f"Due Date: {row.get('due_date')}, "
f"Amount: {row.get('payment_amount')} "
f"(already requested in PR {existing_pr})"
)
frappe.throw(
_("The following payment schedule(s) already exist:\n{0}").format(
"\n".join(duplicate_schedules)
)
)
payment_reference = set_payment_references(args.get("schedules"))
# Determine grand_total
if selected_payment_schedules and not has_payment_entry:
grand_total = sum(row.get("payment_amount") for row in selected_payment_schedules)
else:
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
if not grand_total: if not grand_total:
frappe.throw(_("Payment Entry is already created")) frappe.throw(_("Payment Entry is already created"))
@@ -566,7 +641,6 @@ def make_payment_request(**args):
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) # sets fields on ref_doc loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) # sets fields on ref_doc
ref_doc.db_update() ref_doc.db_update()
grand_total = grand_total - loyalty_amount grand_total = grand_total - loyalty_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) existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
@@ -586,21 +660,20 @@ def make_payment_request(**args):
else: else:
# If PR's are processed, cancel all of them. # If PR's are processed, cancel all of them.
cancel_old_payment_requests(ref_doc.doctype, ref_doc.name) cancel_old_payment_requests(ref_doc.doctype, ref_doc.name)
else: elif not selected_payment_schedules:
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)
draft_payment_request = frappe.db.get_value( draft_payment_request = frappe.db.get_value(
"Payment Request", "Payment Request",
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0}, {"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
) )
if draft_payment_request: if draft_payment_request:
frappe.db.set_value(
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
)
pr = frappe.get_doc("Payment Request", draft_payment_request) pr = frappe.get_doc("Payment Request", draft_payment_request)
set_payment_references(pr, ref_doc) if selected_payment_schedules:
apply_payment_references(pr, payment_reference)
pr.save()
else: else:
bank_account = ( bank_account = (
get_party_bank_account(args.get("party_type"), args.get("party")) get_party_bank_account(args.get("party_type"), args.get("party"))
@@ -621,8 +694,6 @@ def make_payment_request(**args):
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company) party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
party_account_currency = get_account_currency(party_account) party_account_currency = get_account_currency(party_account)
set_payment_references(pr, ref_doc)
pr.update( pr.update(
{ {
"payment_gateway_account": gateway_account.get("name"), "payment_gateway_account": gateway_account.get("name"),
@@ -657,7 +728,10 @@ def make_payment_request(**args):
} }
) )
# Update dimensions if selected_payment_schedules:
apply_payment_references(pr, payment_reference)
# Dimensions
pr.update( pr.update(
{ {
"cost_center": ref_doc.get("cost_center"), "cost_center": ref_doc.get("cost_center"),
@@ -686,6 +760,51 @@ def make_payment_request(**args):
return pr.as_dict() return pr.as_dict()
def apply_payment_references(pr, payment_reference):
existing_refs = pr.get("payment_reference") or []
existing_ids = {r.get("payment_schedule") for r in existing_refs if r.get("payment_schedule")}
new_refs = [r for r in (payment_reference or []) if r.get("payment_schedule") not in existing_ids]
pr.set("payment_reference", existing_refs + new_refs)
pr.set("grand_total", sum(flt(r.get("amount")) for r in pr.get("payment_reference")))
def set_payment_references(payment_schedules):
payment_schedules = json.loads(payment_schedules) if payment_schedules else []
payment_reference = []
for row in payment_schedules:
payment_reference.append(
{
"payment_term": row.get("payment_term"),
"payment_schedule": row.get("name"),
"description": row.get("description"),
"due_date": row.get("due_date"),
"amount": row.get("payment_amount"),
}
)
return payment_reference
def get_existing_payment_entry(ref_docname):
pe = frappe.qb.DocType("Payment Entry")
per = frappe.qb.DocType("Payment Entry Reference")
existing_pe = (
frappe.qb.from_(pe)
.join(per)
.on(per.parent == pe.name)
.select(pe.name)
.where(pe.docstatus < 2)
.where(per.reference_name == ref_docname)
.limit(1)
.run()
)
return existing_pe
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 grand_total = 0
@@ -1032,36 +1151,20 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
return res return res
def set_payment_references(payment_request, ref_doc): @frappe.whitelist()
def get_available_payment_schedules(reference_doctype, reference_name):
ref_doc = frappe.get_doc(reference_doctype, reference_name)
if not hasattr(ref_doc, "payment_schedule") or not ref_doc.payment_schedule: if not hasattr(ref_doc, "payment_schedule") or not ref_doc.payment_schedule:
return return []
existing_refs = get_existing_payment_references(ref_doc.name) if get_existing_payment_entry(reference_name):
return []
existing_map = {make_key(r.payment_term, r.due_date, r.amount): r for r in existing_refs} existing_refs = get_existing_payment_references(reference_name)
existing_ids = {r["payment_schedule"] for r in existing_refs if r.get("payment_schedule")}
payment_request.reference = [] return [r for r in ref_doc.payment_schedule if r.name not in existing_ids]
for row in ref_doc.payment_schedule:
key = make_key(row.payment_term, row.due_date, row.payment_amount)
existing = existing_map.get(key)
if existing and (existing.manually_selected or existing.auto_selected):
continue
payment_request.append(
"payment_reference",
{
"payment_term": row.payment_term,
"description": row.description,
"due_date": row.due_date,
"amount": row.payment_amount,
},
)
def make_key(payment_term, due_date, amount):
return (payment_term, due_date, flt(amount))
def get_existing_payment_references(reference_name): def get_existing_payment_references(reference_name):
@@ -1075,14 +1178,15 @@ def get_existing_payment_references(reference_name):
.select( .select(
PRF.payment_term, PRF.payment_term,
PRF.due_date, PRF.due_date,
PRF.amount, PRF.amount.as_("payment_amount"),
PRF.manually_selected, PRF.payment_schedule,
PRF.auto_selected,
PRF.parent, PRF.parent,
) )
.where(PR.reference_name == reference_name) .where(PR.reference_name == reference_name)
.where(PR.docstatus == 1) .where(PR.docstatus < 2)
.where(PR.status.isin(["Initiated", "Partially Paid", "Payment Ordered", "Paid"])) .where(
PR.status.isin(["Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid"])
)
).run(as_dict=True) ).run(as_dict=True)
return result return result

View File

@@ -1,12 +1,14 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import json
import re import re
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
import frappe import frappe
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.utils import add_days, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry 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_entry.test_payment_entry import create_payment_terms_template
@@ -852,64 +854,122 @@ class TestPaymentRequest(IntegrationTestCase):
self.assertEqual(pr.grand_total, pi.outstanding_amount) self.assertEqual(pr.grand_total, pi.outstanding_amount)
def test_payment_schedule_row_selection(self): def test_payment_request_grand_total_from_selected_schedules(self):
from frappe.utils import add_days, nowdate po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=86)
po.payment_schedule = [] po.payment_schedule = []
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 33}) po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 30})
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 33}) po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 30})
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 20}) po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 40})
po.save() po.save()
po.submit() po.submit()
pr1 = make_payment_request( schedules = json.dumps(
dt="Purchase Order", [
dn=po.name, {
mute_email=1, "payment_term": row.payment_term,
submit_doc=False, "name": row.name,
return_doc=True, "due_date": row.due_date,
) "payment_amount": row.payment_amount,
pr1.payment_reference[0].manually_selected = 1 "description": row.description,
pr1.payment_reference[1].auto_selected = 0 }
pr1.payment_reference[2].manually_selected = 1 for row in [po.payment_schedule[0], po.payment_schedule[2]]
pr1.grand_total = 53 ]
pr1.submit() )
pr = make_payment_request(
pr2 = make_payment_request(
dt="Purchase Order", dt="Purchase Order",
dn=po.name, dn=po.name,
mute_email=1, mute_email=1,
submit_doc=False, submit_doc=False,
return_doc=True, return_doc=True,
schedules=schedules,
) )
self.assertEqual(len(pr2.payment_reference), 1) pr.submit()
self.assertEqual(pr2.payment_reference[0].amount, 33)
def test_auto_selected_rows_are_not_reused(self): self.assertEqual(pr.grand_total, 70)
self.assertEqual(len(pr.payment_reference), 2)
def test_draft_pr_reuse_merges_payment_references(self):
from frappe.utils import add_days, nowdate from frappe.utils import add_days, nowdate
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=80) po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
po.payment_schedule = [] po.payment_schedule = []
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 40}) po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 50})
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 10}) po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 50})
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 30}) po.save()
po.submit()
schedules = json.dumps(
[
{
"payment_term": row.payment_term,
"name": row.name,
"due_date": row.due_date,
"payment_amount": row.payment_amount,
"description": row.description,
}
for row in [po.payment_schedule[0]]
]
)
pr = make_payment_request(
dt="Purchase Order",
dn=po.name,
mute_email=1,
submit_doc=False,
return_doc=True,
schedules=schedules,
)
pr.save()
schedules = json.dumps(
[
{
"payment_term": row.payment_term,
"name": row.name,
"due_date": row.due_date,
"payment_amount": row.payment_amount,
"description": row.description,
}
for row in [po.payment_schedule[1]]
]
)
# call make_payment_request again → reuse draft
pr_reused = make_payment_request(
dt="Purchase Order",
dn=po.name,
mute_email=1,
submit_doc=False,
return_doc=True,
schedules=schedules,
)
self.assertEqual(pr.name, pr_reused.name)
self.assertEqual(pr_reused.grand_total, 100)
self.assertEqual(len(pr_reused.payment_reference), 2)
def test_schedule_pr_not_allowed_if_payment_entry_exists(self):
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
po.payment_schedule = []
row = po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 100})
po.save() po.save()
po.submit() po.submit()
pr1 = make_payment_request( # create PE first
dt="Purchase Order", pr = make_payment_request(dt="Purchase Order", dn=po.name, mute_email=1, submit_doc=1, return_doc=1)
dn=po.name, pr.create_payment_entry()
mute_email=1,
submit_doc=False,
return_doc=True,
)
pr1.submit() schedules = json.dumps(
[
{
"name": row.name,
"payment_term": row.payment_term,
"due_date": row.due_date,
"payment_amount": row.payment_amount,
"description": row.description,
}
]
)
with self.assertRaises(frappe.ValidationError): with self.assertRaises(frappe.ValidationError):
make_payment_request( make_payment_request(
@@ -918,4 +978,5 @@ class TestPaymentRequest(IntegrationTestCase):
mute_email=1, mute_email=1,
submit_doc=False, submit_doc=False,
return_doc=True, return_doc=True,
schedules=schedules,
) )

View File

@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.frm.add_custom_button( this.frm.add_custom_button(
__("Payment Request"), __("Payment Request"),
function () { function () {
me.make_payment_request(); me.make_payment_request_with_schedule();
}, },
__("Create") __("Create")
); );

View File

@@ -138,7 +138,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
this.frm.add_custom_button( this.frm.add_custom_button(
__("Payment Request"), __("Payment Request"),
function () { function () {
me.make_payment_request(); me.make_payment_request_with_schedule();
}, },
__("Create") __("Create")
); );

View File

@@ -428,7 +428,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
this.frm.add_custom_button( this.frm.add_custom_button(
__("Payment Request"), __("Payment Request"),
function () { function () {
me.make_payment_request(); me.make_payment_request_with_schedule();
}, },
__("Create") __("Create")
); );

View File

@@ -450,7 +450,106 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}, },
}); });
} }
make_payment_request_with_schedule = async function () {
let frm = this.frm;
const { message: schedules } = await frappe.call({
method: "erpnext.accounts.doctype.payment_request.payment_request.get_available_payment_schedules",
args: {
reference_doctype: frm.doctype,
reference_name: frm.doc.name,
},
});
if (!schedules.length) {
this.make_payment_request();
return;
}
if (!schedules || !schedules.length) {
frappe.msgprint(__("No pending payment schedules available."));
return;
}
const dialog = new frappe.ui.Dialog({
title: __("Select Payment Schedule"),
fields: [
{
fieldtype: "Table",
fieldname: "payment_schedules",
label: __("Payment Schedules"),
cannot_add_rows: true,
in_place_edit: false,
data: schedules,
fields: [
{
fieldtype: "Data",
fieldname: "name",
label: __("Schedule Name"),
read_only: 1,
},
{
fieldtype: "Data",
fieldname: "payment_term",
label: __("Payment Term"),
in_list_view: 1,
read_only: 1,
},
{
fieldtype: "Date",
fieldname: "due_date",
label: __("Due Date"),
in_list_view: 1,
read_only: 1,
},
{
fieldtype: "Currency",
fieldname: "payment_amount",
label: __("Amount"),
in_list_view: 1,
read_only: 1,
},
],
},
],
primary_action_label: __("Create Payment Request"),
primary_action: async () => {
const values = dialog.get_values();
const selected = values.payment_schedules.filter((r) => r.__checked);
if (!selected.length) {
frappe.msgprint(__("Please select at least one schedule."));
return;
}
console.log(selected);
dialog.hide();
let me = this;
const payment_request_type = ["Sales Order", "Sales Invoice"].includes(this.frm.doc.doctype)
? "Inward"
: "Outward";
const { message: pr_name } = await frappe.call({
method: "erpnext.accounts.doctype.payment_request.payment_request.make_payment_request",
args: {
dt: me.frm.doc.doctype,
dn: me.frm.doc.name,
recipient_id: me.frm.doc.contact_email,
payment_request_type: payment_request_type,
party_type: payment_request_type == "Outward" ? "Supplier" : "Customer",
party: payment_request_type == "Outward" ? me.frm.doc.supplier : me.frm.doc.customer,
party_name:
payment_request_type == "Outward"
? me.frm.doc.supplier_name
: me.frm.doc.customer_name,
reference_doctype: frm.doctype,
reference_name: frm.docname,
schedules: selected,
},
});
frappe.set_route("Form", "Payment Request", pr_name.name);
},
});
dialog.show();
};
onload_post_render() { onload_post_render() {
if ( if (
this.frm.doc.__islocal && this.frm.doc.__islocal &&

View File

@@ -1150,7 +1150,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
if (flt(doc.per_billed) < 100 + frappe.boot.sysdefaults.over_billing_allowance) { if (flt(doc.per_billed) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
this.frm.add_custom_button( this.frm.add_custom_button(
__("Payment Request"), __("Payment Request"),
() => this.make_payment_request(), () => this.make_payment_request_with_schedule(),
__("Create") __("Create")
); );