mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-13 20:05:09 +00:00
feat(payment request): create payment request as per payment schedules
(cherry picked from commit e476dff842)
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"manually_selected",
|
||||
"auto_selected",
|
||||
"column_break_lnjp",
|
||||
"payment_schedule",
|
||||
"section_break_fjhh",
|
||||
"description",
|
||||
"section_break_mjlv",
|
||||
@@ -58,25 +58,23 @@
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "manually_selected",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Manually Selected"
|
||||
"fieldname": "column_break_lnjp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "auto_selected",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Auto Selected"
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "payment_schedule",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Schedule",
|
||||
"options": "Payment Schedule",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-05 11:26:29.877050",
|
||||
"modified": "2026-01-19 02:21:36.455830",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reference",
|
||||
|
||||
@@ -15,13 +15,12 @@ class PaymentReference(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
amount: DF.Currency
|
||||
auto_selected: DF.Check
|
||||
description: DF.SmallText | None
|
||||
due_date: DF.Date | None
|
||||
manually_selected: DF.Check
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
payment_schedule: DF.Link | None
|
||||
payment_term: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"reference_name",
|
||||
"payment_reference_section",
|
||||
"payment_reference",
|
||||
"calculate_total_amount_by_selected_rows",
|
||||
"transaction_details",
|
||||
"grand_total",
|
||||
"currency",
|
||||
@@ -160,6 +159,7 @@
|
||||
"label": "Amount",
|
||||
"non_negative": 1,
|
||||
"options": "currency",
|
||||
"read_only_depends_on": "eval:doc.payment_reference.length>0",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -465,16 +465,12 @@
|
||||
"fieldname": "payment_reference_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "calculate_total_amount_by_selected_rows",
|
||||
"fieldtype": "Button",
|
||||
"label": "Calculate Total Amount by Selected Rows"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_reference",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Reference",
|
||||
"options": "Payment Reference"
|
||||
"options": "Payment Reference",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -482,7 +478,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-05 11:27:51.406257",
|
||||
"modified": "2026-01-13 12:53:00.963274",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
||||
@@ -111,15 +111,36 @@ class PaymentRequest(Document):
|
||||
if self.get("__islocal"):
|
||||
self.status = "Draft"
|
||||
self.validate_reference_document()
|
||||
self.validate_against_payment_reference()
|
||||
self.validate_payment_request_amount()
|
||||
# self.validate_currency()
|
||||
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):
|
||||
if not self.reference_doctype or not self.reference_name:
|
||||
frappe.throw(_("To create a Payment Request reference document is required"))
|
||||
|
||||
def validate_payment_request_amount(self):
|
||||
if self.payment_reference:
|
||||
return
|
||||
if self.grand_total == 0:
|
||||
frappe.throw(
|
||||
_("{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)
|
||||
if not args.get("company"):
|
||||
args.company = ref_doc.company
|
||||
|
||||
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:
|
||||
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
|
||||
ref_doc.db_update()
|
||||
grand_total = grand_total - loyalty_amount
|
||||
|
||||
# fetches existing payment request `grand_total` amount
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
|
||||
|
||||
@@ -586,21 +660,20 @@ def make_payment_request(**args):
|
||||
else:
|
||||
# If PR's are processed, cancel all of them.
|
||||
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)
|
||||
|
||||
draft_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
set_payment_references(pr, ref_doc)
|
||||
if selected_payment_schedules:
|
||||
apply_payment_references(pr, payment_reference)
|
||||
pr.save()
|
||||
|
||||
else:
|
||||
bank_account = (
|
||||
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_currency = get_account_currency(party_account)
|
||||
|
||||
set_payment_references(pr, ref_doc)
|
||||
|
||||
pr.update(
|
||||
{
|
||||
"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(
|
||||
{
|
||||
"cost_center": ref_doc.get("cost_center"),
|
||||
@@ -686,6 +760,51 @@ def make_payment_request(**args):
|
||||
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):
|
||||
"""get amount based on doctype"""
|
||||
grand_total = 0
|
||||
@@ -1032,36 +1151,20 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
|
||||
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:
|
||||
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 = []
|
||||
|
||||
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))
|
||||
return [r for r in ref_doc.payment_schedule if r.name not in existing_ids]
|
||||
|
||||
|
||||
def get_existing_payment_references(reference_name):
|
||||
@@ -1075,14 +1178,15 @@ def get_existing_payment_references(reference_name):
|
||||
.select(
|
||||
PRF.payment_term,
|
||||
PRF.due_date,
|
||||
PRF.amount,
|
||||
PRF.manually_selected,
|
||||
PRF.auto_selected,
|
||||
PRF.amount.as_("payment_amount"),
|
||||
PRF.payment_schedule,
|
||||
PRF.parent,
|
||||
)
|
||||
.where(PR.reference_name == reference_name)
|
||||
.where(PR.docstatus == 1)
|
||||
.where(PR.status.isin(["Initiated", "Partially Paid", "Payment Ordered", "Paid"]))
|
||||
.where(PR.docstatus < 2)
|
||||
.where(
|
||||
PR.status.isin(["Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid"])
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import re
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
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.test_payment_entry import create_payment_terms_template
|
||||
@@ -852,64 +854,122 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
|
||||
self.assertEqual(pr.grand_total, pi.outstanding_amount)
|
||||
|
||||
def test_payment_schedule_row_selection(self):
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=86)
|
||||
|
||||
def test_payment_request_grand_total_from_selected_schedules(self):
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 33})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 33})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 20})
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 30})
|
||||
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": 40})
|
||||
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
pr1 = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
)
|
||||
pr1.payment_reference[0].manually_selected = 1
|
||||
pr1.payment_reference[1].auto_selected = 0
|
||||
pr1.payment_reference[2].manually_selected = 1
|
||||
pr1.grand_total = 53
|
||||
pr1.submit()
|
||||
|
||||
pr2 = make_payment_request(
|
||||
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], po.payment_schedule[2]]
|
||||
]
|
||||
)
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
self.assertEqual(len(pr2.payment_reference), 1)
|
||||
self.assertEqual(pr2.payment_reference[0].amount, 33)
|
||||
pr.submit()
|
||||
|
||||
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
|
||||
|
||||
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.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 40})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 10})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 30})
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 50})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 50})
|
||||
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.submit()
|
||||
|
||||
pr1 = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
)
|
||||
# create PE first
|
||||
pr = make_payment_request(dt="Purchase Order", dn=po.name, mute_email=1, submit_doc=1, return_doc=1)
|
||||
pr.create_payment_entry()
|
||||
|
||||
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):
|
||||
make_payment_request(
|
||||
@@ -918,4 +978,5 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -138,7 +138,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -428,7 +428,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -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() {
|
||||
if (
|
||||
this.frm.doc.__islocal &&
|
||||
|
||||
@@ -1150,7 +1150,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
if (flt(doc.per_billed) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
() => this.make_payment_request(),
|
||||
() => this.make_payment_request_with_schedule(),
|
||||
__("Create")
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user