From 5fa408a1234f4aabb8c9443992ce07d925e336f1 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 11:25:01 +0530
Subject: [PATCH 01/60] fix: multiple issues in Payment Request
---
.../doctype/payment_entry/payment_entry.js | 36 +++-
.../doctype/payment_entry/payment_entry.py | 132 ++++++++++++-
.../payment_entry_reference.json | 11 +-
.../payment_entry_reference.py | 1 +
.../payment_request/payment_request.js | 4 +-
.../payment_request/payment_request.json | 13 +-
.../payment_request/payment_request.py | 174 ++++++++++++------
7 files changed, 302 insertions(+), 69 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 6738743793db..c6c3651cb857 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,6 +7,8 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
erpnext.accounts.taxes.setup_tax_validations("Payment Entry");
erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
+const FAULTY_VALUES = ["", null, undefined, 0];
+
frappe.ui.form.on("Payment Entry", {
onload: function (frm) {
frm.ignore_doctypes_on_cancel_all = [
@@ -165,6 +167,19 @@ frappe.ui.form.on("Payment Entry", {
filters: filters,
};
});
+
+ frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ const filters = {
+ docstatus: 1,
+ status: ["!=", "Paid"],
+ reference_doctype: row.reference_doctype,
+ reference_name: row.reference_name,
+ };
+ return {
+ filters: filters,
+ };
+ });
},
refresh: function (frm) {
@@ -995,6 +1010,8 @@ frappe.ui.form.on("Payment Entry", {
total_negative_outstanding - total_positive_outstanding
);
}
+
+ frm.events.set_matched_payment_requests(frm);
}
frm.events.allocate_party_amount_against_ref_docs(
@@ -1647,6 +1664,11 @@ frappe.ui.form.on("Payment Entry", {
return current_tax_amount;
},
+
+ set_matched_payment_requests: async function (frm) {
+ await frappe.after_ajax();
+ frm.call("set_matched_payment_requests");
+ },
});
frappe.ui.form.on("Payment Entry Reference", {
@@ -1689,8 +1711,20 @@ frappe.ui.form.on("Payment Entry Reference", {
}
},
- allocated_amount: function (frm) {
+ allocated_amount: function (frm, cdt, cdn) {
frm.events.set_total_allocated_amount(frm);
+
+ const row = locals[cdt][cdn];
+
+ // if payment_request already set then return
+ if (row.payment_request) return;
+
+ const references = [row.reference_name, row.reference_doctype, row.allocated_amount];
+
+ // if any of the reference fields are faulty, it returns
+ if (FAULTY_VALUES.some((el) => references.includes(el))) return;
+
+ frm.call("set_matched_payment_request", { row_idx: row.idx });
},
references_remove: function (frm) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 3ad1f72af6e3..694c5fe78e67 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -8,7 +8,7 @@
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
-from frappe.utils.data import comma_and, fmt_money
+from frappe.utils.data import comma_and, fmt_money, get_link_to_form
from pypika import Case
from pypika.functions import Coalesce, Sum
@@ -181,6 +181,9 @@ def validate(self):
self.set_status()
self.set_total_in_words()
+ def before_save(self):
+ self.check_payment_requests()
+
def on_submit(self):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
@@ -188,6 +191,7 @@ def on_submit(self):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
+ self.set_payment_req_outstanding_amount()
self.set_payment_req_status()
self.set_status()
@@ -263,9 +267,17 @@ def on_cancel(self):
self.update_advance_paid()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
+ self.set_payment_req_outstanding_amount(cancel=True)
self.set_payment_req_status()
self.set_status()
+ def set_payment_req_outstanding_amount(self, cancel=False):
+ from erpnext.accounts.doctype.payment_request.payment_request import (
+ update_payment_req_outstanding_amount,
+ )
+
+ update_payment_req_outstanding_amount(self, cancel=cancel)
+
def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
@@ -309,6 +321,8 @@ def validate_allocated_amount(self):
if self.payment_type == "Internal Transfer":
return
+ self.validate_allocated_amount_as_of_pr()
+
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
@@ -321,6 +335,21 @@ def validate_allocated_amount(self):
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
+ def validate_allocated_amount_as_of_pr(self):
+ from erpnext.accounts.doctype.payment_request.payment_request import (
+ get_outstanding_amount_of_payment_entry_references as get_outstanding_amounts,
+ )
+
+ outstanding_amounts = get_outstanding_amounts(self.references)
+
+ for ref in self.references:
+ if ref.payment_request and ref.allocated_amount > outstanding_amounts[ref.payment_request]:
+ frappe.throw(
+ _("Allocated Amount cannot be greater than Outstanding Amount of {0}").format(
+ get_link_to_form("Payment Request", ref.payment_request)
+ )
+ )
+
def term_based_allocation_enabled_for_reference(
self, reference_doctype: str, reference_name: str
) -> bool:
@@ -1701,6 +1730,103 @@ def get_current_tax_fraction(self, tax):
return current_tax_fraction
+ def check_payment_requests(self):
+ if not self.references:
+ return
+
+ not_set_count = sum(1 for row in self.references if not row.payment_request)
+
+ if not_set_count == 0:
+ return
+ elif not_set_count == 1:
+ msg = _("{0} {1} is not set in {2}").format(
+ not_set_count,
+ frappe.bold("Payment Request"),
+ frappe.bold("Payment References"),
+ )
+ else:
+ msg = _("{0} {1} are not set in {2}").format(
+ not_set_count,
+ frappe.bold("Payment Request"),
+ frappe.bold("Payment References"),
+ )
+
+ frappe.msgprint(msg=msg, alert=True, indicator="orange")
+
+ # todo: can be optimized
+ @frappe.whitelist()
+ def set_matched_payment_requests(self):
+ if not self.references:
+ return
+
+ matched_count = 0
+
+ for row in self.references:
+ if row.payment_request or (
+ not row.reference_doctype or not row.reference_name or not row.allocated_amount
+ ):
+ continue
+
+ row.payment_request = get_matched_payment_request(
+ row.reference_doctype, row.reference_name, row.allocated_amount
+ )
+
+ if row.payment_request:
+ matched_count += 1
+
+ if matched_count == 0:
+ return
+ elif matched_count == 1:
+ msg = _("{0} matched {1} is set").format(matched_count, frappe.bold("Payment Request"))
+ else:
+ msg = _("{0} matched {1} are set").format(matched_count, frappe.bold("Payment Request"))
+
+ frappe.msgprint(
+ msg=msg,
+ alert=True,
+ )
+
+ @frappe.whitelist()
+ def set_matched_payment_request(self, row_idx):
+ row = next((row for row in self.references if row.idx == row_idx), None)
+
+ if not row:
+ frappe.throw(_("Row #{0} not found").format(row_idx), title=_("Row Not Found"))
+
+ # if payment entry already set then do not set it again
+ if row.payment_request:
+ return
+
+ row.payment_request = get_matched_payment_request(
+ row.reference_doctype, row.reference_name, row.allocated_amount
+ )
+
+ if row.payment_request:
+ frappe.msgprint(
+ msg=_("Matched {0} is set").format(frappe.bold("Payment Request")),
+ alert=True,
+ )
+
+
+# todo: can be optimized
+def get_matched_payment_request(reference_doctype, reference_name, outstanding_amount):
+ payment_requests = frappe.get_all(
+ doctype="Payment Request",
+ filters={
+ "reference_doctype": reference_doctype,
+ "reference_name": reference_name,
+ "outstanding_amount": outstanding_amount,
+ "status": ["!=", "Paid"],
+ "docstatus": 1,
+ },
+ pluck="name",
+ )
+
+ if len(payment_requests) == 1:
+ return payment_requests[0]
+
+ return None
+
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
@@ -1732,6 +1858,7 @@ def _on_previous_row_error(row_range):
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
+# todo: modify its test
@frappe.whitelist()
def get_outstanding_reference_documents(args, validate=False):
if isinstance(args, str):
@@ -1889,6 +2016,9 @@ def get_outstanding_reference_documents(args, validate=False):
)
)
+ frappe.log("Data")
+ frappe.log(data)
+
return data
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 352ece24f06b..7fce86c99d9a 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -18,7 +18,8 @@
"allocated_amount",
"exchange_rate",
"exchange_gain_loss",
- "account"
+ "account",
+ "payment_request"
],
"fields": [
{
@@ -120,12 +121,18 @@
"fieldname": "payment_type",
"fieldtype": "Data",
"label": "Payment Type"
+ },
+ {
+ "fieldname": "payment_request",
+ "fieldtype": "Link",
+ "label": "Payment Request",
+ "options": "Payment Request"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-04-05 09:44:08.310593",
+ "modified": "2024-07-20 17:57:32.866780",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index 4a027b4ee32b..68d819d08408 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -25,6 +25,7 @@ class PaymentEntryReference(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
+ payment_request: DF.Link | None
payment_term: DF.Link | None
payment_type: DF.Data | None
reference_doctype: DF.Link
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index f12facfbf5a6..44313e5c0d2c 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -52,8 +52,8 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
}
if (
- (!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") &&
- frm.doc.status == "Initiated"
+ frm.doc.payment_request_type == "Outward" &&
+ ["Initiated", "Partially Paid"].includes(frm.doc.status)
) {
frm.add_custom_button(__("Create Payment Entry"), function () {
frappe.call({
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 7674712374c8..46723df10610 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -21,6 +21,7 @@
"grand_total",
"is_a_subscription",
"column_break_18",
+ "outstanding_amount",
"currency",
"subscription_section",
"subscription_plans",
@@ -400,13 +401,21 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.docstatus==1",
+ "fieldname": "outstanding_amount",
+ "fieldtype": "Currency",
+ "label": "Outstanding Amount",
+ "non_negative": 1,
+ "read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-06-20 13:54:55.245774",
+ "modified": "2024-07-20 17:54:33.064658",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -444,4 +453,4 @@
"sort_field": "creation",
"sort_order": "DESC",
"states": []
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 8d20493706ce..51ebb7e2f8ea 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -49,6 +49,7 @@ class PaymentRequest(Document):
cost_center: DF.Link | None
currency: DF.Link | None
email_to: DF.Data | None
+ failed_reason: DF.Data | None
grand_total: DF.Currency
iban: DF.ReadOnly | None
is_a_subscription: DF.Check
@@ -57,16 +58,17 @@ class PaymentRequest(Document):
mode_of_payment: DF.Link | None
mute_email: DF.Check
naming_series: DF.Literal["ACC-PRQ-.YYYY.-"]
+ outstanding_amount: DF.Currency
party: DF.DynamicLink | None
party_type: DF.Link | None
payment_account: DF.ReadOnly | None
- payment_channel: DF.Literal["", "Email", "Phone"]
+ payment_channel: DF.Literal["", "Email", "Phone", "Other"]
payment_gateway: DF.ReadOnly | None
payment_gateway_account: DF.Link | None
payment_order: DF.Link | None
payment_request_type: DF.Literal["Outward", "Inward"]
payment_url: DF.Data | None
- print_format: DF.Literal
+ print_format: DF.Literal[None]
project: DF.Link | None
reference_doctype: DF.Link | None
reference_name: DF.DynamicLink | None
@@ -100,6 +102,9 @@ def validate_reference_document(self):
frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self):
+ if self.grand_total == 0:
+ frappe.throw(_("Total Payment Request cannot be zero"))
+
existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
)
@@ -159,6 +164,8 @@ def on_change(self):
ref_doc.set_advance_payment_status()
def before_submit(self):
+ self.outstanding_amount = self.grand_total
+
if self.payment_request_type == "Outward":
self.status = "Initiated"
elif self.payment_request_type == "Inward":
@@ -265,7 +272,9 @@ def get_payment_url(self):
)
def set_as_paid(self):
- if self.payment_channel == "Phone":
+ self.db_set("status", "Paid")
+
+ if self.payment_channel == "Phone" and self.status != "Paid":
self.db_set("status", "Paid")
else:
@@ -277,6 +286,8 @@ def set_as_paid(self):
def create_payment_entry(self, submit=True):
"""create entry"""
+ if self.payment_channel == "Phone":
+ frappe.throw(_("Payment Entry cannot be created for Phone Payment"))
frappe.flags.ignore_account_permission = True
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
@@ -290,11 +301,14 @@ def create_payment_entry(self, submit=True):
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account)
- bank_amount = self.grand_total
+ bank_amount = self.outstanding_amount
+
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
+ total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
+ base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
+ party_amount = round(self.outstanding / total * base_total, self.precision("grand_total"))
else:
- party_amount = self.grand_total
+ party_amount = self.outstanding_amount
payment_entry = get_payment_entry(
self.reference_doctype,
@@ -315,6 +329,9 @@ def create_payment_entry(self, submit=True):
}
)
+ # Add reference of Payment Request
+ payment_entry.get("references")[0].payment_request = self.name
+
# Update dimensions
payment_entry.update(
{
@@ -323,13 +340,8 @@ def create_payment_entry(self, submit=True):
}
)
- if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- amount = payment_entry.base_paid_amount
- else:
- amount = self.grand_total
-
- payment_entry.received_amount = amount
- payment_entry.get("references")[0].allocated_amount = amount
+ payment_entry.received_amount = self.outstanding_amount
+ payment_entry.get("references")[0].allocated_amount = self.outstanding_amount
for dimension in get_accounting_dimensions():
payment_entry.update({dimension: self.get(dimension)})
@@ -529,7 +541,6 @@ def get_amount(ref_doc, payment_account=None):
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
- grand_total -= get_paid_amount_against_order(dt, ref_doc.name)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
if ref_doc.party_account_currency == ref_doc.currency:
@@ -554,24 +565,20 @@ def get_amount(ref_doc, payment_account=None):
def get_existing_payment_request_amount(ref_dt, ref_dn):
"""
- Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
- and get the summation of existing paid payment request for Phone payment channel.
+ Get the total amount of `Paid` / `Partially Paid` payment requests against a document.
"""
- existing_payment_request_amount = frappe.db.sql(
- """
- select sum(grand_total)
- from `tabPayment Request`
- where
- reference_doctype = %s
- and reference_name = %s
- and docstatus = 1
- and (status != 'Paid'
- or (payment_channel = 'Phone'
- and status = 'Paid'))
- """,
- (ref_dt, ref_dn),
+ PR = frappe.qb.DocType("Payment Request")
+
+ response = (
+ frappe.qb.from_(PR)
+ .select(Sum(PR.grand_total - PR.outstanding_amount))
+ .where(PR.reference_doctype == ref_dt)
+ .where(PR.reference_name == ref_dn)
+ .where(PR.docstatus == 1)
+ .run()
)
- return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
+
+ return response[0][0] or 0
def get_gateway_details(args): # nosemgrep
@@ -613,41 +620,86 @@ def make_payment_entry(docname):
return doc.create_payment_entry(submit=False).as_dict()
-def update_payment_req_status(doc, method):
- from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
+def update_payment_req_outstanding_amount(pe_doc, cancel=False):
+ outstanding_amounts = get_outstanding_amount_of_payment_entry_references(pe_doc.references)
- for ref in doc.references:
- payment_request_name = frappe.db.get_value(
- "Payment Request",
- {
- "reference_doctype": ref.reference_doctype,
- "reference_name": ref.reference_name,
- "docstatus": 1,
- },
+ for ref in pe_doc.references:
+ if not ref.payment_request:
+ continue
+
+ old_outstanding_amount = outstanding_amounts[ref.payment_request]
+
+ new_outstanding_amount = (
+ old_outstanding_amount + ref.allocated_amount
+ if cancel
+ else old_outstanding_amount - ref.allocated_amount
)
- if payment_request_name:
- ref_details = get_reference_details(
- ref.reference_doctype,
- ref.reference_name,
- doc.party_account_currency,
- doc.party_type,
- doc.party,
+ if not cancel and new_outstanding_amount < 0:
+ frappe.throw(
+ _(
+ "The allocated amount is greater than the outstanding amount of Payment Request {0}"
+ ).format(ref.payment_request)
)
- pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
- status = pay_req_doc.status
-
- if status != "Paid" and not ref_details.outstanding_amount:
- status = "Paid"
- elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:
- status = "Partially Paid"
- elif ref_details.outstanding_amount == ref_details.total_amount:
- if pay_req_doc.payment_request_type == "Outward":
- status = "Initiated"
- elif pay_req_doc.payment_request_type == "Inward":
- status = "Requested"
-
- pay_req_doc.db_set("status", status)
+
+ frappe.db.set_value(
+ "Payment Request",
+ ref.payment_request,
+ "outstanding_amount",
+ new_outstanding_amount,
+ )
+
+
+def update_payment_req_status(pe_doc, method):
+ payment_requests = frappe.get_all(
+ "Payment Request",
+ filters={"name": ["in", get_referenced_payment_requests(pe_doc.references)]},
+ fields=[
+ "name",
+ "grand_total",
+ "outstanding_amount",
+ "payment_request_type",
+ ],
+ )
+
+ payment_requests = {pr.name: pr for pr in payment_requests}
+
+ for ref in pe_doc.references:
+ if not ref.payment_request:
+ continue
+
+ payment_request = payment_requests[ref.payment_request]
+
+ if payment_request["outstanding_amount"] == payment_request["grand_total"]:
+ status = "Initiated" if payment_request["payment_request_type"] == "Outward" else "Requested"
+ elif payment_request["outstanding_amount"] == 0:
+ status = "Paid"
+ elif payment_request["outstanding_amount"] > 0:
+ status = "Partially Paid"
+
+ frappe.db.set_value(
+ "Payment Request",
+ ref.payment_request,
+ "status",
+ status,
+ )
+
+
+def get_outstanding_amount_of_payment_entry_references(references: list) -> dict:
+ payment_requests = get_referenced_payment_requests(references)
+
+ return dict(
+ frappe.get_all(
+ "Payment Request",
+ filters={"name": ["in", payment_requests]},
+ fields=["name", "outstanding_amount"],
+ as_list=True,
+ )
+ )
+
+
+def get_referenced_payment_requests(references: list) -> set:
+ return {row.payment_request for row in references if row.payment_request}
def get_dummy_message(doc):
From 469b805a97948dce202eeabcef957790eaea4911 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 11:37:52 +0530
Subject: [PATCH 02/60] chore: minor changes
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 694c5fe78e67..5722a9137a85 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2016,8 +2016,6 @@ def get_outstanding_reference_documents(args, validate=False):
)
)
- frappe.log("Data")
- frappe.log(data)
return data
From 3718762fab0be651b7a975b95fedab48b0574860 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 15:32:07 +0530
Subject: [PATCH 03/60] fix: remove bug
---
.../doctype/payment_request/payment_request.py | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 51ebb7e2f8ea..85e1a6c10219 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -467,6 +467,7 @@ def make_payment_request(**args):
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
)
+ # fetches existing payment request `grand_total` amount
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount:
@@ -484,7 +485,6 @@ def make_payment_request(**args):
args["payment_request_type"] = (
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
)
-
pr.update(
{
"payment_gateway_account": gateway_account.get("name"),
@@ -563,15 +563,23 @@ def get_amount(ref_doc, payment_account=None):
return grand_total
-def get_existing_payment_request_amount(ref_dt, ref_dn):
+def get_existing_payment_request_amount(ref_dt, ref_dn, only_paid=False):
"""
- Get the total amount of `Paid` / `Partially Paid` payment requests against a document.
+ Return the total amount of Payment Requests against a reference document. \n
+ If `only_paid` is True, it will return the total amount of paid Payment Requests. \n
+ Else, it will return the total amount of all Payment Requests.
"""
+
PR = frappe.qb.DocType("Payment Request")
+ if only_paid:
+ select = Sum(PR.grand_total - PR.outstanding_amount)
+ else:
+ select = Sum(PR.grand_total)
+
response = (
frappe.qb.from_(PR)
- .select(Sum(PR.grand_total - PR.outstanding_amount))
+ .select(select)
.where(PR.reference_doctype == ref_dt)
.where(PR.reference_name == ref_dn)
.where(PR.docstatus == 1)
@@ -709,7 +717,7 @@ def get_dummy_message(doc):
{%- else %}Hello,
{% endif %}
{{ _("Requesting payment against {0} {1} for amount {2}").format(doc.doctype,
- doc.name, doc.get_formatted("grand_total")) }}
+ doc.name, doc.get_formatted("grand_total")) }}
{{ _("Make Payment") }}
From 9ed1d8145c4b201e4241003e0ace897ceeaaba14 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 15:36:51 +0530
Subject: [PATCH 04/60] fix: replace `round` with `flt`
---
erpnext/accounts/doctype/payment_request/payment_request.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 85e1a6c10219..4d269dcbbbe8 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -306,7 +306,7 @@ def create_payment_entry(self, submit=True):
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
- party_amount = round(self.outstanding / total * base_total, self.precision("grand_total"))
+ party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
else:
party_amount = self.outstanding_amount
From aeddb817b40aaedea0af2b1050cdafd75c5d9dde Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 17:21:57 +0530
Subject: [PATCH 05/60] fix: update `set_advance_payment_status()` logic
---
.../doctype/payment_entry/payment_entry.py | 2 -
.../payment_request/payment_request.py | 9 ++--
erpnext/controllers/accounts_controller.py | 41 +++++++++----------
3 files changed, 22 insertions(+), 30 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 5722a9137a85..dc6d98d2b781 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1858,7 +1858,6 @@ def _on_previous_row_error(row_range):
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
-# todo: modify its test
@frappe.whitelist()
def get_outstanding_reference_documents(args, validate=False):
if isinstance(args, str):
@@ -2016,7 +2015,6 @@ def get_outstanding_reference_documents(args, validate=False):
)
)
-
return data
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 4d269dcbbbe8..f93d74fa1939 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -272,10 +272,8 @@ def get_payment_url(self):
)
def set_as_paid(self):
- self.db_set("status", "Paid")
-
if self.payment_channel == "Phone" and self.status != "Paid":
- self.db_set("status", "Paid")
+ self.db_set({"status": "Paid", "outstanding_amount": 0})
else:
payment_entry = self.create_payment_entry()
@@ -304,8 +302,8 @@ def create_payment_entry(self, submit=True):
bank_amount = self.outstanding_amount
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
- base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
+ total = ref_doc.get("rounded_total") or ref_doc.grand_total
+ base_total = ref_doc.get("base_rounded_total") or ref_doc.base_grand_total
party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
else:
party_amount = self.outstanding_amount
@@ -569,7 +567,6 @@ def get_existing_payment_request_amount(ref_dt, ref_dn, only_paid=False):
If `only_paid` is True, it will return the total amount of paid Payment Requests. \n
Else, it will return the total amount of all Payment Requests.
"""
-
PR = frappe.qb.DocType("Payment Request")
if only_paid:
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 3a3de662d591..95adce8f2387 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1943,35 +1943,32 @@ def set_total_advance_paid(self):
self.set_advance_payment_status()
def set_advance_payment_status(self):
- new_status = None
+ from erpnext.accounts.doctype.payment_request.payment_request import (
+ get_existing_payment_request_amount as get_paid_amount,
+ )
- stati = frappe.get_all(
+ new_status = None
+ available_payment_requests = frappe.db.count(
"Payment Request",
- {
- "reference_doctype": self.doctype,
- "reference_name": self.name,
- "docstatus": 1,
- },
- pluck="status",
+ {"reference_doctype": self.doctype, "reference_name": self.name, "docstatus": 1},
)
- if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
- if not stati:
+ total_amount = self.get("rounded_total") or self.grand_total
+ paid_amount = get_paid_amount(self.doctype, self.name, only_paid=True)
+
+ if paid_amount == total_amount:
+ new_status = "Fully Paid"
+ elif paid_amount > 0 and paid_amount < total_amount:
+ new_status = "Partially Paid"
+ elif self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
+ if not available_payment_requests:
new_status = "Not Requested"
- elif "Requested" in stati or "Failed" in stati:
+ elif paid_amount == 0 or paid_amount == 0.0:
new_status = "Requested"
- elif "Partially Paid" in stati:
- new_status = "Partially Paid"
- elif "Paid" in stati:
- new_status = "Fully Paid"
- if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
- if not stati:
+ elif self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
+ if not available_payment_requests:
new_status = "Not Initiated"
- elif "Initiated" in stati or "Failed" in stati or "Payment Ordered" in stati:
+ elif paid_amount == 0 or paid_amount == 0.0:
new_status = "Initiated"
- elif "Partially Paid" in stati:
- new_status = "Partially Paid"
- elif "Paid" in stati:
- new_status = "Fully Paid"
if new_status == self.advance_payment_status:
return
From 3c4b84dec3bd8874b397d326521f81e3408b0410 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 22 Jul 2024 18:52:00 +0530
Subject: [PATCH 06/60] fix: removed bug of `set_advance_payment_status`
---
.../doctype/payment_entry/payment_entry.py | 18 +++++++++++++--
.../payment_request/payment_request.py | 22 +++++++++++--------
erpnext/controllers/accounts_controller.py | 2 ++
3 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index dc6d98d2b781..1854daab5864 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -193,6 +193,7 @@ def on_submit(self):
self.update_payment_schedule()
self.set_payment_req_outstanding_amount()
self.set_payment_req_status()
+ self.set_reference_advance_payment_status()
self.set_status()
def set_liability_account(self):
@@ -269,6 +270,7 @@ def on_cancel(self):
self.update_payment_schedule(cancel=1)
self.set_payment_req_outstanding_amount(cancel=True)
self.set_payment_req_status()
+ self.set_reference_advance_payment_status()
self.set_status()
def set_payment_req_outstanding_amount(self, cancel=False):
@@ -283,6 +285,18 @@ def set_payment_req_status(self):
update_payment_req_status(self, None)
+ # todo: need to optimize
+ def set_reference_advance_payment_status(self):
+ advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
+ "advance_payment_payable_doctypes"
+ )
+
+ for ref in self.get("references"):
+ ref_doc = frappe.get_doc(ref.reference_doctype, ref.reference_name)
+ if ref.reference_doctype in advance_payment_doctypes:
+ # set advance payment status
+ ref_doc.set_advance_payment_status()
+
def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True)
@@ -1753,7 +1767,7 @@ def check_payment_requests(self):
frappe.msgprint(msg=msg, alert=True, indicator="orange")
- # todo: can be optimized
+ # todo: can be optimize
@frappe.whitelist()
def set_matched_payment_requests(self):
if not self.references:
@@ -1808,7 +1822,7 @@ def set_matched_payment_request(self, row_idx):
)
-# todo: can be optimized
+# todo: can be optimize
def get_matched_payment_request(reference_doctype, reference_name, outstanding_amount):
payment_requests = frappe.get_all(
doctype="Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index f93d74fa1939..f3adfd035117 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -154,15 +154,6 @@ def validate_subscription_details(self):
).format(self.grand_total, amount)
)
- def on_change(self):
- ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
- advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
- "advance_payment_payable_doctypes"
- )
- if self.reference_doctype in advance_payment_doctypes:
- # set advance payment status
- ref_doc.set_advance_payment_status()
-
def before_submit(self):
self.outstanding_amount = self.grand_total
@@ -180,6 +171,9 @@ def before_submit(self):
self.send_email()
self.make_communication_entry()
+ def on_submit(self):
+ self.update_reference_advance_payment_status()
+
def request_phone_payment(self):
controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
@@ -217,6 +211,7 @@ def get_request_amount(self):
def on_cancel(self):
self.check_if_payment_entry_exists()
self.set_as_cancelled()
+ self.update_reference_advance_payment_status()
def make_invoice(self):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
@@ -424,6 +419,15 @@ def create_subscription(self, payment_provider, gateway_controller, data):
return create_stripe_subscription(gateway_controller, data)
+ def update_reference_advance_payment_status(self):
+ ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
+ advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
+ "advance_payment_payable_doctypes"
+ )
+ if self.reference_doctype in advance_payment_doctypes:
+ # set advance payment status
+ ref_doc.set_advance_payment_status()
+
@frappe.whitelist(allow_guest=True)
def make_payment_request(**args):
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 95adce8f2387..3c4599f30d98 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1942,6 +1942,8 @@ def set_total_advance_paid(self):
self.set_advance_payment_status()
+ # todo: need to optimize
+ # todo: modularize
def set_advance_payment_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import (
get_existing_payment_request_amount as get_paid_amount,
From a26a75f83131e23f3e8accb8aa89af042d94eb5b Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 24 Jul 2024 16:54:20 +0530
Subject: [PATCH 07/60] fix: changes as per review
---
.../doctype/payment_entry/payment_entry.js | 21 +--
.../doctype/payment_entry/payment_entry.py | 173 ++++++++++--------
.../payment_request/payment_request.json | 10 +-
.../payment_request/payment_request.py | 105 +++++------
erpnext/controllers/accounts_controller.py | 44 ++---
5 files changed, 170 insertions(+), 183 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index c6c3651cb857..1c54ee2b733b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,8 +7,6 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
erpnext.accounts.taxes.setup_tax_validations("Payment Entry");
erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
-const FAULTY_VALUES = ["", null, undefined, 0];
-
frappe.ui.form.on("Payment Entry", {
onload: function (frm) {
frm.ignore_doctypes_on_cancel_all = [
@@ -1010,8 +1008,6 @@ frappe.ui.form.on("Payment Entry", {
total_negative_outstanding - total_positive_outstanding
);
}
-
- frm.events.set_matched_payment_requests(frm);
}
frm.events.allocate_party_amount_against_ref_docs(
@@ -1117,6 +1113,7 @@ frappe.ui.form.on("Payment Entry", {
});
frm.refresh_fields();
+ if (frappe.flags.allocate_payment_amount) frm.call("set_matched_payment_requests");
frm.events.set_total_allocated_amount(frm);
},
@@ -1664,11 +1661,6 @@ frappe.ui.form.on("Payment Entry", {
return current_tax_amount;
},
-
- set_matched_payment_requests: async function (frm) {
- await frappe.after_ajax();
- frm.call("set_matched_payment_requests");
- },
});
frappe.ui.form.on("Payment Entry Reference", {
@@ -1714,15 +1706,10 @@ frappe.ui.form.on("Payment Entry Reference", {
allocated_amount: function (frm, cdt, cdn) {
frm.events.set_total_allocated_amount(frm);
- const row = locals[cdt][cdn];
+ const row = frappe.get_doc(cdt, cdn);
- // if payment_request already set then return
- if (row.payment_request) return;
-
- const references = [row.reference_name, row.reference_doctype, row.allocated_amount];
-
- // if any of the reference fields are faulty, it returns
- if (FAULTY_VALUES.some((el) => references.includes(el))) return;
+ if (row.payment_request || !row.reference_name || !row.reference_doctype || !row.allocated_amount)
+ return;
frm.call("set_matched_payment_request", { row_idx: row.idx });
},
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 1854daab5864..9cc85243d7d7 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -182,7 +182,7 @@ def validate(self):
self.set_total_in_words()
def before_save(self):
- self.check_payment_requests()
+ self.check_references_for_unset_payment_request()
def on_submit(self):
if self.difference_amount:
@@ -191,9 +191,8 @@ def on_submit(self):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
- self.set_payment_req_outstanding_amount()
- self.set_payment_req_status()
- self.set_reference_advance_payment_status()
+ self.update_payment_requests()
+ self.update_references_advance_payment_status()
self.set_status()
def set_liability_account(self):
@@ -268,33 +267,25 @@ def on_cancel(self):
self.update_advance_paid()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
- self.set_payment_req_outstanding_amount(cancel=True)
- self.set_payment_req_status()
- self.set_reference_advance_payment_status()
+ self.update_payment_requests(cancel=True)
+ self.update_references_advance_payment_status()
self.set_status()
- def set_payment_req_outstanding_amount(self, cancel=False):
+ def update_payment_requests(self, cancel=False):
from erpnext.accounts.doctype.payment_request.payment_request import (
- update_payment_req_outstanding_amount,
+ update_payment_requests_as_per_pe_references,
)
- update_payment_req_outstanding_amount(self, cancel=cancel)
+ update_payment_requests_as_per_pe_references(self.references, cancel=cancel)
- def set_payment_req_status(self):
- from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
-
- update_payment_req_status(self, None)
-
- # todo: need to optimize
- def set_reference_advance_payment_status(self):
+ def update_references_advance_payment_status(self):
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
for ref in self.get("references"):
- ref_doc = frappe.get_doc(ref.reference_doctype, ref.reference_name)
if ref.reference_doctype in advance_payment_doctypes:
- # set advance payment status
+ ref_doc = frappe.get_doc(ref.reference_doctype, ref.reference_name)
ref_doc.set_advance_payment_status()
def update_outstanding_amounts(self):
@@ -335,7 +326,7 @@ def validate_allocated_amount(self):
if self.payment_type == "Internal Transfer":
return
- self.validate_allocated_amount_as_of_pr()
+ self.validate_allocated_amount_as_per_payment_request()
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
@@ -349,7 +340,7 @@ def validate_allocated_amount(self):
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
- def validate_allocated_amount_as_of_pr(self):
+ def validate_allocated_amount_as_per_payment_request(self):
from erpnext.accounts.doctype.payment_request.payment_request import (
get_outstanding_amount_of_payment_entry_references as get_outstanding_amounts,
)
@@ -359,9 +350,10 @@ def validate_allocated_amount_as_of_pr(self):
for ref in self.references:
if ref.payment_request and ref.allocated_amount > outstanding_amounts[ref.payment_request]:
frappe.throw(
- _("Allocated Amount cannot be greater than Outstanding Amount of {0}").format(
- get_link_to_form("Payment Request", ref.payment_request)
- )
+ msg=_(
+ "Row #{0}: Allocated Amount cannot be greater than Outstanding Amount of Payment Request {1}"
+ ).format(ref.idx, get_link_to_form("Payment Request", ref.payment_request)),
+ title=_("Invalid Allocated Amount"),
)
def term_based_allocation_enabled_for_reference(
@@ -1744,59 +1736,69 @@ def get_current_tax_fraction(self, tax):
return current_tax_fraction
- def check_payment_requests(self):
+ def check_references_for_unset_payment_request(self):
if not self.references:
return
- not_set_count = sum(1 for row in self.references if not row.payment_request)
+ matched_payment_requests = get_matched_payment_requests_of_references(
+ [row for row in self.references if not row.payment_request]
+ )
- if not_set_count == 0:
- return
- elif not_set_count == 1:
- msg = _("{0} {1} is not set in {2}").format(
- not_set_count,
- frappe.bold("Payment Request"),
- frappe.bold("Payment References"),
- )
- else:
- msg = _("{0} {1} are not set in {2}").format(
- not_set_count,
- frappe.bold("Payment Request"),
- frappe.bold("Payment References"),
+ unset_pr_rows = {}
+
+ for row in self.references:
+ if row.payment_request:
+ continue
+
+ matched_pr = matched_payment_requests.get(
+ (row.reference_doctype, row.reference_name, row.allocated_amount)
)
- frappe.msgprint(msg=msg, alert=True, indicator="orange")
+ if matched_pr:
+ unset_pr_rows[row.idx] = matched_pr
+
+ if unset_pr_rows:
+ message = _("Matched Payment Requests found for references, but not set.
")
+ message += _("View Details
")
+ for idx, pr in unset_pr_rows.items():
+ message += _("- Row #{0}: {1}
").format(idx, get_link_to_form("Payment Request", pr))
+ message += _("
")
+
+ frappe.msgprint(
+ msg=message,
+ indicator="yellow",
+ )
- # todo: can be optimize
@frappe.whitelist()
def set_matched_payment_requests(self):
if not self.references:
return
+ matched_payment_requests = get_matched_payment_requests_of_references(self.references)
+
matched_count = 0
for row in self.references:
- if row.payment_request or (
- not row.reference_doctype or not row.reference_name or not row.allocated_amount
+ if (
+ row.payment_request
+ or not row.reference_doctype
+ or not row.reference_name
+ or not row.allocated_amount
):
continue
- row.payment_request = get_matched_payment_request(
- row.reference_doctype, row.reference_name, row.allocated_amount
+ row.payment_request = matched_payment_requests.get(
+ (row.reference_doctype, row.reference_name, row.allocated_amount)
)
if row.payment_request:
matched_count += 1
- if matched_count == 0:
+ if not matched_count:
return
- elif matched_count == 1:
- msg = _("{0} matched {1} is set").format(matched_count, frappe.bold("Payment Request"))
- else:
- msg = _("{0} matched {1} are set").format(matched_count, frappe.bold("Payment Request"))
frappe.msgprint(
- msg=msg,
+ msg=_("Setting {0} matched Payment Request(s)").format(matched_count),
alert=True,
)
@@ -1808,38 +1810,61 @@ def set_matched_payment_request(self, row_idx):
frappe.throw(_("Row #{0} not found").format(row_idx), title=_("Row Not Found"))
# if payment entry already set then do not set it again
- if row.payment_request:
+ if (
+ row.payment_request
+ or not row.reference_doctype
+ or not row.reference_name
+ or not row.allocated_amount
+ ):
+ return
+
+ matched_pr = get_matched_payment_requests_of_references([row])
+
+ if not matched_pr:
return
- row.payment_request = get_matched_payment_request(
- row.reference_doctype, row.reference_name, row.allocated_amount
+ row.payment_request = matched_pr[(row.reference_doctype, row.reference_name, row.allocated_amount)]
+
+ frappe.msgprint(
+ msg=_("Setting matched Payment Request"),
+ alert=True,
)
- if row.payment_request:
- frappe.msgprint(
- msg=_("Matched {0} is set").format(frappe.bold("Payment Request")),
- alert=True,
- )
+# FIXME: can be optimize and use query builder
+def get_matched_payment_requests_of_references(references=None):
+ if not references:
+ return
-# todo: can be optimize
-def get_matched_payment_request(reference_doctype, reference_name, outstanding_amount):
- payment_requests = frappe.get_all(
- doctype="Payment Request",
- filters={
- "reference_doctype": reference_doctype,
- "reference_name": reference_name,
- "outstanding_amount": outstanding_amount,
- "status": ["!=", "Paid"],
- "docstatus": 1,
- },
- pluck="name",
+ refs = [
+ (row.reference_doctype, row.reference_name, row.allocated_amount)
+ for row in references
+ if row.reference_doctype and row.reference_name and row.allocated_amount
+ ]
+
+ if not refs:
+ return
+
+ all_matched_prs = frappe.db.sql(
+ """
+ select name, reference_doctype, reference_name, outstanding_amount
+ from `tabPayment Request`
+ where (reference_doctype, reference_name, outstanding_amount) in %s
+ and status != 'Paid' and docstatus = 1
+ """,
+ (refs,),
+ as_dict=True,
)
- if len(payment_requests) == 1:
- return payment_requests[0]
+ single_matched_prs = {}
+ for pr in all_matched_prs:
+ key = (pr.reference_doctype, pr.reference_name, pr.outstanding_amount)
+ if key in single_matched_prs:
+ single_matched_prs[key].append(pr.name)
+ else:
+ single_matched_prs[key] = [pr.name]
- return None
+ return {key: names[0] for key, names in single_matched_prs.items() if len(names) == 1}
def validate_inclusive_tax(tax, doc):
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 46723df10610..50cc12aa4ea2 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -134,7 +134,8 @@
"no_copy": 1,
"options": "reference_doctype",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "transaction_details",
@@ -147,7 +148,8 @@
"fieldtype": "Currency",
"label": "Amount",
"non_negative": 1,
- "options": "currency"
+ "options": "currency",
+ "reqd": 1
},
{
"default": "0",
@@ -403,7 +405,7 @@
"read_only": 1
},
{
- "depends_on": "eval:doc.docstatus==1",
+ "depends_on": "eval: doc.docstatus === 1",
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
"label": "Outstanding Amount",
@@ -415,7 +417,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-07-20 17:54:33.064658",
+ "modified": "2024-07-23 19:02:07.754296",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index f3adfd035117..83c4cd966bd8 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -103,7 +103,10 @@ def validate_reference_document(self):
def validate_payment_request_amount(self):
if self.grand_total == 0:
- frappe.throw(_("Total Payment Request cannot be zero"))
+ frappe.throw(
+ _("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
+ title=_("Invalid Amount"),
+ )
existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
@@ -267,7 +270,7 @@ def get_payment_url(self):
)
def set_as_paid(self):
- if self.payment_channel == "Phone" and self.status != "Paid":
+ if self.payment_channel == "Phone":
self.db_set({"status": "Paid", "outstanding_amount": 0})
else:
@@ -279,8 +282,6 @@ def set_as_paid(self):
def create_payment_entry(self, submit=True):
"""create entry"""
- if self.payment_channel == "Phone":
- frappe.throw(_("Payment Entry cannot be created for Phone Payment"))
frappe.flags.ignore_account_permission = True
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
@@ -297,8 +298,8 @@ def create_payment_entry(self, submit=True):
bank_amount = self.outstanding_amount
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- total = ref_doc.get("rounded_total") or ref_doc.grand_total
- base_total = ref_doc.get("base_rounded_total") or ref_doc.base_grand_total
+ total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
+ base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
else:
party_amount = self.outstanding_amount
@@ -314,7 +315,6 @@ def create_payment_entry(self, submit=True):
payment_entry.update(
{
"mode_of_payment": self.mode_of_payment,
- "reference_no": self.name,
"reference_date": nowdate(),
"remarks": "Payment Entry against {} {} via Payment Request {}".format(
self.reference_doctype, self.reference_name, self.name
@@ -323,7 +323,7 @@ def create_payment_entry(self, submit=True):
)
# Add reference of Payment Request
- payment_entry.get("references")[0].payment_request = self.name
+ payment_entry.references[0].payment_request = self.name
# Update dimensions
payment_entry.update(
@@ -333,9 +333,6 @@ def create_payment_entry(self, submit=True):
}
)
- payment_entry.received_amount = self.outstanding_amount
- payment_entry.get("references")[0].allocated_amount = self.outstanding_amount
-
for dimension in get_accounting_dimensions():
payment_entry.update({dimension: self.get(dimension)})
@@ -420,12 +417,11 @@ def create_subscription(self, payment_provider, gateway_controller, data):
return create_stripe_subscription(gateway_controller, data)
def update_reference_advance_payment_status(self):
- ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
if self.reference_doctype in advance_payment_doctypes:
- # set advance payment status
+ ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
ref_doc.set_advance_payment_status()
@@ -475,6 +471,9 @@ def make_payment_request(**args):
if existing_payment_request_amount:
grand_total -= existing_payment_request_amount
+ if not grand_total:
+ frappe.throw(_("Payment Request is already created"))
+
if draft_payment_request:
frappe.db.set_value(
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
@@ -565,29 +564,22 @@ def get_amount(ref_doc, payment_account=None):
return grand_total
-def get_existing_payment_request_amount(ref_dt, ref_dn, only_paid=False):
+def get_existing_payment_request_amount(ref_dt, ref_dn):
"""
- Return the total amount of Payment Requests against a reference document. \n
- If `only_paid` is True, it will return the total amount of paid Payment Requests. \n
- Else, it will return the total amount of all Payment Requests.
+ Return the total amount of Payment Requests against a reference document.
"""
PR = frappe.qb.DocType("Payment Request")
- if only_paid:
- select = Sum(PR.grand_total - PR.outstanding_amount)
- else:
- select = Sum(PR.grand_total)
-
response = (
frappe.qb.from_(PR)
- .select(select)
+ .select(Sum(PR.grand_total))
.where(PR.reference_doctype == ref_dt)
.where(PR.reference_name == ref_dn)
.where(PR.docstatus == 1)
.run()
)
- return response[0][0] or 0
+ return response[0][0] if response else 0
def get_gateway_details(args): # nosemgrep
@@ -629,40 +621,13 @@ def make_payment_entry(docname):
return doc.create_payment_entry(submit=False).as_dict()
-def update_payment_req_outstanding_amount(pe_doc, cancel=False):
- outstanding_amounts = get_outstanding_amount_of_payment_entry_references(pe_doc.references)
-
- for ref in pe_doc.references:
- if not ref.payment_request:
- continue
-
- old_outstanding_amount = outstanding_amounts[ref.payment_request]
-
- new_outstanding_amount = (
- old_outstanding_amount + ref.allocated_amount
- if cancel
- else old_outstanding_amount - ref.allocated_amount
- )
-
- if not cancel and new_outstanding_amount < 0:
- frappe.throw(
- _(
- "The allocated amount is greater than the outstanding amount of Payment Request {0}"
- ).format(ref.payment_request)
- )
-
- frappe.db.set_value(
- "Payment Request",
- ref.payment_request,
- "outstanding_amount",
- new_outstanding_amount,
- )
-
+def update_payment_requests_as_per_pe_references(references=None, cancel=False):
+ if not references:
+ return
-def update_payment_req_status(pe_doc, method):
payment_requests = frappe.get_all(
"Payment Request",
- filters={"name": ["in", get_referenced_payment_requests(pe_doc.references)]},
+ filters={"name": ["in", get_referenced_payment_requests(references)]},
fields=[
"name",
"grand_total",
@@ -673,24 +638,40 @@ def update_payment_req_status(pe_doc, method):
payment_requests = {pr.name: pr for pr in payment_requests}
- for ref in pe_doc.references:
+ for ref in references:
if not ref.payment_request:
continue
payment_request = payment_requests[ref.payment_request]
- if payment_request["outstanding_amount"] == payment_request["grand_total"]:
+ # update outstanding amount
+ new_outstanding_amount = (
+ payment_request["outstanding_amount"] + ref.allocated_amount
+ if cancel
+ else payment_request["outstanding_amount"] - ref.allocated_amount
+ )
+
+ if not cancel and new_outstanding_amount < 0:
+ frappe.throw(
+ msg=_(
+ "The allocated amount is greater than the outstanding amount of Payment Request {0}"
+ ).format(ref.payment_request),
+ title=_("Invalid Allocated Amount"),
+ )
+
+ # update status
+ if new_outstanding_amount == payment_request["grand_total"]:
status = "Initiated" if payment_request["payment_request_type"] == "Outward" else "Requested"
- elif payment_request["outstanding_amount"] == 0:
+ elif new_outstanding_amount == 0:
status = "Paid"
- elif payment_request["outstanding_amount"] > 0:
+ elif new_outstanding_amount > 0:
status = "Partially Paid"
+ # update database
frappe.db.set_value(
"Payment Request",
ref.payment_request,
- "status",
- status,
+ {"outstanding_amount": new_outstanding_amount, "status": status},
)
@@ -718,7 +699,7 @@ def get_dummy_message(doc):
{%- else %}Hello,
{% endif %}
{{ _("Requesting payment against {0} {1} for amount {2}").format(doc.doctype,
- doc.name, doc.get_formatted("grand_total")) }}
+ doc.name, doc.get_formatted("grand_total")) }}
{{ _("Make Payment") }}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 3c4599f30d98..1cb846cf5ce4 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1942,35 +1942,27 @@ def set_total_advance_paid(self):
self.set_advance_payment_status()
- # todo: need to optimize
- # todo: modularize
def set_advance_payment_status(self):
- from erpnext.accounts.doctype.payment_request.payment_request import (
- get_existing_payment_request_amount as get_paid_amount,
- )
-
new_status = None
- available_payment_requests = frappe.db.count(
- "Payment Request",
- {"reference_doctype": self.doctype, "reference_name": self.name, "docstatus": 1},
+
+ paid_amount = frappe.get_value(
+ doctype="Payment Request",
+ filters={
+ "reference_doctype": self.doctype,
+ "reference_name": self.name,
+ "docstatus": 1,
+ },
+ fieldname="sum(grand_total - outstanding_amount)",
)
- total_amount = self.get("rounded_total") or self.grand_total
- paid_amount = get_paid_amount(self.doctype, self.name, only_paid=True)
-
- if paid_amount == total_amount:
- new_status = "Fully Paid"
- elif paid_amount > 0 and paid_amount < total_amount:
- new_status = "Partially Paid"
- elif self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
- if not available_payment_requests:
- new_status = "Not Requested"
- elif paid_amount == 0 or paid_amount == 0.0:
- new_status = "Requested"
- elif self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
- if not available_payment_requests:
- new_status = "Not Initiated"
- elif paid_amount == 0 or paid_amount == 0.0:
- new_status = "Initiated"
+
+ if not paid_amount:
+ if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
+ new_status = "Not Requested" if paid_amount is None else "Requested"
+ elif self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
+ new_status = "Not Initiated" if paid_amount is None else "Initiated"
+ else:
+ total_amount = self.get("rounded_total") or self.get("grand_total")
+ new_status = "Fully Paid" if paid_amount == total_amount else "Partially Paid"
if new_status == self.advance_payment_status:
return
From e5a981bd730fc333181ee1de9aff91b2a1070fb0 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 25 Jul 2024 12:39:59 +0530
Subject: [PATCH 08/60] refactor: replace sql query of
`matched_payment_requests` to query builder
---
.../doctype/payment_entry/payment_entry.py | 39 ++++++++++---------
1 file changed, 21 insertions(+), 18 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9cc85243d7d7..b08464f73f07 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -7,6 +7,8 @@
import frappe
from frappe import ValidationError, _, qb, scrub, throw
+from frappe.query_builder import Tuple
+from frappe.query_builder.functions import Count
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils.data import comma_and, fmt_money, get_link_to_form
from pypika import Case
@@ -1831,11 +1833,11 @@ def set_matched_payment_request(self, row_idx):
)
-# FIXME: can be optimize and use query builder
def get_matched_payment_requests_of_references(references=None):
if not references:
return
+ # to fetch matched rows
refs = [
(row.reference_doctype, row.reference_name, row.allocated_amount)
for row in references
@@ -1845,26 +1847,27 @@ def get_matched_payment_requests_of_references(references=None):
if not refs:
return
- all_matched_prs = frappe.db.sql(
- """
- select name, reference_doctype, reference_name, outstanding_amount
- from `tabPayment Request`
- where (reference_doctype, reference_name, outstanding_amount) in %s
- and status != 'Paid' and docstatus = 1
- """,
- (refs,),
- as_dict=True,
+ PR = frappe.qb.DocType("Payment Request")
+
+ # query to group by reference_doctype, reference_name, outstanding_amount
+ subquery = (
+ frappe.qb.from_(PR)
+ .select(
+ PR.name, PR.reference_doctype, PR.reference_name, PR.outstanding_amount, Count("*").as_("count")
+ )
+ .where(Tuple(PR.reference_doctype, PR.reference_name, PR.outstanding_amount).isin(refs))
+ .where(PR.status != "Paid")
+ .where(PR.docstatus == 1)
+ .groupby(PR.reference_doctype, PR.reference_name, PR.outstanding_amount)
)
- single_matched_prs = {}
- for pr in all_matched_prs:
- key = (pr.reference_doctype, pr.reference_name, pr.outstanding_amount)
- if key in single_matched_prs:
- single_matched_prs[key].append(pr.name)
- else:
- single_matched_prs[key] = [pr.name]
+ # query to fetch matched rows which are single
+ matched_prs = frappe.qb.from_(subquery).select("*").where(subquery.count == 1).run(as_dict=True)
+
+ if not matched_prs:
+ return
- return {key: names[0] for key, names in single_matched_prs.items() if len(names) == 1}
+ return {(pr.reference_doctype, pr.reference_name, pr.outstanding_amount): pr.name for pr in matched_prs}
def validate_inclusive_tax(tax, doc):
From 558d0079701c882cc763cccac61fede954111c67 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 25 Jul 2024 13:02:55 +0530
Subject: [PATCH 09/60] fix: replace `locals` with `get_doc` in set_query
---
.../doctype/payment_entry/payment_entry.js | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 1c54ee2b733b..f8e8a6089ff8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -167,15 +167,14 @@ frappe.ui.form.on("Payment Entry", {
});
frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
- const row = locals[cdt][cdn];
- const filters = {
- docstatus: 1,
- status: ["!=", "Paid"],
- reference_doctype: row.reference_doctype,
- reference_name: row.reference_name,
- };
+ const row = frappe.get_doc(cdt, cdn);
return {
- filters: filters,
+ filters: {
+ docstatus: 1,
+ status: ["!=", "Paid"],
+ reference_doctype: row.reference_doctype,
+ reference_name: row.reference_name,
+ },
};
});
},
From 3c5ef059aeb7c1f4d3970ea077111758318ebe80 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Fri, 26 Jul 2024 16:49:31 +0530
Subject: [PATCH 10/60] fix: changes during review
---
.../doctype/payment_entry/payment_entry.js | 12 ++++++++++--
.../doctype/payment_entry/payment_entry.py | 19 ++++++++++++-------
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index f8e8a6089ff8..23b8f873b5de 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1087,6 +1087,8 @@ frappe.ui.form.on("Payment Entry", {
}
}
+ let set_matched_payment_requests = false;
+
$.each(frm.doc.references || [], function (i, row) {
if (frappe.flags.allocate_payment_amount == 0) {
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
@@ -1095,6 +1097,8 @@ frappe.ui.form.on("Payment Entry", {
frappe.flags.allocate_payment_amount != 0 &&
(!row.allocated_amount || paid_amount_change)
) {
+ let previous_allocated_amount = row.allocated_amount;
+
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
row.allocated_amount =
row.outstanding_amount >= allocated_positive_outstanding
@@ -1108,12 +1112,16 @@ frappe.ui.form.on("Payment Entry", {
: row.outstanding_amount;
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
}
+
+ if (!row.payment_request && row.allocated_amount > previous_allocated_amount) {
+ set_matched_payment_requests = true;
+ }
}
});
frm.refresh_fields();
- if (frappe.flags.allocate_payment_amount) frm.call("set_matched_payment_requests");
frm.events.set_total_allocated_amount(frm);
+ if (set_matched_payment_requests) frm.call("set_matched_payment_requests");
},
set_total_allocated_amount: function (frm) {
@@ -1707,7 +1715,7 @@ frappe.ui.form.on("Payment Entry Reference", {
const row = frappe.get_doc(cdt, cdn);
- if (row.payment_request || !row.reference_name || !row.reference_doctype || !row.allocated_amount)
+ if (row.payment_request || !(row.reference_doctype && row.reference_name && row.allocated_amount))
return;
frm.call("set_matched_payment_request", { row_idx: row.idx });
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index b08464f73f07..ef5945561c26 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -294,15 +294,17 @@ def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True)
def validate_duplicate_entry(self):
- reference_names = []
+ reference_names = {}
for d in self.get("references"):
- if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
+ key = (d.reference_doctype, d.reference_name, d.payment_term, d.payment_request)
+ if key in reference_names:
frappe.throw(
_("Row #{0}: Duplicate entry in References {1} {2}").format(
d.idx, d.reference_doctype, d.reference_name
)
)
- reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
+
+ reference_names.add(key)
def set_bank_account_data(self):
if self.bank_account:
@@ -1742,10 +1744,13 @@ def check_references_for_unset_payment_request(self):
if not self.references:
return
- matched_payment_requests = get_matched_payment_requests_of_references(
+ matched_payment_requests = get_matched_payment_requests(
[row for row in self.references if not row.payment_request]
)
+ if not matched_payment_requests:
+ return
+
unset_pr_rows = {}
for row in self.references:
@@ -1776,7 +1781,7 @@ def set_matched_payment_requests(self):
if not self.references:
return
- matched_payment_requests = get_matched_payment_requests_of_references(self.references)
+ matched_payment_requests = get_matched_payment_requests(self.references)
matched_count = 0
@@ -1820,7 +1825,7 @@ def set_matched_payment_request(self, row_idx):
):
return
- matched_pr = get_matched_payment_requests_of_references([row])
+ matched_pr = get_matched_payment_requests([row])
if not matched_pr:
return
@@ -1833,7 +1838,7 @@ def set_matched_payment_request(self, row_idx):
)
-def get_matched_payment_requests_of_references(references=None):
+def get_matched_payment_requests(references=None):
if not references:
return
From 83b10d4e319813fa618eeadde5204745073733cd Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 12 Aug 2024 12:15:56 +0530
Subject: [PATCH 11/60] fix: minor review changes
---
.../doctype/payment_entry/payment_entry.py | 18 +++---------------
1 file changed, 3 insertions(+), 15 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7b0e2cb00473..2d9945b96cce 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -190,10 +190,9 @@ def on_submit(self):
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
self.update_outstanding_amounts()
- self.update_advance_paid()
self.update_payment_schedule()
self.update_payment_requests()
- self.update_references_advance_payment_status()
+ self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def set_liability_account(self):
@@ -265,11 +264,10 @@ def on_cancel(self):
super().on_cancel()
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
- self.update_advance_paid()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.update_payment_requests(cancel=True)
- self.update_references_advance_payment_status()
+ self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def update_payment_requests(self, cancel=False):
@@ -279,21 +277,11 @@ def update_payment_requests(self, cancel=False):
update_payment_requests_as_per_pe_references(self.references, cancel=cancel)
- def update_references_advance_payment_status(self):
- advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
- "advance_payment_payable_doctypes"
- )
-
- for ref in self.get("references"):
- if ref.reference_doctype in advance_payment_doctypes:
- ref_doc = frappe.get_doc(ref.reference_doctype, ref.reference_name)
- ref_doc.set_advance_payment_status()
-
def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True)
def validate_duplicate_entry(self):
- reference_names = {}
+ reference_names = set()
for d in self.get("references"):
key = (d.reference_doctype, d.reference_name, d.payment_term, d.payment_request)
if key in reference_names:
From dc757e1091c443975961ea3f405c8a96a908aff5 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 14 Aug 2024 11:25:58 +0530
Subject: [PATCH 12/60] fix: remove unnecessary code for setting payment entry
received amount
---
.../accounts/doctype/payment_request/payment_request.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index ebe35ba0bc9c..161b0bfdadd4 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -333,14 +333,6 @@ def create_payment_entry(self, submit=True):
}
)
- if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- amount = payment_entry.base_paid_amount
- else:
- amount = self.grand_total
-
- payment_entry.received_amount = amount
- payment_entry.get("references")[0].allocated_amount = amount
-
# Update 'Paid Amount' on Forex transactions
if self.currency != ref_doc.company_currency:
if (
From 3bfda7958017c59a45695536ac1562c069830c1f Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 15 Aug 2024 01:14:35 +0530
Subject: [PATCH 13/60] fix: logic for ser payment_request if PE made from
transaction
---
.../doctype/payment_entry/payment_entry.py | 125 ++++++++++++++++++
.../payment_request/payment_request.py | 7 +-
2 files changed, 130 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 2d9945b96cce..d6c8e322ca19 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2473,6 +2473,7 @@ def get_reference_details(
return res
+# todo-abdeali: add one more parameter that if it created from `payment_request`
@frappe.whitelist()
def get_payment_entry(
dt,
@@ -2633,9 +2634,133 @@ def get_payment_entry(
pe.set_difference_amount()
+ # only if allocated_amount is set
+ set_open_payment_requests_to_references(pe.references)
+
return pe
+def get_open_payment_requests_for_references(references=None):
+ if not references:
+ return
+
+ refs = {
+ (row.reference_doctype, row.reference_name)
+ for row in references
+ if row.reference_doctype and row.reference_name and row.allocated_amount
+ }
+
+ if not refs:
+ return
+
+ PR = frappe.qb.DocType("Payment Request")
+
+ response = (
+ frappe.qb.from_(PR)
+ .select(PR.name, PR.reference_doctype, PR.reference_name, PR.outstanding_amount)
+ .where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs)))
+ .where(PR.status != "Paid")
+ .where(PR.docstatus == 1)
+ .orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc)
+ ).run(as_dict=True)
+
+ if not response:
+ return
+
+ reference_payment_requests = {}
+
+ for row in response:
+ key = (row.reference_doctype, row.reference_name)
+
+ if key not in reference_payment_requests:
+ reference_payment_requests[key] = {row.name: row.outstanding_amount}
+ else:
+ reference_payment_requests[key][row.name] = row.outstanding_amount
+
+ return reference_payment_requests
+
+
+# todo-abdeali: make it more efficient and less complex
+def set_open_payment_requests_to_references(references=None):
+ if not references:
+ return
+
+ reference_payment_requests = get_open_payment_requests_for_references(references)
+
+ if not reference_payment_requests:
+ return
+
+ row_idx = 0
+
+ while row_idx < len(references):
+ row = references[row_idx]
+ key = (row.reference_doctype, row.reference_name)
+
+ # ? can make it efficient if only one transaction is there but have multiple row because of terms
+ payment_requests = reference_payment_requests.get(key)
+
+ if not payment_requests:
+ row_idx += 1
+ continue
+
+ payment_request, outstanding_amount = next(iter(payment_requests.items()))
+ allocated_amount = row.allocated_amount
+
+ if outstanding_amount == allocated_amount:
+ row.payment_request = payment_request
+ del reference_payment_requests[key][payment_request]
+ row_idx += 1
+ elif outstanding_amount > allocated_amount:
+ row.payment_request = payment_request
+
+ reference_payment_requests[key][payment_request] -= allocated_amount
+ row_idx += 1
+
+ elif outstanding_amount < allocated_amount:
+ row.payment_request = payment_request
+ row.allocated_amount = outstanding_amount
+
+ del reference_payment_requests[key][payment_request]
+ allocated_amount -= outstanding_amount
+
+ while allocated_amount:
+ payment_request, outstanding_amount = next(iter(payment_requests.items()), (None, None))
+
+ new_row = frappe.copy_doc(row)
+ new_row.allocated_amount = allocated_amount
+ references.insert(row_idx + 1, new_row)
+
+ if not payment_request or not outstanding_amount:
+ new_row.allocated_amount = allocated_amount
+ new_row.payment_request = None
+ row_idx += 2
+ break
+ else:
+ new_row.payment_request = payment_request
+
+ if outstanding_amount == allocated_amount:
+ new_row.allocated_amount = allocated_amount
+ del reference_payment_requests[key][payment_request]
+ row_idx += 2
+
+ break
+ elif outstanding_amount > allocated_amount:
+ new_row.allocated_amount = allocated_amount
+ reference_payment_requests[key][payment_request] -= allocated_amount
+ row_idx += 2
+ break
+ elif outstanding_amount < allocated_amount:
+ allocated_amount -= outstanding_amount
+ new_row.allocated_amount = outstanding_amount
+
+ del reference_payment_requests[key][payment_request]
+ row_idx += 1
+
+ # set new idx to all refs
+ for idx, ref in enumerate(references, start=1):
+ ref.idx = idx
+
+
def update_accounting_dimensions(pe, doc):
"""
Updates accounting dimensions in Payment Entry based on the accounting dimensions in the reference document
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 161b0bfdadd4..a858f8a56965 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -561,10 +561,13 @@ def get_amount(ref_doc, payment_account=None):
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
+ # use rounded totals to match with PE
if ref_doc.party_account_currency == ref_doc.currency:
- grand_total = flt(ref_doc.grand_total)
+ grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
else:
- grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate
+ grand_total = (
+ flt(ref_doc.base_rounded_total) or flt(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:
From 208a7e3533344a61ab3098614e1378593f4c2a2c Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 20 Aug 2024 10:11:42 +0530
Subject: [PATCH 14/60] fix: Use rounded total to make Payment Request from
`Sales Invoice` or `Purchase Invoice`
---
.../accounts/doctype/payment_request/payment_request.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index a858f8a56965..776dbe45e747 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -561,12 +561,11 @@ def get_amount(ref_doc, payment_account=None):
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
- # use rounded totals to match with PE
if ref_doc.party_account_currency == ref_doc.currency:
- grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
+ grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
else:
- grand_total = (
- flt(ref_doc.base_rounded_total) or flt(ref_doc.base_grand_total) / ref_doc.conversion_rate
+ grand_total = flt(
+ 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:
From c2973d2e5cf4969736ae691e8730f433eaf8b70b Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 20 Aug 2024 14:37:37 +0530
Subject: [PATCH 15/60] refactor: enhance logic of
`set_open_payment_requests_to_references`
---
.../doctype/payment_entry/payment_entry.py | 114 ++++++++++--------
1 file changed, 61 insertions(+), 53 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index d6c8e322ca19..7ac550c37700 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2680,85 +2680,93 @@ def get_open_payment_requests_for_references(references=None):
return reference_payment_requests
-# todo-abdeali: make it more efficient and less complex
def set_open_payment_requests_to_references(references=None):
if not references:
return
- reference_payment_requests = get_open_payment_requests_for_references(references)
+ # get all unpaid payment requests for the references
+ all_references_payment_requests = get_open_payment_requests_for_references(references)
- if not reference_payment_requests:
+ if not all_references_payment_requests:
return
- row_idx = 0
+ # to manage new rows
+ row_number = 1
+ MOVE_TO_NEXT_ROW = 1
+ TO_SKIP_NEW_ROW = 2
- while row_idx < len(references):
- row = references[row_idx]
- key = (row.reference_doctype, row.reference_name)
+ while row_number <= len(references):
+ row = references[row_number - 1]
+ reference_key = (row.reference_doctype, row.reference_name)
+
+ # update the idx to maintain the order
+ row.idx = row_number
- # ? can make it efficient if only one transaction is there but have multiple row because of terms
- payment_requests = reference_payment_requests.get(key)
+ # unpaid payment requests for the reference
+ reference_payment_requests = all_references_payment_requests.get(reference_key)
- if not payment_requests:
- row_idx += 1
+ if not reference_payment_requests:
+ row_number += MOVE_TO_NEXT_ROW # to move to next reference row
continue
- payment_request, outstanding_amount = next(iter(payment_requests.items()))
+ # get the first payment request and its outstanding amount
+ payment_request, outstanding_amount = next(iter(reference_payment_requests.items()))
allocated_amount = row.allocated_amount
+ # allocate the payment request to the reference
+ row.payment_request = payment_request
+
if outstanding_amount == allocated_amount:
- row.payment_request = payment_request
- del reference_payment_requests[key][payment_request]
- row_idx += 1
- elif outstanding_amount > allocated_amount:
- row.payment_request = payment_request
+ del reference_payment_requests[payment_request]
+ row_number += MOVE_TO_NEXT_ROW
- reference_payment_requests[key][payment_request] -= allocated_amount
- row_idx += 1
+ elif outstanding_amount > allocated_amount:
+ # reduce the outstanding amount of the payment request
+ reference_payment_requests[payment_request] -= allocated_amount
+ row_number += MOVE_TO_NEXT_ROW
- elif outstanding_amount < allocated_amount:
- row.payment_request = payment_request
+ else:
+ # split the reference row to allocate the remaining amount
+ del reference_payment_requests[payment_request]
row.allocated_amount = outstanding_amount
-
- del reference_payment_requests[key][payment_request]
allocated_amount -= outstanding_amount
+ # set the remaining amount to the next row
while allocated_amount:
- payment_request, outstanding_amount = next(iter(payment_requests.items()), (None, None))
-
+ # create a new row for the remaining amount
new_row = frappe.copy_doc(row)
- new_row.allocated_amount = allocated_amount
- references.insert(row_idx + 1, new_row)
+ references.insert(row_number, new_row)
+
+ # get the next payment request and its outstanding amount
+ payment_request, outstanding_amount = next(
+ iter(reference_payment_requests.items()), (None, None)
+ )
+
+ # update new row
+ new_row.idx = row_number + 1
+ new_row.payment_request = payment_request
+ new_row.allocated_amount = min(
+ outstanding_amount if outstanding_amount else allocated_amount, allocated_amount
+ )
if not payment_request or not outstanding_amount:
- new_row.allocated_amount = allocated_amount
- new_row.payment_request = None
- row_idx += 2
+ row_number += TO_SKIP_NEW_ROW
+ break
+
+ elif outstanding_amount == allocated_amount:
+ del reference_payment_requests[payment_request]
+ row_number += TO_SKIP_NEW_ROW
+ break
+
+ elif outstanding_amount > allocated_amount:
+ reference_payment_requests[payment_request] -= allocated_amount
+ row_number += TO_SKIP_NEW_ROW
break
+
else:
- new_row.payment_request = payment_request
-
- if outstanding_amount == allocated_amount:
- new_row.allocated_amount = allocated_amount
- del reference_payment_requests[key][payment_request]
- row_idx += 2
-
- break
- elif outstanding_amount > allocated_amount:
- new_row.allocated_amount = allocated_amount
- reference_payment_requests[key][payment_request] -= allocated_amount
- row_idx += 2
- break
- elif outstanding_amount < allocated_amount:
- allocated_amount -= outstanding_amount
- new_row.allocated_amount = outstanding_amount
-
- del reference_payment_requests[key][payment_request]
- row_idx += 1
-
- # set new idx to all refs
- for idx, ref in enumerate(references, start=1):
- ref.idx = idx
+ allocated_amount -= outstanding_amount
+ del reference_payment_requests[payment_request]
+ row_number += MOVE_TO_NEXT_ROW
def update_accounting_dimensions(pe, doc):
From da36e3102c23133dd5775e5028ace063d8e60816 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 20 Aug 2024 15:41:12 +0530
Subject: [PATCH 16/60] fix: added one optional arg
`created_from_payment_request`
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++---
erpnext/accounts/doctype/payment_request/payment_request.py | 1 +
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7ac550c37700..c6b1b93a5fb1 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2473,7 +2473,6 @@ def get_reference_details(
return res
-# todo-abdeali: add one more parameter that if it created from `payment_request`
@frappe.whitelist()
def get_payment_entry(
dt,
@@ -2485,6 +2484,7 @@ def get_payment_entry(
payment_type=None,
reference_date=None,
ignore_permissions=False,
+ created_from_payment_request=False,
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -2634,8 +2634,8 @@ def get_payment_entry(
pe.set_difference_amount()
- # only if allocated_amount is set
- set_open_payment_requests_to_references(pe.references)
+ if not created_from_payment_request:
+ set_open_payment_requests_to_references(pe.references)
return pe
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 776dbe45e747..49f965a7fd02 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -310,6 +310,7 @@ def create_payment_entry(self, submit=True):
party_amount=party_amount,
bank_account=self.payment_account,
bank_amount=bank_amount,
+ created_from_payment_request=True,
)
payment_entry.update(
From baa11d1e9e531f1d7c1e6553eff7e5173d5b5b3a Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 20 Aug 2024 17:18:07 +0530
Subject: [PATCH 17/60] fix: handle multiple allocation of PR at PE's reference
---
erpnext/accounts/doctype/payment_request/payment_request.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 49f965a7fd02..80cc2fa46879 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -671,6 +671,9 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
else payment_request["outstanding_amount"] - ref.allocated_amount
)
+ # to handle same payment request for the multiple allocations
+ payment_request["outstanding_amount"] = new_outstanding_amount
+
if not cancel and new_outstanding_amount < 0:
frappe.throw(
msg=_(
From 53f30ec4d8a007aeab8bc05652825305413f8e8b Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 20 Aug 2024 19:04:28 +0530
Subject: [PATCH 18/60] fix: logic for PR if outstanding docs fetch
---
.../doctype/payment_entry/payment_entry.js | 19 +-----
.../doctype/payment_entry/payment_entry.py | 62 +------------------
2 files changed, 5 insertions(+), 76 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 616cd2e3e78d..761e6afc4a8d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1104,8 +1104,6 @@ frappe.ui.form.on("Payment Entry", {
}
}
- let set_matched_payment_requests = false;
-
$.each(frm.doc.references || [], function (i, row) {
if (frappe.flags.allocate_payment_amount == 0) {
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
@@ -1114,8 +1112,6 @@ frappe.ui.form.on("Payment Entry", {
frappe.flags.allocate_payment_amount != 0 &&
(!row.allocated_amount || paid_amount_change)
) {
- let previous_allocated_amount = row.allocated_amount;
-
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
row.allocated_amount =
row.outstanding_amount >= allocated_positive_outstanding
@@ -1129,16 +1125,12 @@ frappe.ui.form.on("Payment Entry", {
: row.outstanding_amount;
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
}
-
- if (!row.payment_request && row.allocated_amount > previous_allocated_amount) {
- set_matched_payment_requests = true;
- }
}
});
frm.refresh_fields();
frm.events.set_total_allocated_amount(frm);
- if (set_matched_payment_requests) frm.call("set_matched_payment_requests");
+ if (frappe.flags.allocate_payment_amount) frm.call("set_payment_requests_to_references");
},
set_total_allocated_amount: function (frm) {
@@ -1727,15 +1719,8 @@ frappe.ui.form.on("Payment Entry Reference", {
}
},
- allocated_amount: function (frm, cdt, cdn) {
+ allocated_amount: function (frm) {
frm.events.set_total_allocated_amount(frm);
-
- const row = frappe.get_doc(cdt, cdn);
-
- if (row.payment_request || !(row.reference_doctype && row.reference_name && row.allocated_amount))
- return;
-
- frm.call("set_matched_payment_request", { row_idx: row.idx });
},
references_remove: function (frm) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c6b1b93a5fb1..192c7ac58912 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1719,6 +1719,7 @@ def get_current_tax_fraction(self, tax):
return current_tax_fraction
+ # todo-abdeali: needs changes if PR already use for ref and one ref row have no PR then do not show this!!
def check_references_for_unset_payment_request(self):
if not self.references:
return
@@ -1756,65 +1757,8 @@ def check_references_for_unset_payment_request(self):
)
@frappe.whitelist()
- def set_matched_payment_requests(self):
- if not self.references:
- return
-
- matched_payment_requests = get_matched_payment_requests(self.references)
-
- matched_count = 0
-
- for row in self.references:
- if (
- row.payment_request
- or not row.reference_doctype
- or not row.reference_name
- or not row.allocated_amount
- ):
- continue
-
- row.payment_request = matched_payment_requests.get(
- (row.reference_doctype, row.reference_name, row.allocated_amount)
- )
-
- if row.payment_request:
- matched_count += 1
-
- if not matched_count:
- return
-
- frappe.msgprint(
- msg=_("Setting {0} matched Payment Request(s)").format(matched_count),
- alert=True,
- )
-
- @frappe.whitelist()
- def set_matched_payment_request(self, row_idx):
- row = next((row for row in self.references if row.idx == row_idx), None)
-
- if not row:
- frappe.throw(_("Row #{0} not found").format(row_idx), title=_("Row Not Found"))
-
- # if payment entry already set then do not set it again
- if (
- row.payment_request
- or not row.reference_doctype
- or not row.reference_name
- or not row.allocated_amount
- ):
- return
-
- matched_pr = get_matched_payment_requests([row])
-
- if not matched_pr:
- return
-
- row.payment_request = matched_pr[(row.reference_doctype, row.reference_name, row.allocated_amount)]
-
- frappe.msgprint(
- msg=_("Setting matched Payment Request"),
- alert=True,
- )
+ def set_payment_requests_to_references(self):
+ set_open_payment_requests_to_references(self.references)
def get_matched_payment_requests(references=None):
From 77ba62a6c3cf7c681802b0104d9a47a988828c37 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 21 Aug 2024 15:57:49 +0530
Subject: [PATCH 19/60] fix: formatted Link field for `Payment Request` for
PE's references
---
.../doctype/payment_entry/payment_entry.js | 3 +-
.../doctype/payment_entry/payment_entry.py | 3 ++
.../payment_request/payment_request.py | 42 +++++++++++++++++--
3 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 761e6afc4a8d..4df93ba0b77f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -169,9 +169,8 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
const row = frappe.get_doc(cdt, cdn);
return {
+ query: "erpnext.accounts.doctype.payment_request.payment_request.get_open_payment_requests",
filters: {
- docstatus: 1,
- status: ["!=", "Paid"],
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
},
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 192c7ac58912..2d93353a3425 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -336,6 +336,9 @@ def validate_allocated_amount_as_per_payment_request(self):
get_outstanding_amount_of_payment_entry_references as get_outstanding_amounts,
)
+ if not self.references:
+ return
+
outstanding_amounts = get_outstanding_amounts(self.references)
for ref in self.references:
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 80cc2fa46879..b85761927233 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -698,7 +698,10 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
)
-def get_outstanding_amount_of_payment_entry_references(references: list) -> dict:
+def get_outstanding_amount_of_payment_entry_references(references):
+ if not references:
+ return {}
+
payment_requests = get_referenced_payment_requests(references)
return dict(
@@ -711,8 +714,11 @@ def get_outstanding_amount_of_payment_entry_references(references: list) -> dict
)
-def get_referenced_payment_requests(references: list) -> set:
- return {row.payment_request for row in references if row.payment_request}
+def get_referenced_payment_requests(references):
+ if not references:
+ return ()
+
+ return {row["payment_request"] for row in references if row["payment_request"]}
def get_dummy_message(doc):
@@ -823,3 +829,33 @@ def get_paid_amount_against_order(dt, dn):
)
)
).run()[0][0] or 0
+
+
+@frappe.whitelist()
+def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filters):
+ reference_doctype = filters.get("reference_doctype")
+ reference_name = filters.get("reference_doctype")
+
+ if not reference_doctype or not reference_name:
+ return []
+
+ open_payment_requests = frappe.get_all(
+ "Payment Request",
+ filters={
+ "reference_doctype": filters["reference_doctype"],
+ "reference_name": filters["reference_name"],
+ "status": ["!=", "Paid"],
+ "docstatus": 1,
+ },
+ fields=["name", "grand_total", "outstanding_amount"],
+ order_by="transaction_date ASC,creation ASC",
+ )
+
+ return [
+ (
+ pr.name,
+ _("Grand Total: {0}").format(pr.grand_total),
+ _("Outstanding Amount: {0}").format(pr.outstanding_amount),
+ )
+ for pr in open_payment_requests
+ ]
From bb02ffbaacb5ea59d8e1aaec31b022f768d111b7 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 21 Aug 2024 16:33:14 +0530
Subject: [PATCH 20/60] fix: replace `get_all()` with `get_list()` for getting
Payment Request for Link field
---
erpnext/accounts/doctype/payment_request/payment_request.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index b85761927233..b301a80aec47 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -833,13 +833,14 @@ def get_paid_amount_against_order(dt, dn):
@frappe.whitelist()
def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filters):
+ # permission checks in `get_list()`
reference_doctype = filters.get("reference_doctype")
reference_name = filters.get("reference_doctype")
if not reference_doctype or not reference_name:
return []
- open_payment_requests = frappe.get_all(
+ open_payment_requests = frappe.get_list(
"Payment Request",
filters={
"reference_doctype": filters["reference_doctype"],
From f024d6670c434eb51e5d88d0719f285c0303c6a9 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 21 Aug 2024 16:33:44 +0530
Subject: [PATCH 21/60] fix: replace `get_all()` with `get_list()` for getting
Payment Request for Link field
---
erpnext/accounts/doctype/payment_request/payment_request.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index b301a80aec47..a194b657563e 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -833,7 +833,7 @@ def get_paid_amount_against_order(dt, dn):
@frappe.whitelist()
def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filters):
- # permission checks in `get_list()`
+ # permission checks in `get_list()`
reference_doctype = filters.get("reference_doctype")
reference_name = filters.get("reference_doctype")
From 70ec3f6aba4b5171fa483fbcb16304cd7ef84ab9 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 21 Aug 2024 16:45:45 +0530
Subject: [PATCH 22/60] chore: format `payment_entry.js` file
---
.../doctype/payment_entry/payment_entry.js | 64 +++++++++----------
1 file changed, 32 insertions(+), 32 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 4df93ba0b77f..03aeab606aab 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -195,6 +195,7 @@ frappe.ui.form.on("Payment Entry", {
};
});
},
+
refresh: function (frm) {
erpnext.hide_company(frm);
frm.events.hide_unhide_fields(frm);
@@ -1676,6 +1677,37 @@ frappe.ui.form.on("Payment Entry", {
return current_tax_amount;
},
+
+ cost_center: function (frm) {
+ if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
+ return frappe.call({
+ method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
+ args: {
+ company: frm.doc.company,
+ date: frm.doc.posting_date,
+ paid_from: frm.doc.paid_from,
+ paid_to: frm.doc.paid_to,
+ ptype: frm.doc.party_type,
+ pty: frm.doc.party,
+ cost_center: frm.doc.cost_center,
+ },
+ callback: function (r, rt) {
+ if (r.message) {
+ frappe.run_serially([
+ () => {
+ frm.set_value(
+ "paid_from_account_balance",
+ r.message.paid_from_account_balance
+ );
+ frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
+ frm.set_value("party_balance", r.message.party_balance);
+ },
+ ]);
+ }
+ },
+ });
+ }
+ },
});
frappe.ui.form.on("Payment Entry Reference", {
@@ -1768,35 +1800,3 @@ frappe.ui.form.on("Payment Entry Deduction", {
frm.events.set_unallocated_amount(frm);
},
});
-frappe.ui.form.on("Payment Entry", {
- cost_center: function (frm) {
- if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
- return frappe.call({
- method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
- args: {
- company: frm.doc.company,
- date: frm.doc.posting_date,
- paid_from: frm.doc.paid_from,
- paid_to: frm.doc.paid_to,
- ptype: frm.doc.party_type,
- pty: frm.doc.party,
- cost_center: frm.doc.cost_center,
- },
- callback: function (r, rt) {
- if (r.message) {
- frappe.run_serially([
- () => {
- frm.set_value(
- "paid_from_account_balance",
- r.message.paid_from_account_balance
- );
- frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
- frm.set_value("party_balance", r.message.party_balance);
- },
- ]);
- }
- },
- });
- }
- },
-});
From cc5db67362b398480503ac753769ce0d3a29fd74 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 21 Aug 2024 17:40:06 +0530
Subject: [PATCH 23/60] style: Show preview popup of `Payment Request`
---
.../accounts/doctype/payment_request/payment_request.json | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 50cc12aa4ea2..e08a0c8ffb7b 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -70,6 +70,7 @@
{
"fieldname": "transaction_date",
"fieldtype": "Date",
+ "in_preview": 1,
"label": "Transaction Date"
},
{
@@ -408,6 +409,7 @@
"depends_on": "eval: doc.docstatus === 1",
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
+ "in_preview": 1,
"label": "Outstanding Amount",
"non_negative": 1,
"read_only": 1
@@ -417,7 +419,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-07-23 19:02:07.754296",
+ "modified": "2024-08-21 17:17:16.584404",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -452,6 +454,7 @@
"write": 1
}
],
+ "show_preview_popup": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": []
From 3270991d3e10eb210e147bfbb20480522661592d Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 22 Aug 2024 12:32:10 +0530
Subject: [PATCH 24/60] fix: remove minor bug
---
erpnext/accounts/doctype/payment_request/payment_request.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index a194b657563e..c4d291cf91cf 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -665,7 +665,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
payment_request = payment_requests[ref.payment_request]
# update outstanding amount
- new_outstanding_amount = (
+ new_outstanding_amount = flt(
payment_request["outstanding_amount"] + ref.allocated_amount
if cancel
else payment_request["outstanding_amount"] - ref.allocated_amount
@@ -718,7 +718,7 @@ def get_referenced_payment_requests(references):
if not references:
return ()
- return {row["payment_request"] for row in references if row["payment_request"]}
+ return {row.payment_request for row in references if row.payment_request}
def get_dummy_message(doc):
From dfefdffa9b2a80003d9376370c283e797b344ddc Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 22 Aug 2024 18:28:13 +0530
Subject: [PATCH 25/60] fix: add virtual field for Payment Term and Request
`outstanding_amount` in PE's reference
---
.../doctype/payment_entry/payment_entry.py | 4 +++-
.../payment_entry_reference.json | 22 +++++++++++++++++--
.../payment_entry_reference.py | 2 ++
3 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 2d93353a3425..16c1c1962a40 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2660,8 +2660,9 @@ def set_open_payment_requests_to_references(references=None):
payment_request, outstanding_amount = next(iter(reference_payment_requests.items()))
allocated_amount = row.allocated_amount
- # allocate the payment request to the reference
+ # allocate the payment request to the reference and PR's outstanding amount
row.payment_request = payment_request
+ row.payment_request_outstanding = outstanding_amount
if outstanding_amount == allocated_amount:
del reference_payment_requests[payment_request]
@@ -2692,6 +2693,7 @@ def set_open_payment_requests_to_references(references=None):
# update new row
new_row.idx = row_number + 1
new_row.payment_request = payment_request
+ new_row.payment_request_outstanding = outstanding_amount
new_row.allocated_amount = min(
outstanding_amount if outstanding_amount else allocated_amount, allocated_amount
)
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 7fce86c99d9a..75d5a79b97b7 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -10,6 +10,7 @@
"due_date",
"bill_no",
"payment_term",
+ "payment_term_outstanding",
"account_type",
"payment_type",
"column_break_4",
@@ -19,7 +20,8 @@
"exchange_rate",
"exchange_gain_loss",
"account",
- "payment_request"
+ "payment_request",
+ "payment_request_outstanding"
],
"fields": [
{
@@ -127,12 +129,28 @@
"fieldtype": "Link",
"label": "Payment Request",
"options": "Payment Request"
+ },
+ {
+ "depends_on": "eval: doc.payment_term",
+ "fieldname": "payment_term_outstanding",
+ "fieldtype": "Float",
+ "is_virtual": 1,
+ "label": "Payment Term Outstanding",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.payment_request",
+ "fieldname": "payment_request_outstanding",
+ "fieldtype": "Float",
+ "is_virtual": 1,
+ "label": "Payment Request Outstanding",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-07-20 17:57:32.866780",
+ "modified": "2024-08-22 18:16:42.138982",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index 68d819d08408..fbd5571531b2 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -26,7 +26,9 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_request: DF.Link | None
+ payment_request_outstanding: DF.Float
payment_term: DF.Link | None
+ payment_term_outstanding: DF.Float
payment_type: DF.Data | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
From d4b36015c076a711daae8e0aed87f72914d71e6b Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 23 Aug 2024 12:46:57 +0530
Subject: [PATCH 26/60] fix: get outstanding amount in PE's reference on
realtime
---
.../doctype/payment_entry/payment_entry.js | 8 +++++++
.../payment_entry_reference.json | 2 +-
.../payment_entry_reference.py | 24 +++++++++++++++++--
3 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 03aeab606aab..1ceeccbcb78f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -194,6 +194,14 @@ frappe.ui.form.on("Payment Entry", {
},
};
});
+
+ // todo: fetch payment term outstanding amount also
+ frm.add_fetch(
+ "payment_request",
+ "outstanding_amount",
+ "payment_request_outstanding",
+ "Payment Entry Reference"
+ );
},
refresh: function (frm) {
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 75d5a79b97b7..0506517a70c0 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -150,7 +150,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-08-22 18:16:42.138982",
+ "modified": "2024-08-23 12:35:40.525380",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index fbd5571531b2..3afff4c8d25b 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -1,7 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-
+import frappe
from frappe.model.document import Document
@@ -35,4 +35,24 @@ class PaymentEntryReference(Document):
total_amount: DF.Float
# end: auto-generated types
- pass
+ @property
+ def payment_term_outstanding(self):
+ if not self.payment_term:
+ return 0
+
+ return frappe.db.get_value(
+ "Payment Schedule",
+ {
+ "payment_term": self.payment_term,
+ "parenttype": self.reference_doctype,
+ "parent": self.reference_name,
+ },
+ "outstanding",
+ )
+
+ @property
+ def payment_request_outstanding(self):
+ if not self.payment_request:
+ return 0
+
+ return frappe.db.get_value("Payment Request", self.payment_request, "outstanding_amount")
From 9e092bb62234397ac70ba08d0fc2f3f6856a61ee Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Sat, 24 Aug 2024 17:23:53 +0530
Subject: [PATCH 27/60] fix: move allocation of allocated_amount to server side
(no change)
---
.../doctype/payment_entry/payment_entry.js | 94 ++------------
.../doctype/payment_entry/payment_entry.py | 119 +++++++++++++++++-
2 files changed, 122 insertions(+), 91 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 1ceeccbcb78f..96c72bf820ec 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -228,6 +228,7 @@ frappe.ui.form.on("Payment Entry", {
);
}
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
+ frappe.flags.allocate_payment_amount = true;
},
validate_company: (frm) => {
@@ -1050,95 +1051,16 @@ frappe.ui.form.on("Payment Entry", {
return ["Sales Invoice", "Purchase Invoice"];
},
- allocate_party_amount_against_ref_docs: function (frm, paid_amount, paid_amount_change) {
- var total_positive_outstanding_including_order = 0;
- var total_negative_outstanding = 0;
- var total_deductions = frappe.utils.sum(
- $.map(frm.doc.deductions || [], function (d) {
- return flt(d.amount);
- })
- );
-
- paid_amount -= total_deductions;
-
- $.each(frm.doc.references || [], function (i, row) {
- if (flt(row.outstanding_amount) > 0)
- total_positive_outstanding_including_order += flt(row.outstanding_amount);
- else total_negative_outstanding += Math.abs(flt(row.outstanding_amount));
- });
-
- var allocated_negative_outstanding = 0;
- if (
- (frm.doc.payment_type == "Receive" && frm.doc.party_type == "Customer") ||
- (frm.doc.payment_type == "Pay" && frm.doc.party_type == "Supplier") ||
- (frm.doc.payment_type == "Pay" && frm.doc.party_type == "Employee")
- ) {
- if (total_positive_outstanding_including_order > paid_amount) {
- var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
- allocated_negative_outstanding =
- total_negative_outstanding < remaining_outstanding
- ? total_negative_outstanding
- : remaining_outstanding;
- }
-
- var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
- } else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
- total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
- if (paid_amount > total_negative_outstanding) {
- if (total_negative_outstanding == 0) {
- frappe.msgprint(
- __("Cannot {0} {1} {2} without any negative outstanding invoice", [
- frm.doc.payment_type,
- frm.doc.party_type == "Customer" ? "to" : "from",
- frm.doc.party_type,
- ])
- );
- return false;
- } else {
- frappe.msgprint(
- __("Paid Amount cannot be greater than total negative outstanding amount {0}", [
- total_negative_outstanding,
- ])
- );
- return false;
- }
- } else {
- allocated_positive_outstanding = total_negative_outstanding - paid_amount;
- allocated_negative_outstanding =
- paid_amount +
- (total_positive_outstanding_including_order < allocated_positive_outstanding
- ? total_positive_outstanding_including_order
- : allocated_positive_outstanding);
- }
- }
-
- $.each(frm.doc.references || [], function (i, row) {
- if (frappe.flags.allocate_payment_amount == 0) {
- //If allocate payment amount checkbox is unchecked, set zero to allocate amount
- row.allocated_amount = 0;
- } else if (
- frappe.flags.allocate_payment_amount != 0 &&
- (!row.allocated_amount || paid_amount_change)
- ) {
- if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
- row.allocated_amount =
- row.outstanding_amount >= allocated_positive_outstanding
- ? allocated_positive_outstanding
- : row.outstanding_amount;
- allocated_positive_outstanding -= flt(row.allocated_amount);
- } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
- row.allocated_amount =
- Math.abs(row.outstanding_amount) >= allocated_negative_outstanding
- ? -1 * allocated_negative_outstanding
- : row.outstanding_amount;
- allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
- }
- }
+ allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
+ await frm.call("allocate_party_amount_against_ref_docs", {
+ paid_amount: paid_amount,
+ paid_amount_change: paid_amount_change ? paid_amount_change : 0,
+ allocate_payment_amount: frappe.flags.allocate_payment_amount
+ ? frappe.flags.allocate_payment_amount
+ : 0,
});
- frm.refresh_fields();
frm.events.set_total_allocated_amount(frm);
- if (frappe.flags.allocate_payment_amount) frm.call("set_payment_requests_to_references");
},
set_total_allocated_amount: function (frm) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 16c1c1962a40..7c58dd86a23c 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -182,8 +182,10 @@ def validate(self):
self.set_status()
self.set_total_in_words()
+ # todo-abdeali: move to front end
def before_save(self):
- self.check_references_for_unset_payment_request()
+ pass
+ # self.check_references_for_unset_payment_request()
def on_submit(self):
if self.difference_amount:
@@ -1727,7 +1729,7 @@ def check_references_for_unset_payment_request(self):
if not self.references:
return
- matched_payment_requests = get_matched_payment_requests(
+ matched_payment_requests = get_matched_payment_request_of_references(
[row for row in self.references if not row.payment_request]
)
@@ -1760,11 +1762,91 @@ def check_references_for_unset_payment_request(self):
)
@frappe.whitelist()
- def set_payment_requests_to_references(self):
- set_open_payment_requests_to_references(self.references)
+ def allocate_party_amount_against_ref_docs(
+ self, paid_amount, paid_amount_change, allocate_payment_amount
+ ):
+ if not self.references:
+ return
+
+ # if `allocate_payment_amount` is False, then do not allocate amount
+ if not allocate_payment_amount:
+ for ref in self.references:
+ ref.allocated_amount = 0
+ return
+ # todo-abdeali: here will update table (reference) priority given to those which have PR inside it ...
+ # todo: store old data also, never should allow more amount than paid amount
+
+ # variables which will be used to calculate the allocated amount
+ total_positive_outstanding_including_order = 0
+ total_negative_outstanding = 0
+ paid_amount -= sum(flt(d.amount, self.precision("paid_amount")) for d in self.deductions)
+
+ # count total positive outstanding and total negative outstanding
+ for ref in self.references:
+ outstanding_amount = flt(ref.outstanding_amount, self.precision("paid_amount"))
+ abs_outstanding_amount = abs(outstanding_amount)
+
+ if outstanding_amount > 0:
+ total_positive_outstanding_including_order += abs_outstanding_amount
+ else:
+ total_negative_outstanding += abs_outstanding_amount
+
+ # allocation variables counting
+ allocated_negative_outstanding = 0
+ allocated_positive_outstanding = 0
+
+ if (self.payment_type == "Receive" and self.party_type == "Customer") or (
+ self.payment_type == "Pay" and self.party_type in ("Supplier", "Employee")
+ ):
+ if total_positive_outstanding_including_order > paid_amount:
+ remaining_outstanding = total_positive_outstanding_including_order - paid_amount
+ allocated_negative_outstanding = min(remaining_outstanding, total_negative_outstanding)
+ allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
+
+ elif self.party_type in ("Supplier", "Employee"):
+ if paid_amount > total_negative_outstanding:
+ if total_negative_outstanding == 0:
+ frappe.msgprint(
+ _("Cannot {0} from {2} without any negative outstanding invoice").format(
+ self.payment_type,
+ self.party_type,
+ )
+ )
+ else:
+ frappe.msgprint(
+ _("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
+ total_negative_outstanding
+ )
+ )
+
+ return
+
+ else:
+ allocated_positive_outstanding = total_negative_outstanding - paid_amount
+ allocated_negative_outstanding = paid_amount + min(
+ total_positive_outstanding_including_order, allocated_positive_outstanding
+ )
-def get_matched_payment_requests(references=None):
+ # correct data, because it it is possible PT's outstanding amount is not updated
+ payment_term_outstanding = get_payment_term_outstanding_of_references(self.references)
+
+ if not payment_term_outstanding:
+ payment_term_outstanding = {}
+
+ # ! may possible that PR is there or not and same for the PT
+ # todo: allocate amount (based on outstanding_amount + PT outstanding amount + PR outstanding amount)
+
+ for ref in self.references:
+ if ref.outstanding_amount > 0 and allocated_positive_outstanding > 0:
+ ref.allocated_amount = min(allocated_positive_outstanding, ref.outstanding_amount)
+ allocated_positive_outstanding -= flt(ref.allocated_amount)
+ elif ref.outstanding_amount < 0 and allocated_negative_outstanding > 0:
+ ref.allocated_amount = min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
+ allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
+
+
+def get_matched_payment_request_of_references(references=None):
if not references:
return
@@ -1801,6 +1883,33 @@ def get_matched_payment_requests(references=None):
return {(pr.reference_doctype, pr.reference_name, pr.outstanding_amount): pr.name for pr in matched_prs}
+def get_payment_term_outstanding_of_references(references=None):
+ if not references:
+ return
+
+ refs = {
+ (row.reference_doctype, row.reference_name, row.payment_term)
+ for row in references
+ if row.reference_doctype and row.reference_name and row.payment_term
+ }
+
+ if not refs:
+ return
+
+ PS = frappe.qb.DocType("Payment Schedule")
+
+ response = (
+ frappe.qb.from_(PS)
+ .select(PS.parenttype, PS.parent, PS.payment_term, PS.outstanding)
+ .where(Tuple(PS.parenttype, PS.parent, PS.payment_term).isin(refs))
+ ).run(as_dict=True)
+
+ if not response:
+ return
+
+ return {(row.parenttype, row.parent, row.payment_term): row.outstanding for row in response}
+
+
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
throw(
From c40419d1a3126d72f76b35641ad8ef98905b80e8 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Sat, 24 Aug 2024 18:41:12 +0530
Subject: [PATCH 28/60] fix: some minor changes to allocation
---
.../doctype/payment_entry/payment_entry.js | 20 +++++++++++++------
.../doctype/payment_entry/payment_entry.py | 15 +++++++-------
2 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 96c72bf820ec..eef3569c594d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -796,7 +796,7 @@ frappe.ui.form.on("Payment Entry", {
);
if (frm.doc.payment_type == "Pay")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1, false);
else frm.events.set_unallocated_amount(frm);
frm.set_paid_amount_based_on_received_amount = false;
@@ -817,7 +817,7 @@ frappe.ui.form.on("Payment Entry", {
}
if (frm.doc.payment_type == "Receive")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1, false);
else frm.events.set_unallocated_amount(frm);
},
@@ -1037,7 +1037,9 @@ frappe.ui.form.on("Payment Entry", {
frm.events.allocate_party_amount_against_ref_docs(
frm,
- frm.doc.payment_type == "Receive" ? frm.doc.paid_amount : frm.doc.received_amount
+ frm.doc.payment_type == "Receive" ? frm.doc.paid_amount : frm.doc.received_amount,
+ 0,
+ true
);
},
});
@@ -1051,13 +1053,19 @@ frappe.ui.form.on("Payment Entry", {
return ["Sales Invoice", "Purchase Invoice"];
},
- allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
+ allocate_party_amount_against_ref_docs: async function (
+ frm,
+ paid_amount,
+ paid_amount_change,
+ allocate_payment_request
+ ) {
await frm.call("allocate_party_amount_against_ref_docs", {
paid_amount: paid_amount,
- paid_amount_change: paid_amount_change ? paid_amount_change : 0,
+ paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount
? frappe.flags.allocate_payment_amount
- : 0,
+ : false,
+ allocate_payment_request: allocate_payment_request,
});
frm.events.set_total_allocated_amount(frm);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7c58dd86a23c..73f75d58d2ee 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1763,7 +1763,7 @@ def check_references_for_unset_payment_request(self):
@frappe.whitelist()
def allocate_party_amount_against_ref_docs(
- self, paid_amount, paid_amount_change, allocate_payment_amount
+ self, paid_amount, paid_amount_change, allocate_payment_amount, allocate_payment_request
):
if not self.references:
return
@@ -1828,11 +1828,11 @@ def allocate_party_amount_against_ref_docs(
total_positive_outstanding_including_order, allocated_positive_outstanding
)
- # correct data, because it it is possible PT's outstanding amount is not updated
- payment_term_outstanding = get_payment_term_outstanding_of_references(self.references)
+ payment_term_outstanding = {}
- if not payment_term_outstanding:
- payment_term_outstanding = {}
+ if not allocate_payment_request:
+ # correct data, because it it is possible PT's outstanding amount is not updated
+ payment_term_outstanding = get_payment_term_outstanding_of_references(self.references)
# ! may possible that PR is there or not and same for the PT
# todo: allocate amount (based on outstanding_amount + PT outstanding amount + PR outstanding amount)
@@ -1845,6 +1845,9 @@ def allocate_party_amount_against_ref_docs(
ref.allocated_amount = min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
+ if allocate_payment_request:
+ set_open_payment_requests_to_references(self.references)
+
def get_matched_payment_request_of_references(references=None):
if not references:
@@ -2771,7 +2774,6 @@ def set_open_payment_requests_to_references(references=None):
# allocate the payment request to the reference and PR's outstanding amount
row.payment_request = payment_request
- row.payment_request_outstanding = outstanding_amount
if outstanding_amount == allocated_amount:
del reference_payment_requests[payment_request]
@@ -2802,7 +2804,6 @@ def set_open_payment_requests_to_references(references=None):
# update new row
new_row.idx = row_number + 1
new_row.payment_request = payment_request
- new_row.payment_request_outstanding = outstanding_amount
new_row.allocated_amount = min(
outstanding_amount if outstanding_amount else allocated_amount, allocated_amount
)
From a7aaff133445a464ef76c2397e7baa4950982c6d Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 26 Aug 2024 11:46:11 +0530
Subject: [PATCH 29/60] fix: Split `Payment Request` if PE is created from PR
and there are `Payment Terms`
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 5 +----
erpnext/accounts/doctype/payment_request/payment_request.py | 5 +++--
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 73f75d58d2ee..3ccf086f4256 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1768,7 +1768,6 @@ def allocate_party_amount_against_ref_docs(
if not self.references:
return
- # if `allocate_payment_amount` is False, then do not allocate amount
if not allocate_payment_amount:
for ref in self.references:
ref.allocated_amount = 0
@@ -1776,12 +1775,10 @@ def allocate_party_amount_against_ref_docs(
# todo-abdeali: here will update table (reference) priority given to those which have PR inside it ...
# todo: store old data also, never should allow more amount than paid amount
- # variables which will be used to calculate the allocated amount
total_positive_outstanding_including_order = 0
total_negative_outstanding = 0
paid_amount -= sum(flt(d.amount, self.precision("paid_amount")) for d in self.deductions)
- # count total positive outstanding and total negative outstanding
for ref in self.references:
outstanding_amount = flt(ref.outstanding_amount, self.precision("paid_amount"))
abs_outstanding_amount = abs(outstanding_amount)
@@ -1832,7 +1829,7 @@ def allocate_party_amount_against_ref_docs(
if not allocate_payment_request:
# correct data, because it it is possible PT's outstanding amount is not updated
- payment_term_outstanding = get_payment_term_outstanding_of_references(self.references)
+ payment_term_outstanding = get_payment_term_outstanding_of_references(self.references) or {}
# ! may possible that PR is there or not and same for the PT
# todo: allocate amount (based on outstanding_amount + PT outstanding amount + PR outstanding amount)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index c4d291cf91cf..c9c4bcce3065 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -323,8 +323,9 @@ def create_payment_entry(self, submit=True):
}
)
- # Add reference of Payment Request
- payment_entry.references[0].payment_request = self.name
+ # Update payment_request for each reference in payment_entry (Payment Term can splits the row)
+ for row in payment_entry.references:
+ row.payment_request = self.name
# Update dimensions
payment_entry.update(
From 568adefa32a25ddeb5b85036df060ad6de26b0c9 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 26 Aug 2024 12:14:11 +0530
Subject: [PATCH 30/60] fix: minor logic changes
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 3ccf086f4256..fa5c6222569f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1835,10 +1835,10 @@ def allocate_party_amount_against_ref_docs(
# todo: allocate amount (based on outstanding_amount + PT outstanding amount + PR outstanding amount)
for ref in self.references:
- if ref.outstanding_amount > 0 and allocated_positive_outstanding > 0:
+ if ref.outstanding_amount > 0 and allocated_positive_outstanding >= 0:
ref.allocated_amount = min(allocated_positive_outstanding, ref.outstanding_amount)
allocated_positive_outstanding -= flt(ref.allocated_amount)
- elif ref.outstanding_amount < 0 and allocated_negative_outstanding > 0:
+ elif ref.outstanding_amount < 0 and allocated_negative_outstanding:
ref.allocated_amount = min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
From fc340c4b51cb0a8bdbc134af2648381d6fca7cd2 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 26 Aug 2024 17:20:24 +0530
Subject: [PATCH 31/60] fix: Allocation of allocated_amount if `paid_amount` is
changes
---
.../doctype/payment_entry/payment_entry.js | 15 +--
.../doctype/payment_entry/payment_entry.py | 109 +++++++++++++++---
2 files changed, 94 insertions(+), 30 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index eef3569c594d..00c8ac6790e8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -796,7 +796,7 @@ frappe.ui.form.on("Payment Entry", {
);
if (frm.doc.payment_type == "Pay")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1, false);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, true);
else frm.events.set_unallocated_amount(frm);
frm.set_paid_amount_based_on_received_amount = false;
@@ -817,7 +817,7 @@ frappe.ui.form.on("Payment Entry", {
}
if (frm.doc.payment_type == "Receive")
- frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1, false);
+ frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, true);
else frm.events.set_unallocated_amount(frm);
},
@@ -1038,8 +1038,7 @@ frappe.ui.form.on("Payment Entry", {
frm.events.allocate_party_amount_against_ref_docs(
frm,
frm.doc.payment_type == "Receive" ? frm.doc.paid_amount : frm.doc.received_amount,
- 0,
- true
+ false
);
},
});
@@ -1053,19 +1052,13 @@ frappe.ui.form.on("Payment Entry", {
return ["Sales Invoice", "Purchase Invoice"];
},
- allocate_party_amount_against_ref_docs: async function (
- frm,
- paid_amount,
- paid_amount_change,
- allocate_payment_request
- ) {
+ allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
await frm.call("allocate_party_amount_against_ref_docs", {
paid_amount: paid_amount,
paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount
? frappe.flags.allocate_payment_amount
: false,
- allocate_payment_request: allocate_payment_request,
});
frm.events.set_total_allocated_amount(frm);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index fa5c6222569f..b4f2ae3d299f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1763,7 +1763,7 @@ def check_references_for_unset_payment_request(self):
@frappe.whitelist()
def allocate_party_amount_against_ref_docs(
- self, paid_amount, paid_amount_change, allocate_payment_amount, allocate_payment_request
+ self, paid_amount, paid_amount_change, allocate_payment_amount
):
if not self.references:
return
@@ -1772,8 +1772,6 @@ def allocate_party_amount_against_ref_docs(
for ref in self.references:
ref.allocated_amount = 0
return
- # todo-abdeali: here will update table (reference) priority given to those which have PR inside it ...
- # todo: store old data also, never should allow more amount than paid amount
total_positive_outstanding_including_order = 0
total_negative_outstanding = 0
@@ -1788,7 +1786,6 @@ def allocate_party_amount_against_ref_docs(
else:
total_negative_outstanding += abs_outstanding_amount
- # allocation variables counting
allocated_negative_outstanding = 0
allocated_positive_outstanding = 0
@@ -1825,24 +1822,61 @@ def allocate_party_amount_against_ref_docs(
total_positive_outstanding_including_order, allocated_positive_outstanding
)
- payment_term_outstanding = {}
+ if paid_amount_change:
+ # correct data, because it is possible that PT's outstanding amount is not updated
+ payment_request_outstanding = get_payment_request_outstanding_of_references(self.references) or {}
+ references_outstanding = get_payment_term_outstanding_of_references(self.references) or {}
+ references_outstanding.update(get_no_payment_terms_references_outstanding(self.references))
+ not_allocated_amounts = references_outstanding.copy()
- if not allocate_payment_request:
- # correct data, because it it is possible PT's outstanding amount is not updated
- payment_term_outstanding = get_payment_term_outstanding_of_references(self.references) or {}
+ for ref in self.references:
+ if not ref.payment_request:
+ continue
- # ! may possible that PR is there or not and same for the PT
- # todo: allocate amount (based on outstanding_amount + PT outstanding amount + PR outstanding amount)
+ # fetch outstanding_amount
+ key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
+ outstanding_amount = references_outstanding[key]
+ pr_outstanding_amount = payment_request_outstanding[ref.payment_request]
+
+ if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
+ ref.allocated_amount = min(
+ allocated_positive_outstanding, outstanding_amount, pr_outstanding_amount
+ )
+ allocated_positive_outstanding -= flt(ref.allocated_amount)
+ not_allocated_amounts[key] -= flt(ref.allocated_amount)
+ payment_request_outstanding[ref.payment_request] -= flt(ref.allocated_amount)
+ elif outstanding_amount < 0 and allocated_negative_outstanding:
+ ref.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
+ allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
+ not_allocated_amounts[key] -= abs(flt(ref.allocated_amount))
+ payment_request_outstanding[ref.payment_request] -= abs(flt(ref.allocated_amount))
+
+ for ref in self.references:
+ if ref.payment_request:
+ continue
+
+ key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
+ outstanding_amount = not_allocated_amounts[key]
+
+ if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
+ ref.allocated_amount = min(allocated_positive_outstanding, outstanding_amount)
+ allocated_positive_outstanding -= flt(ref.allocated_amount)
+ elif outstanding_amount < 0 and allocated_negative_outstanding:
+ ref.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
+ allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
+
+ else:
+ # todo: make more efficient using same variable
+ for ref in self.references:
+ if ref.outstanding_amount > 0 and allocated_positive_outstanding >= 0:
+ ref.allocated_amount = min(allocated_positive_outstanding, ref.outstanding_amount)
+ allocated_positive_outstanding -= flt(ref.allocated_amount)
+ elif ref.outstanding_amount < 0 and allocated_negative_outstanding:
+ ref.allocated_amount = (
+ min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
+ )
+ allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
- for ref in self.references:
- if ref.outstanding_amount > 0 and allocated_positive_outstanding >= 0:
- ref.allocated_amount = min(allocated_positive_outstanding, ref.outstanding_amount)
- allocated_positive_outstanding -= flt(ref.allocated_amount)
- elif ref.outstanding_amount < 0 and allocated_negative_outstanding:
- ref.allocated_amount = min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
- allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
-
- if allocate_payment_request:
set_open_payment_requests_to_references(self.references)
@@ -1910,6 +1944,43 @@ def get_payment_term_outstanding_of_references(references=None):
return {(row.parenttype, row.parent, row.payment_term): row.outstanding for row in response}
+def get_payment_request_outstanding_of_references(references=None):
+ if not references:
+ return
+
+ prs = {row.payment_request for row in references if row.payment_request}
+
+ if not prs:
+ return
+
+ PR = frappe.qb.DocType("Payment Request")
+
+ response = (frappe.qb.from_(PR).select(PR.name, PR.outstanding_amount).where(PR.name.isin(prs))).run()
+
+ if not response:
+ return
+
+ return dict(response)
+
+
+def get_no_payment_terms_references_outstanding(references):
+ if not references:
+ return
+
+ outstanding_amounts = {}
+
+ for ref in references:
+ if ref.payment_term:
+ continue
+
+ key = (ref.reference_doctype, ref.reference_name, None)
+
+ if key not in outstanding_amounts:
+ outstanding_amounts[key] = ref.outstanding_amount
+
+ return outstanding_amounts
+
+
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
throw(
From ac51bdff06e653601897ea87978a5377f79c5584 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 27 Aug 2024 00:29:51 +0530
Subject: [PATCH 32/60] fix: improve logic of allocation
---
.../doctype/payment_entry/payment_entry.py | 168 +++++++++++-------
1 file changed, 106 insertions(+), 62 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index b4f2ae3d299f..c4a97be08479 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1773,22 +1773,25 @@ def allocate_party_amount_against_ref_docs(
ref.allocated_amount = 0
return
+ # calculating outstanding amounts
total_positive_outstanding_including_order = 0
total_negative_outstanding = 0
paid_amount -= sum(flt(d.amount, self.precision("paid_amount")) for d in self.deductions)
for ref in self.references:
- outstanding_amount = flt(ref.outstanding_amount, self.precision("paid_amount"))
- abs_outstanding_amount = abs(outstanding_amount)
+ reference_outstanding_amount = flt(ref.outstanding_amount, self.precision("paid_amount"))
+ abs_outstanding_amount = abs(reference_outstanding_amount)
- if outstanding_amount > 0:
+ if reference_outstanding_amount > 0:
total_positive_outstanding_including_order += abs_outstanding_amount
else:
total_negative_outstanding += abs_outstanding_amount
+ # calculating allocated outstanding amounts
allocated_negative_outstanding = 0
allocated_positive_outstanding = 0
+ # checking party type and payment type
if (self.payment_type == "Receive" and self.party_type == "Customer") or (
self.payment_type == "Pay" and self.party_type in ("Supplier", "Employee")
):
@@ -1822,62 +1825,93 @@ def allocate_party_amount_against_ref_docs(
total_positive_outstanding_including_order, allocated_positive_outstanding
)
- if paid_amount_change:
- # correct data, because it is possible that PT's outstanding amount is not updated
- payment_request_outstanding = get_payment_request_outstanding_of_references(self.references) or {}
- references_outstanding = get_payment_term_outstanding_of_references(self.references) or {}
- references_outstanding.update(get_no_payment_terms_references_outstanding(self.references))
- not_allocated_amounts = references_outstanding.copy()
+ # inner function to set `allocated_amount` to those row which have no PR
+ def _allocation_to_unset_pr_row(
+ row, outstanding_amount, allocated_positive_outstanding, allocated_negative_outstanding
+ ):
+ if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
+ row.allocated_amount = min(allocated_positive_outstanding, outstanding_amount)
+ allocated_positive_outstanding -= flt(row.allocated_amount)
+ elif outstanding_amount < 0 and allocated_negative_outstanding:
+ row.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
+ allocated_negative_outstanding -= abs(flt(row.allocated_amount))
+ return allocated_positive_outstanding, allocated_negative_outstanding
+
+ # allocate amount based on `paid_amount` is changed or not
+ if not paid_amount_change:
+ for ref in self.references:
+ allocated_positive_outstanding, allocated_negative_outstanding = _allocation_to_unset_pr_row(
+ ref,
+ ref.outstanding_amount,
+ allocated_positive_outstanding,
+ allocated_negative_outstanding,
+ )
+
+ set_open_payment_requests_to_references(self.references)
+
+ else:
+ payment_request_outstanding_amounts = (
+ get_payment_request_outstanding_of_references(self.references) or {}
+ )
+ references_outstanding_amounts = get_reference_outstanding_amounts(self.references) or {}
+ remaining_references_allocated_amounts = references_outstanding_amounts.copy()
+ # Re allocate amount to those references which have PR set (Higher priority)
for ref in self.references:
if not ref.payment_request:
continue
- # fetch outstanding_amount
+ # fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
- outstanding_amount = references_outstanding[key]
- pr_outstanding_amount = payment_request_outstanding[ref.payment_request]
+ reference_outstanding_amount = references_outstanding_amounts[key]
+ pr_outstanding_amount = payment_request_outstanding_amounts[ref.payment_request]
+
+ if reference_outstanding_amount > 0 and allocated_positive_outstanding >= 0:
+ # allocate amount according to outstanding amounts
+ outstanding_amounts = (
+ allocated_positive_outstanding,
+ reference_outstanding_amount,
+ pr_outstanding_amount,
+ )
+
+ ref.allocated_amount = min(outstanding_amounts)
- if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
- ref.allocated_amount = min(
- allocated_positive_outstanding, outstanding_amount, pr_outstanding_amount
+ # update amounts to track allocation
+ allocated_amount = flt(ref.allocated_amount)
+ allocated_positive_outstanding -= allocated_amount
+ remaining_references_allocated_amounts[key] -= allocated_amount
+ payment_request_outstanding_amounts[ref.payment_request] -= allocated_amount
+
+ elif reference_outstanding_amount < 0 and allocated_negative_outstanding:
+ # allocate amount according to outstanding amounts
+ outstanding_amounts = (
+ allocated_negative_outstanding,
+ abs(reference_outstanding_amount),
+ pr_outstanding_amount,
)
- allocated_positive_outstanding -= flt(ref.allocated_amount)
- not_allocated_amounts[key] -= flt(ref.allocated_amount)
- payment_request_outstanding[ref.payment_request] -= flt(ref.allocated_amount)
- elif outstanding_amount < 0 and allocated_negative_outstanding:
- ref.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
- allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
- not_allocated_amounts[key] -= abs(flt(ref.allocated_amount))
- payment_request_outstanding[ref.payment_request] -= abs(flt(ref.allocated_amount))
+ ref.allocated_amount = min(outstanding_amounts) * -1
+
+ # update amounts to track allocation
+ allocated_amount = abs(flt(ref.allocated_amount))
+ allocated_negative_outstanding -= allocated_amount
+ remaining_references_allocated_amounts[key] += allocated_amount # negative amount
+ payment_request_outstanding_amounts[ref.payment_request] -= allocated_amount
+
+ # Re allocate amount to those references which have no PR (Lower priority)
for ref in self.references:
if ref.payment_request:
continue
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
- outstanding_amount = not_allocated_amounts[key]
-
- if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
- ref.allocated_amount = min(allocated_positive_outstanding, outstanding_amount)
- allocated_positive_outstanding -= flt(ref.allocated_amount)
- elif outstanding_amount < 0 and allocated_negative_outstanding:
- ref.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
- allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
+ reference_outstanding_amount = remaining_references_allocated_amounts[key]
- else:
- # todo: make more efficient using same variable
- for ref in self.references:
- if ref.outstanding_amount > 0 and allocated_positive_outstanding >= 0:
- ref.allocated_amount = min(allocated_positive_outstanding, ref.outstanding_amount)
- allocated_positive_outstanding -= flt(ref.allocated_amount)
- elif ref.outstanding_amount < 0 and allocated_negative_outstanding:
- ref.allocated_amount = (
- min(allocated_negative_outstanding, abs(ref.outstanding_amount)) * -1
- )
- allocated_negative_outstanding -= abs(flt(ref.allocated_amount))
-
- set_open_payment_requests_to_references(self.references)
+ allocated_positive_outstanding, allocated_negative_outstanding = _allocation_to_unset_pr_row(
+ ref,
+ reference_outstanding_amount,
+ allocated_positive_outstanding,
+ allocated_negative_outstanding,
+ )
def get_matched_payment_request_of_references(references=None):
@@ -1917,6 +1951,16 @@ def get_matched_payment_request_of_references(references=None):
return {(pr.reference_doctype, pr.reference_name, pr.outstanding_amount): pr.name for pr in matched_prs}
+def get_reference_outstanding_amounts(references=None):
+ if not references:
+ return
+
+ refs_with_payment_term = get_payment_term_outstanding_of_references(references) or {}
+ refs_without_payment_term = get_no_payment_terms_references_outstanding(references) or {}
+
+ return {**refs_with_payment_term, **refs_without_payment_term}
+
+
def get_payment_term_outstanding_of_references(references=None):
if not references:
return
@@ -1944,6 +1988,24 @@ def get_payment_term_outstanding_of_references(references=None):
return {(row.parenttype, row.parent, row.payment_term): row.outstanding for row in response}
+def get_no_payment_terms_references_outstanding(references):
+ if not references:
+ return
+
+ outstanding_amounts = {}
+
+ for ref in references:
+ if ref.payment_term:
+ continue
+
+ key = (ref.reference_doctype, ref.reference_name, None)
+
+ if key not in outstanding_amounts:
+ outstanding_amounts[key] = ref.outstanding_amount
+
+ return outstanding_amounts
+
+
def get_payment_request_outstanding_of_references(references=None):
if not references:
return
@@ -1963,24 +2025,6 @@ def get_payment_request_outstanding_of_references(references=None):
return dict(response)
-def get_no_payment_terms_references_outstanding(references):
- if not references:
- return
-
- outstanding_amounts = {}
-
- for ref in references:
- if ref.payment_term:
- continue
-
- key = (ref.reference_doctype, ref.reference_name, None)
-
- if key not in outstanding_amounts:
- outstanding_amounts[key] = ref.outstanding_amount
-
- return outstanding_amounts
-
-
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
throw(
From b860eeaaa4c22cf611dcfd6631cbf17bf49912e8 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Tue, 27 Aug 2024 17:12:45 +0530
Subject: [PATCH 33/60] fix: set matched payment request if unset
---
.../doctype/payment_entry/payment_entry.js | 24 ++++++
.../doctype/payment_entry/payment_entry.py | 78 +++++++++++--------
2 files changed, 68 insertions(+), 34 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 00c8ac6790e8..f54ab356f6b7 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1639,6 +1639,30 @@ frappe.ui.form.on("Payment Entry", {
});
}
},
+
+ after_save: function (frm) {
+ const { matched_payment_requests } = frappe.last_response;
+ if (!matched_payment_requests) return;
+
+ const COLUMN_LABEL = [
+ [__("Reference DocType"), __("Reference Name"), __("Allocated Amount"), __("Payment Request")],
+ ];
+
+ frappe.msgprint({
+ title: __("Matched Payment Request"),
+ message: COLUMN_LABEL.concat(matched_payment_requests),
+ as_table: true,
+ primary_action: {
+ label: __("Allocate Payment Request"),
+ action() {
+ frappe.hide_msgprint();
+ frm.call("set_matched_payment_requests", { matched_payment_requests }, () => {
+ frm.dirty();
+ });
+ },
+ },
+ });
+ },
});
frappe.ui.form.on("Payment Entry Reference", {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c4a97be08479..9cdc1ae1c224 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -182,10 +182,8 @@ def validate(self):
self.set_status()
self.set_total_in_words()
- # todo-abdeali: move to front end
def before_save(self):
- pass
- # self.check_references_for_unset_payment_request()
+ self.set_matched_unset_payment_requests_to_response()
def on_submit(self):
if self.difference_amount:
@@ -1724,8 +1722,7 @@ def get_current_tax_fraction(self, tax):
return current_tax_fraction
- # todo-abdeali: needs changes if PR already use for ref and one ref row have no PR then do not show this!!
- def check_references_for_unset_payment_request(self):
+ def set_matched_unset_payment_requests_to_response(self):
if not self.references:
return
@@ -1736,30 +1733,7 @@ def check_references_for_unset_payment_request(self):
if not matched_payment_requests:
return
- unset_pr_rows = {}
-
- for row in self.references:
- if row.payment_request:
- continue
-
- matched_pr = matched_payment_requests.get(
- (row.reference_doctype, row.reference_name, row.allocated_amount)
- )
-
- if matched_pr:
- unset_pr_rows[row.idx] = matched_pr
-
- if unset_pr_rows:
- message = _("Matched Payment Requests found for references, but not set.
")
- message += _("View Details
")
- for idx, pr in unset_pr_rows.items():
- message += _("- Row #{0}: {1}
").format(idx, get_link_to_form("Payment Request", pr))
- message += _("
")
-
- frappe.msgprint(
- msg=message,
- indicator="yellow",
- )
+ frappe.response["matched_payment_requests"] = matched_payment_requests
@frappe.whitelist()
def allocate_party_amount_against_ref_docs(
@@ -1913,17 +1887,39 @@ def _allocation_to_unset_pr_row(
allocated_negative_outstanding,
)
+ @frappe.whitelist()
+ def set_matched_payment_requests(self, matched_payment_requests):
+ if not self.references:
+ return
+
+ # modify matched_payment_requests
+ payment_requests = {}
+
+ for row in matched_payment_requests:
+ key = tuple(row[:3])
+ payment_requests[key] = row[3]
+
+ for ref in self.references:
+ if ref.payment_request:
+ continue
+
+ key = (ref.reference_doctype, ref.reference_name, ref.allocated_amount)
+
+ if key in payment_requests:
+ ref.payment_request = payment_requests[key]
+ del payment_requests[key]
+
def get_matched_payment_request_of_references(references=None):
if not references:
return
# to fetch matched rows
- refs = [
+ refs = {
(row.reference_doctype, row.reference_name, row.allocated_amount)
for row in references
if row.reference_doctype and row.reference_name and row.allocated_amount
- ]
+ }
if not refs:
return
@@ -1934,7 +1930,11 @@ def get_matched_payment_request_of_references(references=None):
subquery = (
frappe.qb.from_(PR)
.select(
- PR.name, PR.reference_doctype, PR.reference_name, PR.outstanding_amount, Count("*").as_("count")
+ PR.reference_doctype,
+ PR.reference_name,
+ PR.outstanding_amount.as_("allocated_amount"),
+ PR.name.as_("payment_request"),
+ Count("*").as_("count"),
)
.where(Tuple(PR.reference_doctype, PR.reference_name, PR.outstanding_amount).isin(refs))
.where(PR.status != "Paid")
@@ -1943,12 +1943,22 @@ def get_matched_payment_request_of_references(references=None):
)
# query to fetch matched rows which are single
- matched_prs = frappe.qb.from_(subquery).select("*").where(subquery.count == 1).run(as_dict=True)
+ matched_prs = (
+ frappe.qb.from_(subquery)
+ .select(
+ subquery.reference_doctype,
+ subquery.reference_name,
+ subquery.allocated_amount,
+ subquery.payment_request,
+ )
+ .where(subquery.count == 1)
+ .run()
+ )
if not matched_prs:
return
- return {(pr.reference_doctype, pr.reference_name, pr.outstanding_amount): pr.name for pr in matched_prs}
+ return matched_prs
def get_reference_outstanding_amounts(references=None):
From 451bdd55ed59743684caf6ac728422a3ca0e7db5 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 11 Sep 2024 11:12:29 +0530
Subject: [PATCH 34/60] fix: minor changes
---
erpnext/accounts/doctype/payment_request/payment_request.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 0e63ed522df9..d905f9adc788 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -608,8 +608,7 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
.run()
)
- return response[0][0] if response else 0
-
+ return response[0][0] if response[0] else 0
def get_gateway_details(args): # nosemgrep
"""
From 9e89da43ced64318f10c5396e9b5965c980172de Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 11 Sep 2024 14:02:54 +0530
Subject: [PATCH 35/60] fix: Allocate single Payment Request if PE created from
PR
---
.../doctype/payment_entry/payment_entry.py | 6 +--
.../payment_request/payment_request.py | 53 +++++++++++++++++--
2 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f5befc503abe..bdd21cb0f8db 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1821,7 +1821,7 @@ def _allocation_to_unset_pr_row(
allocated_negative_outstanding,
)
- set_open_payment_requests_to_references(self.references)
+ allocate_open_payment_requests_to_references(self.references)
else:
payment_request_outstanding_amounts = (
@@ -2816,7 +2816,7 @@ def get_payment_entry(
pe.set_difference_amount()
if not created_from_payment_request:
- set_open_payment_requests_to_references(pe.references)
+ allocate_open_payment_requests_to_references(pe.references)
return pe
@@ -2861,7 +2861,7 @@ def get_open_payment_requests_for_references(references=None):
return reference_payment_requests
-def set_open_payment_requests_to_references(references=None):
+def allocate_open_payment_requests_to_references(references=None):
if not references:
return
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index d905f9adc788..08479cf4c752 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -324,9 +324,8 @@ def create_payment_entry(self, submit=True):
}
)
- # Update payment_request for each reference in payment_entry (Payment Term can splits the row)
- for row in payment_entry.references:
- row.payment_request = self.name
+ # Allocate payment_request for each reference in payment_entry (Payment Term can splits the row)
+ self._allocate_payment_request_to_pe_references(references=payment_entry.references)
# Update dimensions
payment_entry.update(
@@ -438,6 +437,53 @@ def update_reference_advance_payment_status(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
ref_doc.set_advance_payment_status()
+ def _allocate_payment_request_to_pe_references(self, references):
+ if len(references) == 1:
+ references[0].payment_request = self.name
+ return
+
+ outstanding_amount = self.outstanding_amount
+
+ # to manage rows
+ row_number = 1
+ MOVE_TO_NEXT_ROW = 1
+ TO_SKIP_NEW_ROW = 2
+ NEW_ROW_ADDED = False
+
+ while row_number <= len(references):
+ row = references[row_number - 1]
+
+ # update the idx to maintain the order
+ row.idx = row_number
+
+ if outstanding_amount == 0:
+ if not NEW_ROW_ADDED:
+ break
+ continue
+
+ # allocate the payment request to the row
+ row.payment_request = self.name
+
+ if row.allocated_amount <= outstanding_amount:
+ outstanding_amount -= row.allocated_amount
+ row_number += MOVE_TO_NEXT_ROW
+ else:
+ remaining_allocated_amount = row.allocated_amount - outstanding_amount
+ row.allocated_amount = outstanding_amount
+ outstanding_amount = 0
+
+ # create a new row without PR for remaining unallocated amount
+ new_row = frappe.copy_doc(row)
+ references.insert(row_number, new_row)
+
+ # update new row
+ new_row.idx = row_number + 1
+ new_row.payment_request = None
+ new_row.allocated_amount = remaining_allocated_amount
+
+ NEW_ROW_ADDED = True
+ row_number += TO_SKIP_NEW_ROW
+
@frappe.whitelist(allow_guest=True)
def make_payment_request(**args):
@@ -610,6 +656,7 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
return response[0][0] if response[0] else 0
+
def get_gateway_details(args): # nosemgrep
"""
Return gateway and payment account of default payment gateway
From 871806ebce6d3560451b271c61b207ba2f6aa86e Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 12 Sep 2024 13:32:49 +0530
Subject: [PATCH 36/60] fix: improve code logic
---
.../doctype/payment_entry/payment_entry.js | 6 +-
.../doctype/payment_entry/payment_entry.py | 158 ++++++++++++++----
.../payment_entry_reference.py | 6 +-
.../payment_request/payment_request.py | 16 +-
4 files changed, 139 insertions(+), 47 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index faee53b121aa..699fdb3cf5eb 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1067,12 +1067,10 @@ frappe.ui.form.on("Payment Entry", {
},
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
- await frm.call("allocate_party_amount_against_ref_docs", {
+ await frm.call("allocate_party_amount_and_payment_request_against_ref_docs", {
paid_amount: paid_amount,
paid_amount_change: paid_amount_change,
- allocate_payment_amount: frappe.flags.allocate_payment_amount
- ? frappe.flags.allocate_payment_amount
- : false,
+ allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
});
frm.events.set_total_allocated_amount(frm);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index bdd21cb0f8db..e91bebefff83 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -332,6 +332,9 @@ def validate_allocated_amount(self):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_as_per_payment_request(self):
+ """
+ Allocated amount should not be greater than the outstanding amount of the Payment Request.f
+ """
from erpnext.accounts.doctype.payment_request.payment_request import (
get_outstanding_amount_of_payment_entry_references as get_outstanding_amounts,
)
@@ -1723,6 +1726,10 @@ def get_current_tax_fraction(self, tax):
return current_tax_fraction
def set_matched_unset_payment_requests_to_response(self):
+ """
+ Find matched Payment Requests for those references which have no Payment Request set.\n
+ And set to `frappe.response` to show in the frontend for allocation.
+ """
if not self.references:
return
@@ -1736,9 +1743,15 @@ def set_matched_unset_payment_requests_to_response(self):
frappe.response["matched_payment_requests"] = matched_payment_requests
@frappe.whitelist()
- def allocate_party_amount_against_ref_docs(
+ def allocate_party_amount_and_payment_request_against_ref_docs(
self, paid_amount, paid_amount_change, allocate_payment_amount
):
+ """
+ Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
+ :param paid_amount: Paid Amount / Received Amount.
+ :param paid_amount_change: Flag to check if `Paid Amount` is changed or not.
+ :param allocate_payment_amount: Flag to allocate amount or not.
+ """
if not self.references:
return
@@ -1825,9 +1838,9 @@ def _allocation_to_unset_pr_row(
else:
payment_request_outstanding_amounts = (
- get_payment_request_outstanding_of_references(self.references) or {}
+ get_payment_request_outstanding_set_in_references(self.references) or {}
)
- references_outstanding_amounts = get_reference_outstanding_amounts(self.references) or {}
+ references_outstanding_amounts = get_references_outstanding_amount(self.references) or {}
remaining_references_allocated_amounts = references_outstanding_amounts.copy()
# Re allocate amount to those references which have PR set (Higher priority)
@@ -1889,10 +1902,21 @@ def _allocation_to_unset_pr_row(
@frappe.whitelist()
def set_matched_payment_requests(self, matched_payment_requests):
- if not self.references:
+ """
+ Set `Payment Request` against `Reference` based on `matched_payment_requests`.\n
+ :param matched_payment_requests: List of tuple of matched Payment Requests.
+
+ ---
+ Example: [(reference_doctype, reference_name, allocated_amount, payment_request), ...]
+ """
+ if not self.references or not matched_payment_requests:
return
+ if isinstance(matched_payment_requests, str):
+ matched_payment_requests = json.loads(matched_payment_requests)
+
# modify matched_payment_requests
+ # like (reference_doctype, reference_name, allocated_amount): payment_request
payment_requests = {}
for row in matched_payment_requests:
@@ -1907,10 +1931,17 @@ def set_matched_payment_requests(self, matched_payment_requests):
if key in payment_requests:
ref.payment_request = payment_requests[key]
- del payment_requests[key]
+ del payment_requests[key] # to avoid duplicate allocation
def get_matched_payment_request_of_references(references=None):
+ """
+ Get those `Payment Requests` which are matched with `References`.\n
+ - Amount must be same.
+ - Only single `Payment Request` available for this amount.
+
+ Example: [(reference_doctype, reference_name, allocated_amount, payment_request), ...]
+ """
if not references:
return
@@ -1955,23 +1986,31 @@ def get_matched_payment_request_of_references(references=None):
.run()
)
- if not matched_prs:
- return
+ return matched_prs if matched_prs else None
- return matched_prs
+def get_references_outstanding_amount(references=None):
+ """
+ Fetch accurate outstanding amount of `References`.\n
+ - If `Payment Term` is set, then fetch outstanding amount from `Payment Schedule`.
+ - If `Payment Term` is not set, then fetch outstanding amount from `References` it self.
-def get_reference_outstanding_amounts(references=None):
+ Example: {(reference_doctype, reference_name, payment_term): outstanding_amount, ...}
+ """
if not references:
return
- refs_with_payment_term = get_payment_term_outstanding_of_references(references) or {}
- refs_without_payment_term = get_no_payment_terms_references_outstanding(references) or {}
+ refs_with_payment_term = get_outstanding_of_references_with_payment_term(references) or {}
+ refs_without_payment_term = get_outstanding_of_references_with_no_payment_term(references) or {}
return {**refs_with_payment_term, **refs_without_payment_term}
-def get_payment_term_outstanding_of_references(references=None):
+def get_outstanding_of_references_with_payment_term(references=None):
+ """
+ Fetch outstanding amount of `References` which have `Payment Term` set.\n
+ Example: {(reference_doctype, reference_name, payment_term): outstanding_amount, ...}
+ """
if not references:
return
@@ -1998,7 +2037,14 @@ def get_payment_term_outstanding_of_references(references=None):
return {(row.parenttype, row.parent, row.payment_term): row.outstanding for row in response}
-def get_no_payment_terms_references_outstanding(references):
+def get_outstanding_of_references_with_no_payment_term(references):
+ """
+ Fetch outstanding amount of `References` which have no `Payment Term` set.\n
+ - Fetch outstanding amount from `References` it self.
+
+ Note: `None` is used for allocation of `Payment Request`
+ Example: {(reference_doctype, reference_name, None): outstanding_amount, ...}
+ """
if not references:
return
@@ -2016,23 +2062,28 @@ def get_no_payment_terms_references_outstanding(references):
return outstanding_amounts
-def get_payment_request_outstanding_of_references(references=None):
+def get_payment_request_outstanding_set_in_references(references=None):
+ """
+ Fetch outstanding amount of `Payment Request` which are set in `References`.\n
+ Example: {payment_request: outstanding_amount, ...}
+ """
if not references:
return
- prs = {row.payment_request for row in references if row.payment_request}
+ referenced_payment_requests = {row.payment_request for row in references if row.payment_request}
- if not prs:
+ if not referenced_payment_requests:
return
PR = frappe.qb.DocType("Payment Request")
- response = (frappe.qb.from_(PR).select(PR.name, PR.outstanding_amount).where(PR.name.isin(prs))).run()
-
- if not response:
- return
+ response = (
+ frappe.qb.from_(PR)
+ .select(PR.name, PR.outstanding_amount)
+ .where(PR.name.isin(referenced_payment_requests))
+ ).run()
- return dict(response)
+ return dict(response) if response else None
def validate_inclusive_tax(tax, doc):
@@ -2815,6 +2866,7 @@ def get_payment_entry(
pe.set_difference_amount()
+ # If PE is created from PR directly, then no need to find open PRs for the references
if not created_from_payment_request:
allocate_open_payment_requests_to_references(pe.references)
@@ -2822,6 +2874,12 @@ def get_payment_entry(
def get_open_payment_requests_for_references(references=None):
+ """
+ Fetch all unpaid Payment Requests for the references. \n
+ - Each reference can have multiple Payment Requests. \n
+
+ Example: {("Sales Invoice", "SINV-00001"): {"PREQ-00001": 1000, "PREQ-00002": 2000}}
+ """
if not references:
return
@@ -2862,13 +2920,41 @@ def get_open_payment_requests_for_references(references=None):
def allocate_open_payment_requests_to_references(references=None):
+ """
+ Allocate unpaid Payment Requests to the references. \n
+ ---
+ - Allocation based on below factors
+ - Reference Allocated Amount
+ - Reference Outstanding Amount (With Payment Terms or without Payment Terms)
+ - Reference Payment Request's outstanding amount
+ ---
+ - Allocation based on below scenarios
+ - Reference's Allocated Amount == Payment Request's Outstanding Amount
+ - Allocate the Payment Request to the reference
+ - This PR will not be allocated further
+ - Reference's Allocated Amount < Payment Request's Outstanding Amount
+ - Allocate the Payment Request to the reference
+ - Reduce the PR's outstanding amount by the allocated amount
+ - This PR can be allocated further
+ - Reference's Allocated Amount > Payment Request's Outstanding Amount
+ - Allocate the Payment Request to the reference
+ - Reduce Allocated Amount of the reference by the PR's outstanding amount
+ - Create a new row for the remaining amount until the Allocated Amount is 0
+ - Allocate PR if available
+ ---
+ - Note:
+ - Priority is given to the first Payment Request of respective references.
+ - Single Reference can have multiple rows.
+ - With Payment Terms or without Payment Terms
+ - With Payment Request or without Payment Request
+ """
if not references:
return
# get all unpaid payment requests for the references
- all_references_payment_requests = get_open_payment_requests_for_references(references)
+ references_open_payment_requests = get_open_payment_requests_for_references(references)
- if not all_references_payment_requests:
+ if not references_open_payment_requests:
return
# to manage new rows
@@ -2884,24 +2970,24 @@ def allocate_open_payment_requests_to_references(references=None):
row.idx = row_number
# unpaid payment requests for the reference
- reference_payment_requests = all_references_payment_requests.get(reference_key)
+ reference_payment_requests = references_open_payment_requests.get(reference_key)
if not reference_payment_requests:
row_number += MOVE_TO_NEXT_ROW # to move to next reference row
continue
# get the first payment request and its outstanding amount
- payment_request, outstanding_amount = next(iter(reference_payment_requests.items()))
+ payment_request, pr_outstanding_amount = next(iter(reference_payment_requests.items()))
allocated_amount = row.allocated_amount
# allocate the payment request to the reference and PR's outstanding amount
row.payment_request = payment_request
- if outstanding_amount == allocated_amount:
+ if pr_outstanding_amount == allocated_amount:
del reference_payment_requests[payment_request]
row_number += MOVE_TO_NEXT_ROW
- elif outstanding_amount > allocated_amount:
+ elif pr_outstanding_amount > allocated_amount:
# reduce the outstanding amount of the payment request
reference_payment_requests[payment_request] -= allocated_amount
row_number += MOVE_TO_NEXT_ROW
@@ -2909,8 +2995,8 @@ def allocate_open_payment_requests_to_references(references=None):
else:
# split the reference row to allocate the remaining amount
del reference_payment_requests[payment_request]
- row.allocated_amount = outstanding_amount
- allocated_amount -= outstanding_amount
+ row.allocated_amount = pr_outstanding_amount
+ allocated_amount -= pr_outstanding_amount
# set the remaining amount to the next row
while allocated_amount:
@@ -2918,8 +3004,8 @@ def allocate_open_payment_requests_to_references(references=None):
new_row = frappe.copy_doc(row)
references.insert(row_number, new_row)
- # get the next payment request and its outstanding amount
- payment_request, outstanding_amount = next(
+ # get the first payment request and its outstanding amount
+ payment_request, pr_outstanding_amount = next(
iter(reference_payment_requests.items()), (None, None)
)
@@ -2927,25 +3013,25 @@ def allocate_open_payment_requests_to_references(references=None):
new_row.idx = row_number + 1
new_row.payment_request = payment_request
new_row.allocated_amount = min(
- outstanding_amount if outstanding_amount else allocated_amount, allocated_amount
+ pr_outstanding_amount if pr_outstanding_amount else allocated_amount, allocated_amount
)
- if not payment_request or not outstanding_amount:
+ if not payment_request or not pr_outstanding_amount:
row_number += TO_SKIP_NEW_ROW
break
- elif outstanding_amount == allocated_amount:
+ elif pr_outstanding_amount == allocated_amount:
del reference_payment_requests[payment_request]
row_number += TO_SKIP_NEW_ROW
break
- elif outstanding_amount > allocated_amount:
+ elif pr_outstanding_amount > allocated_amount:
reference_payment_requests[payment_request] -= allocated_amount
row_number += TO_SKIP_NEW_ROW
break
else:
- allocated_amount -= outstanding_amount
+ allocated_amount -= pr_outstanding_amount
del reference_payment_requests[payment_request]
row_number += MOVE_TO_NEXT_ROW
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index 3afff4c8d25b..c7d9909950d8 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -37,8 +37,8 @@ class PaymentEntryReference(Document):
@property
def payment_term_outstanding(self):
- if not self.payment_term:
- return 0
+ if not self.payment_term or not self.reference_doctype or not self.reference_name:
+ return
return frappe.db.get_value(
"Payment Schedule",
@@ -53,6 +53,6 @@ def payment_term_outstanding(self):
@property
def payment_request_outstanding(self):
if not self.payment_request:
- return 0
+ return
return frappe.db.get_value("Payment Request", self.payment_request, "outstanding_amount")
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 08479cf4c752..e870c4214c13 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -438,6 +438,12 @@ def update_reference_advance_payment_status(self):
ref_doc.set_advance_payment_status()
def _allocate_payment_request_to_pe_references(self, references):
+ """
+ Allocate the Payment Request to the Payment Entry references based on\n
+ - Allocated Amount.
+ - Outstanding Amount of Payment Request.\n
+ Payment Request is doc itself and references are the rows of Payment Entry.
+ """
if len(references) == 1:
references[0].payment_request = self.name
return
@@ -459,6 +465,8 @@ def _allocate_payment_request_to_pe_references(self, references):
if outstanding_amount == 0:
if not NEW_ROW_ADDED:
break
+
+ row_number += MOVE_TO_NEXT_ROW
continue
# allocate the payment request to the row
@@ -700,7 +708,7 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
if not references:
return
- payment_requests = frappe.get_all(
+ referenced_payment_requests = frappe.get_all(
"Payment Request",
filters={"name": ["in", get_referenced_payment_requests(references)]},
fields=[
@@ -711,13 +719,13 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
],
)
- payment_requests = {pr.name: pr for pr in payment_requests}
+ referenced_payment_requests = {pr.name: pr for pr in referenced_payment_requests}
for ref in references:
if not ref.payment_request:
continue
- payment_request = payment_requests[ref.payment_request]
+ payment_request = referenced_payment_requests[ref.payment_request]
# update outstanding amount
new_outstanding_amount = flt(
@@ -783,7 +791,7 @@ def get_dummy_message(doc):
{%- else %}Hello,
{% endif %}
{{ _("Requesting payment against {0} {1} for amount {2}").format(doc.doctype,
- doc.name, doc.get_formatted("grand_total")) }}
+ doc.name, doc.get_formatted("grand_total")) }}
{{ _("Make Payment") }}
From 3ee0a0e473fcface1b296a6d65f63484bd9e7531 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 12 Sep 2024 16:52:54 +0530
Subject: [PATCH 37/60] fix: Removed duplication code
---
.../doctype/payment_entry/payment_entry.js | 2 +-
.../doctype/payment_entry/payment_entry.py | 11 +++---
.../payment_request/payment_request.py | 37 +++++--------------
3 files changed, 15 insertions(+), 35 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 699fdb3cf5eb..4591ca809f2e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -177,7 +177,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
const row = frappe.get_doc(cdt, cdn);
return {
- query: "erpnext.accounts.doctype.payment_request.payment_request.get_open_payment_requests",
+ query: "erpnext.accounts.doctype.payment_request.payment_request.get_open_payment_requests_query",
filters: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index e91bebefff83..cf99082b5131 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -335,17 +335,16 @@ def validate_allocated_amount_as_per_payment_request(self):
"""
Allocated amount should not be greater than the outstanding amount of the Payment Request.f
"""
- from erpnext.accounts.doctype.payment_request.payment_request import (
- get_outstanding_amount_of_payment_entry_references as get_outstanding_amounts,
- )
-
if not self.references:
return
- outstanding_amounts = get_outstanding_amounts(self.references)
+ pr_outstanding_amounts = get_payment_request_outstanding_set_in_references(self.references)
+
+ if not pr_outstanding_amounts:
+ return
for ref in self.references:
- if ref.payment_request and ref.allocated_amount > outstanding_amounts[ref.payment_request]:
+ if ref.payment_request and ref.allocated_amount > pr_outstanding_amounts[ref.payment_request]:
frappe.throw(
msg=_(
"Row #{0}: Allocated Amount cannot be greater than Outstanding Amount of Payment Request {1}"
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index e870c4214c13..7e6ab9d7a438 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -705,12 +705,15 @@ def make_payment_entry(docname):
def update_payment_requests_as_per_pe_references(references=None, cancel=False):
+ """
+ Update Payment Request's `Status` and `Outstanding Amount` based on Payment Entry Reference's `Allocated Amount`.
+ """
if not references:
return
referenced_payment_requests = frappe.get_all(
"Payment Request",
- filters={"name": ["in", get_referenced_payment_requests(references)]},
+ filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]},
fields=[
"name",
"grand_total",
@@ -761,29 +764,6 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
)
-def get_outstanding_amount_of_payment_entry_references(references):
- if not references:
- return {}
-
- payment_requests = get_referenced_payment_requests(references)
-
- return dict(
- frappe.get_all(
- "Payment Request",
- filters={"name": ["in", payment_requests]},
- fields=["name", "outstanding_amount"],
- as_list=True,
- )
- )
-
-
-def get_referenced_payment_requests(references):
- if not references:
- return ()
-
- return {row.payment_request for row in references if row.payment_request}
-
-
def get_dummy_message(doc):
return frappe.render_template(
"""{% if doc.contact_person -%}
@@ -791,7 +771,7 @@ def get_dummy_message(doc):
{%- else %}Hello,
{% endif %}
{{ _("Requesting payment against {0} {1} for amount {2}").format(doc.doctype,
- doc.name, doc.get_formatted("grand_total")) }}
+ doc.name, doc.get_formatted("grand_total")) }}
{{ _("Make Payment") }}
@@ -895,7 +875,7 @@ def get_paid_amount_against_order(dt, dn):
@frappe.whitelist()
-def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filters):
+def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
# permission checks in `get_list()`
reference_doctype = filters.get("reference_doctype")
reference_name = filters.get("reference_doctype")
@@ -909,6 +889,7 @@ def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filter
"reference_doctype": filters["reference_doctype"],
"reference_name": filters["reference_name"],
"status": ["!=", "Paid"],
+ "outstanding_amount": ["!=", 0], # for compatibility with old data
"docstatus": 1,
},
fields=["name", "grand_total", "outstanding_amount"],
@@ -918,8 +899,8 @@ def get_open_payment_requests(doctype, txt, searchfield, start, page_len, filter
return [
(
pr.name,
- _("Grand Total: {0}").format(pr.grand_total),
- _("Outstanding Amount: {0}").format(pr.outstanding_amount),
+ _("Grand Total: {0}").format(pr.grand_total),
+ _("Outstanding Amount: {0}").format(pr.outstanding_amount),
)
for pr in open_payment_requests
]
From 2182e0c621a3479a01cebd63119d268b1b156edf Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Thu, 12 Sep 2024 17:27:36 +0530
Subject: [PATCH 38/60] fix: proper message title
---
erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 4591ca809f2e..4c8723e62d19 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1661,7 +1661,7 @@ frappe.ui.form.on("Payment Entry", {
];
frappe.msgprint({
- title: __("Matched Payment Request"),
+ title: __("Unset Matched Payment Request"),
message: COLUMN_LABEL.concat(matched_payment_requests),
as_table: true,
primary_action: {
From c5b5057cd88d3cba5d5e8c2c8c8e3b1b9dc2606f Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 13 Sep 2024 14:47:32 +0530
Subject: [PATCH 39/60] refactor: Rename method of Allocation Amount to
References
---
erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +-
erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 4c8723e62d19..56fbc18f12a8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1067,7 +1067,7 @@ frappe.ui.form.on("Payment Entry", {
},
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
- await frm.call("allocate_party_amount_and_payment_request_against_ref_docs", {
+ await frm.call("allocate_amount_to_references", {
paid_amount: paid_amount,
paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cf99082b5131..f4864d059dfa 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1742,14 +1742,12 @@ def set_matched_unset_payment_requests_to_response(self):
frappe.response["matched_payment_requests"] = matched_payment_requests
@frappe.whitelist()
- def allocate_party_amount_and_payment_request_against_ref_docs(
- self, paid_amount, paid_amount_change, allocate_payment_amount
- ):
+ def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocate_payment_amount):
"""
Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
:param paid_amount: Paid Amount / Received Amount.
:param paid_amount_change: Flag to check if `Paid Amount` is changed or not.
- :param allocate_payment_amount: Flag to allocate amount or not.
+ :param allocate_payment_amount: Flag to allocate amount or not. (Payment Request is also dependent on this flag)
"""
if not self.references:
return
From 22e41e49588ace54ec3adba665491cb4fc07ab2c Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 13 Sep 2024 15:58:33 +0530
Subject: [PATCH 40/60] refactor: Changing `grand_total` description based on
`party_type`
---
.../doctype/payment_request/payment_request.js | 11 +++++++++++
.../doctype/payment_request/payment_request.json | 3 +--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index 44313e5c0d2c..dd959251f168 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -25,6 +25,8 @@ frappe.ui.form.on("Payment Request", "onload", function (frm, dt, dn) {
});
frappe.ui.form.on("Payment Request", "refresh", function (frm) {
+ set_grand_total_field_description(frm);
+
if (frm.doc.status == "Failed") {
frm.set_intro(__("Failure: {0}", [frm.doc.failed_reason]), "red");
}
@@ -97,3 +99,12 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function (frm) {
});
}
});
+
+frappe.ui.form.on("Payment Request", "party_type", function (frm) {
+ set_grand_total_field_description(frm);
+});
+
+function set_grand_total_field_description(frm) {
+ const party_type = (frm.doc.party_type || "party").toLowerCase();
+ frm.get_field("grand_total").set_description(`Amount in ${party_type}'s currency`);
+}
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index d9baeee28539..8ebdf53bbde0 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -146,7 +146,6 @@
"label": "Transaction Details"
},
{
- "description": "Amount in customer's currency",
"fieldname": "grand_total",
"fieldtype": "Currency",
"label": "Amount",
@@ -432,7 +431,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-08-21 17:17:16.584404",
+ "modified": "2024-09-13 15:36:08.198722",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
From 3a95e183a4231c42b1ad7402984f41ca8b0365af Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 13 Sep 2024 19:48:34 +0530
Subject: [PATCH 41/60] refactor: update Payment Request
---
.../payment_request/payment_request.js | 11 ------
.../payment_request/payment_request.json | 7 ++--
.../payment_request/payment_request.py | 36 +++++--------------
3 files changed, 13 insertions(+), 41 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index dd959251f168..44313e5c0d2c 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -25,8 +25,6 @@ frappe.ui.form.on("Payment Request", "onload", function (frm, dt, dn) {
});
frappe.ui.form.on("Payment Request", "refresh", function (frm) {
- set_grand_total_field_description(frm);
-
if (frm.doc.status == "Failed") {
frm.set_intro(__("Failure: {0}", [frm.doc.failed_reason]), "red");
}
@@ -99,12 +97,3 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function (frm) {
});
}
});
-
-frappe.ui.form.on("Payment Request", "party_type", function (frm) {
- set_grand_total_field_description(frm);
-});
-
-function set_grand_total_field_description(frm) {
- const party_type = (frm.doc.party_type || "party").toLowerCase();
- frm.get_field("grand_total").set_description(`Amount in ${party_type}'s currency`);
-}
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 8ebdf53bbde0..5b1b9e4a14f5 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -146,8 +146,10 @@
"label": "Transaction Details"
},
{
+ "description": "Amount in party's account currency",
"fieldname": "grand_total",
"fieldtype": "Currency",
+ "in_preview": 1,
"label": "Amount",
"non_negative": 1,
"options": "currency",
@@ -166,7 +168,7 @@
{
"fieldname": "currency",
"fieldtype": "Link",
- "label": "Transaction Currency",
+ "label": "Party Account Currency",
"options": "Currency",
"read_only": 1
},
@@ -413,6 +415,7 @@
"in_preview": 1,
"label": "Outstanding Amount",
"non_negative": 1,
+ "options": "currency",
"read_only": 1
},
{
@@ -431,7 +434,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-09-13 15:36:08.198722",
+ "modified": "2024-09-13 19:17:28.734384",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 7e6ab9d7a438..056b59e9c6e0 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -46,6 +46,7 @@ class PaymentRequest(Document):
bank_account: DF.Link | None
bank_account_no: DF.ReadOnly | None
branch_code: DF.ReadOnly | None
+ company: DF.Link | None
cost_center: DF.Link | None
currency: DF.Link | None
email_to: DF.Data | None
@@ -87,7 +88,6 @@ class PaymentRequest(Document):
subscription_plans: DF.Table[SubscriptionPlanDetail]
swift_number: DF.ReadOnly | None
transaction_date: DF.Date | None
- company: DF.Link | None
# end: auto-generated types
def validate(self):
@@ -287,36 +287,20 @@ def create_payment_entry(self, submit=True):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
- if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
- party_account = ref_doc.debit_to
- elif self.reference_doctype == "Purchase Invoice":
- party_account = ref_doc.credit_to
- else:
- party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
-
- party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account)
-
- bank_amount = self.outstanding_amount
-
- if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
- base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
- party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
- else:
- party_amount = self.outstanding_amount
-
+ # outstanding amount is already in Part's account currency
payment_entry = get_payment_entry(
self.reference_doctype,
self.reference_name,
- party_amount=party_amount,
+ party_amount=self.outstanding_amount,
bank_account=self.payment_account,
- bank_amount=bank_amount,
+ bank_amount=self.outstanding_amount,
created_from_payment_request=True,
)
payment_entry.update(
{
"mode_of_payment": self.mode_of_payment,
+ "reference_no": self.name, # to prevent validation error
"reference_date": nowdate(),
"remarks": "Payment Entry against {} {} via Payment Request {}".format(
self.reference_doctype, self.reference_name, self.name
@@ -562,7 +546,7 @@ def make_payment_request(**args):
"payment_account": gateway_account.get("payment_account"),
"payment_channel": gateway_account.get("payment_channel"),
"payment_request_type": args.get("payment_request_type"),
- "currency": ref_doc.currency,
+ "currency": ref_doc.party_account_currency, # no need of conversion using this
"grand_total": grand_total,
"mode_of_payment": args.mode_of_payment,
"email_to": args.recipient_id or ref_doc.owner,
@@ -622,12 +606,8 @@ def get_amount(ref_doc, payment_account=None):
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
- if ref_doc.party_account_currency == ref_doc.currency:
- grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
- else:
- grand_total = flt(
- flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate
- )
+ # always get base amount to create a payment request (to match with PE)
+ grand_total = flt(ref_doc.base_rounded_total) or flt(ref_doc.base_grand_total)
elif dt == "Sales Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
From b4aee31d85f03716dbf83a48a43eb1af2955d91c Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 16 Sep 2024 13:48:38 +0530
Subject: [PATCH 42/60] fix: Remove virtual property of payment_term_oustanding
from references
---
.../payment_entry_reference.json | 3 +--
.../payment_entry_reference.py | 15 ---------------
2 files changed, 1 insertion(+), 17 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 0506517a70c0..365058c0bca7 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -134,7 +134,6 @@
"depends_on": "eval: doc.payment_term",
"fieldname": "payment_term_outstanding",
"fieldtype": "Float",
- "is_virtual": 1,
"label": "Payment Term Outstanding",
"read_only": 1
},
@@ -150,7 +149,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-08-23 12:35:40.525380",
+ "modified": "2024-09-16 13:44:14.289408",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index c7d9909950d8..2ac92ba4a841 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -35,21 +35,6 @@ class PaymentEntryReference(Document):
total_amount: DF.Float
# end: auto-generated types
- @property
- def payment_term_outstanding(self):
- if not self.payment_term or not self.reference_doctype or not self.reference_name:
- return
-
- return frappe.db.get_value(
- "Payment Schedule",
- {
- "payment_term": self.payment_term,
- "parenttype": self.reference_doctype,
- "parent": self.reference_name,
- },
- "outstanding",
- )
-
@property
def payment_request_outstanding(self):
if not self.payment_request:
From 7ceeb2c59afc63ab5408b392b2ecc44bbb86dd95 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 16 Sep 2024 15:38:54 +0530
Subject: [PATCH 43/60] fix: fetch party account currency for creating payment
request
---
.../doctype/payment_request/payment_request.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 056b59e9c6e0..da838a1e803f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -539,6 +539,14 @@ def make_payment_request(**args):
args["payment_request_type"] = (
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
)
+
+ party_type = args.get("party_type") or "Customer"
+ party_account_currency = ref_doc.party_account_currency
+
+ if not party_account_currency:
+ party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
+ party_account_currency = get_account_currency(party_account)
+
pr.update(
{
"payment_gateway_account": gateway_account.get("name"),
@@ -546,7 +554,7 @@ def make_payment_request(**args):
"payment_account": gateway_account.get("payment_account"),
"payment_channel": gateway_account.get("payment_channel"),
"payment_request_type": args.get("payment_request_type"),
- "currency": ref_doc.party_account_currency, # no need of conversion using this
+ "currency": party_account_currency, # consistent with PE
"grand_total": grand_total,
"mode_of_payment": args.mode_of_payment,
"email_to": args.recipient_id or ref_doc.owner,
@@ -555,7 +563,7 @@ def make_payment_request(**args):
"reference_doctype": ref_doc.doctype,
"reference_name": ref_doc.name,
"company": ref_doc.get("company"),
- "party_type": args.get("party_type") or "Customer",
+ "party_type": party_type,
"party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account,
"make_sales_invoice": (
From ca0c1535e695e94ff7f353d5f5c295e8ce71b2a6 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 16 Sep 2024 16:41:08 +0530
Subject: [PATCH 44/60] fix: use transaction currency as base in payment
request
---
.../payment_request/payment_request.json | 18 +++++--
.../payment_request/payment_request.py | 54 ++++++++++++++++---
2 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 5b1b9e4a14f5..5a1652f6db27 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -20,10 +20,11 @@
"reference_name",
"transaction_details",
"grand_total",
+ "outstanding_amount",
"is_a_subscription",
"column_break_18",
- "outstanding_amount",
"currency",
+ "party_account_currency",
"subscription_section",
"subscription_plans",
"bank_account_details",
@@ -146,7 +147,7 @@
"label": "Transaction Details"
},
{
- "description": "Amount in party's account currency",
+ "description": "Amount in transaction currency",
"fieldname": "grand_total",
"fieldtype": "Currency",
"in_preview": 1,
@@ -168,7 +169,7 @@
{
"fieldname": "currency",
"fieldtype": "Link",
- "label": "Party Account Currency",
+ "label": "Transaction Currency",
"options": "Currency",
"read_only": 1
},
@@ -415,7 +416,7 @@
"in_preview": 1,
"label": "Outstanding Amount",
"non_negative": 1,
- "options": "currency",
+ "options": "party_account_currency",
"read_only": 1
},
{
@@ -428,13 +429,20 @@
{
"fieldname": "column_break_pnyv",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "party_account_currency",
+ "fieldtype": "Link",
+ "label": "Party Account Currency",
+ "options": "Currency",
+ "read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-09-13 19:17:28.734384",
+ "modified": "2024-09-16 06:34:36.517077",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index da838a1e803f..969071e95f02 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -7,6 +7,7 @@
from frappe.utils import flt, nowdate
from frappe.utils.background_jobs import enqueue
+from erpnext import get_company_currency
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
@@ -61,6 +62,7 @@ class PaymentRequest(Document):
naming_series: DF.Literal["ACC-PRQ-.YYYY.-"]
outstanding_amount: DF.Currency
party: DF.DynamicLink | None
+ party_account_currency: DF.Link | None
party_type: DF.Link | None
payment_account: DF.ReadOnly | None
payment_channel: DF.Literal["", "Email", "Phone", "Other"]
@@ -159,7 +161,20 @@ def validate_subscription_details(self):
)
def before_submit(self):
- self.outstanding_amount = self.grand_total
+ if (
+ self.currency != self.party_account_currency
+ and self.party_account_currency == get_company_currency(self.company)
+ ):
+ invoice = frappe.get_value(
+ self.reference_doctype, self.reference_name, ["grand_total", "base_grand_total"]
+ )
+ self.outstanding_amount = flt(
+ self.grand_total / invoice.grand_total * invoice.base_grand_total,
+ self.precision("outstanding_amount"),
+ )
+
+ else:
+ self.outstanding_amount = self.grand_total
if self.payment_request_type == "Outward":
self.status = "Initiated"
@@ -287,13 +302,35 @@ def create_payment_entry(self, submit=True):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
+ if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
+ party_account = ref_doc.debit_to
+ elif self.reference_doctype == "Purchase Invoice":
+ party_account = ref_doc.credit_to
+ else:
+ party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
+
+ party_account_currency = (
+ self.get("party_account_currency")
+ or ref_doc.get("party_account_currency")
+ or get_account_currency(party_account)
+ )
+
+ bank_amount = self.outstanding_amount
+
+ if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
+ total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
+ base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
+ party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
+ else:
+ party_amount = self.outstanding_amount
+
# outstanding amount is already in Part's account currency
payment_entry = get_payment_entry(
self.reference_doctype,
self.reference_name,
- party_amount=self.outstanding_amount,
+ party_amount=party_amount,
bank_account=self.payment_account,
- bank_amount=self.outstanding_amount,
+ bank_amount=bank_amount,
created_from_payment_request=True,
)
@@ -554,7 +591,8 @@ def make_payment_request(**args):
"payment_account": gateway_account.get("payment_account"),
"payment_channel": gateway_account.get("payment_channel"),
"payment_request_type": args.get("payment_request_type"),
- "currency": party_account_currency, # consistent with PE
+ "currency": ref_doc.currency,
+ "party_account_currency": party_account_currency,
"grand_total": grand_total,
"mode_of_payment": args.mode_of_payment,
"email_to": args.recipient_id or ref_doc.owner,
@@ -614,8 +652,12 @@ def get_amount(ref_doc, payment_account=None):
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"):
- # always get base amount to create a payment request (to match with PE)
- grand_total = flt(ref_doc.base_rounded_total) or flt(ref_doc.base_grand_total)
+ if ref_doc.party_account_currency == ref_doc.currency:
+ grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
+ else:
+ grand_total = flt(
+ 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:
From 79840a167499435e185db347738b4dd7a0442d29 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 16 Sep 2024 16:54:50 +0530
Subject: [PATCH 45/60] fix: party amount for creating payment entry
---
.../doctype/payment_request/payment_request.py | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 969071e95f02..62cc85e33e4e 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -165,11 +165,17 @@ def before_submit(self):
self.currency != self.party_account_currency
and self.party_account_currency == get_company_currency(self.company)
):
+ # set outstanding amount in party account currency
invoice = frappe.get_value(
- self.reference_doctype, self.reference_name, ["grand_total", "base_grand_total"]
+ self.reference_doctype,
+ self.reference_name,
+ ["rounded_total", "grand_total", "base_rounded_total", "base_grand_total"],
+ as_dict=1,
)
+ grand_total = invoice.get("rounded_total") or invoice.get("grand_total")
+ base_grand_total = invoice.get("base_rounded_total") or invoice.get("base_grand_total")
self.outstanding_amount = flt(
- self.grand_total / invoice.grand_total * invoice.base_grand_total,
+ self.grand_total / grand_total * base_grand_total,
self.precision("outstanding_amount"),
)
@@ -315,14 +321,12 @@ def create_payment_entry(self, submit=True):
or get_account_currency(party_account)
)
- bank_amount = self.outstanding_amount
+ bank_amount = self.grand_total
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- total = ref_doc.get("rounded_total") or ref_doc.get("grand_total")
- base_total = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
- party_amount = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
- else:
party_amount = self.outstanding_amount
+ else:
+ party_amount = self.grand_total
# outstanding amount is already in Part's account currency
payment_entry = get_payment_entry(
From 3cec6f0cf36dfddcf0b9202f910df74cd3f4945c Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Mon, 16 Sep 2024 17:06:17 +0530
Subject: [PATCH 46/60] fix: allow for proportional amount paid by bank
---
.../accounts/doctype/payment_request/payment_request.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 62cc85e33e4e..dbdd77c39fbb 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -321,12 +321,11 @@ def create_payment_entry(self, submit=True):
or get_account_currency(party_account)
)
- bank_amount = self.grand_total
+ party_amount = bank_amount = self.outstanding_amount
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
- party_amount = self.outstanding_amount
- else:
- party_amount = self.grand_total
+ exchange_rate = ref_doc.get("conversion_rate")
+ bank_amount = flt(self.outstanding_amount / exchange_rate, self.precision("grand_total"))
# outstanding amount is already in Part's account currency
payment_entry = get_payment_entry(
From 4416fb72247d1855dcec2e1d141f3887c9b0082c Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 16 Sep 2024 18:03:54 +0530
Subject: [PATCH 47/60] fix: Changed field order in Payment Request
---
.../payment_request/payment_request.json | 7 ++++---
.../doctype/payment_request/payment_request.py | 17 ++---------------
2 files changed, 6 insertions(+), 18 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 5a1652f6db27..36ef7a59ca8f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -20,10 +20,10 @@
"reference_name",
"transaction_details",
"grand_total",
- "outstanding_amount",
+ "currency",
"is_a_subscription",
"column_break_18",
- "currency",
+ "outstanding_amount",
"party_account_currency",
"subscription_section",
"subscription_plans",
@@ -411,6 +411,7 @@
},
{
"depends_on": "eval: doc.docstatus === 1",
+ "description": "Amount in party's bank account currency",
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
"in_preview": 1,
@@ -442,7 +443,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-09-16 06:34:36.517077",
+ "modified": "2024-09-16 17:50:54.440090",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index dbdd77c39fbb..77fdd37fec74 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -35,12 +35,9 @@ class PaymentRequest(Document):
from typing import TYPE_CHECKING
if TYPE_CHECKING:
+ from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import SubscriptionPlanDetail
from frappe.types import DF
- from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import (
- SubscriptionPlanDetail,
- )
-
account: DF.ReadOnly | None
amended_from: DF.Link | None
bank: DF.Link | None
@@ -75,17 +72,7 @@ class PaymentRequest(Document):
project: DF.Link | None
reference_doctype: DF.Link | None
reference_name: DF.DynamicLink | None
- status: DF.Literal[
- "",
- "Draft",
- "Requested",
- "Initiated",
- "Partially Paid",
- "Payment Ordered",
- "Paid",
- "Failed",
- "Cancelled",
- ]
+ status: DF.Literal["", "Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid", "Failed", "Cancelled"]
subject: DF.Data | None
subscription_plans: DF.Table[SubscriptionPlanDetail]
swift_number: DF.ReadOnly | None
From aa93c8bc2f347d8abd5f5ee4a1b804e4c9fb827e Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 16 Sep 2024 18:57:05 +0530
Subject: [PATCH 48/60] fix: Minor refactor in Payment Entry Reference table
data
---
erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 +
.../payment_entry_reference/payment_entry_reference.json | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 86924a869403..3e5d643e775e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1010,6 +1010,7 @@ frappe.ui.form.on("Payment Entry", {
c.outstanding_amount = d.outstanding_amount;
c.bill_no = d.bill_no;
c.payment_term = d.payment_term;
+ c.payment_term_outstanding = d.payment_term_outstanding;
c.allocated_amount = d.allocated_amount;
c.account = d.account;
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 365058c0bca7..f5d39c134b50 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -138,7 +138,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.payment_request",
+ "depends_on": "eval: doc.payment_request && doc.payment_request_outstanding",
"fieldname": "payment_request_outstanding",
"fieldtype": "Float",
"is_virtual": 1,
@@ -149,7 +149,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-09-16 13:44:14.289408",
+ "modified": "2024-09-16 18:11:50.019343",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
From e92f44ccea8f37aedf4ff147850132921a7e0aa9 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 16 Sep 2024 20:10:49 +0530
Subject: [PATCH 49/60] test: Added test cases for allow Payment at `Partially
Paid` status for PR
---
.../payment_request/test_payment_request.py | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 8aa169fa3a24..67f048f304ff 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -415,3 +415,30 @@ def test_conversion_on_foreign_currency_accounts(self):
self.assertEqual(pe.paid_amount, 800)
self.assertEqual(pe.base_received_amount, 800)
self.assertEqual(pe.received_amount, 10)
+
+ def test_multiple_payment_if_partially_paid(self):
+ so = make_sales_order(currency="INR", qty=1, rate=1000)
+
+ pr = make_payment_request(
+ dt="Sales Order",
+ dn=so.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
+
+ pe = pr.create_payment_entry(submit=False)
+ pe.paid_amount = 200
+ pe.references[0].allocated_amount = 200
+ pe.submit()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Partially Paid")
+
+ pe = pr.create_payment_entry(submit=False)
+ pe.paid_amount = 800
+ pe.references[0].allocated_amount = 800
+ pe.submit()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Paid")
From 3cb9cbc20797658c768554bcf9c422a11a0ff6d5 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Mon, 16 Sep 2024 20:28:30 +0530
Subject: [PATCH 50/60] test: Update partial paid status test case
---
.../payment_request/test_payment_request.py | 30 ++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 67f048f304ff..ef616b7c3ee9 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -416,7 +416,7 @@ def test_conversion_on_foreign_currency_accounts(self):
self.assertEqual(pe.base_received_amount, 800)
self.assertEqual(pe.received_amount, 10)
- def test_multiple_payment_if_partially_paid(self):
+ def test_multiple_payment_if_partially_paid_for_same_currency(self):
so = make_sales_order(currency="INR", qty=1, rate=1000)
pr = make_payment_request(
@@ -442,3 +442,31 @@ def test_multiple_payment_if_partially_paid(self):
pr.load_from_db()
self.assertEqual(pr.status, "Paid")
+
+ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
+ si = create_sales_invoice(currency="USD", conversion_rate=50, qty=1, rate=100)
+
+ pr = make_payment_request(
+ dt="Sales Invoice",
+ dn=si.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
+
+ # 50 USD -> 5000 INR
+ pe = pr.create_payment_entry(submit=False)
+ pe.paid_amount = 2000
+ pe.references[0].allocated_amount = 2000
+ pe.submit()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Partially Paid")
+
+ pe = pr.create_payment_entry(submit=False)
+ pe.paid_amount = 3000
+ pe.references[0].allocated_amount = 3000
+ pe.submit()
+ pr.load_from_db()
+
+ self.assertEqual(pr.status, "Paid")
From f13cc62977688990895176ca613e3ee3f2f2bbf5 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Wed, 18 Sep 2024 10:06:55 +0530
Subject: [PATCH 51/60] test: Update test case for same currency PR
---
.../payment_request/payment_request.py | 17 +++++-
.../payment_request/test_payment_request.py | 54 +++++++++++++++----
2 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 77fdd37fec74..dbdd77c39fbb 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -35,9 +35,12 @@ class PaymentRequest(Document):
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import SubscriptionPlanDetail
from frappe.types import DF
+ from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import (
+ SubscriptionPlanDetail,
+ )
+
account: DF.ReadOnly | None
amended_from: DF.Link | None
bank: DF.Link | None
@@ -72,7 +75,17 @@ class PaymentRequest(Document):
project: DF.Link | None
reference_doctype: DF.Link | None
reference_name: DF.DynamicLink | None
- status: DF.Literal["", "Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid", "Failed", "Cancelled"]
+ status: DF.Literal[
+ "",
+ "Draft",
+ "Requested",
+ "Initiated",
+ "Partially Paid",
+ "Payment Ordered",
+ "Paid",
+ "Failed",
+ "Cancelled",
+ ]
subject: DF.Data | None
subscription_plans: DF.Table[SubscriptionPlanDetail]
swift_number: DF.ReadOnly | None
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index ef616b7c3ee9..a571bd9d2c5a 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
+import re
import unittest
from unittest.mock import patch
@@ -336,8 +337,8 @@ def test_payment_entry(self):
gl_entries = frappe.db.sql(
"""select account, debit, credit, against_voucher
- from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""",
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""",
pe.name,
as_dict=1,
)
@@ -427,21 +428,46 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
return_doc=1,
)
+ self.assertEqual(pr.grand_total, 1000)
+ self.assertEqual(pr.outstanding_amount, pr.grand_total)
+ self.assertEqual(pr.party_account_currency, pr.currency) # INR
+ self.assertEqual(pr.status, "Requested")
+
+ # to make partial payment
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 200
pe.references[0].allocated_amount = 200
pe.submit()
- pr.load_from_db()
- self.assertEqual(pr.status, "Partially Paid")
+ self.assertEqual(pe.references[0].payment_request, pr.name)
- pe = pr.create_payment_entry(submit=False)
- pe.paid_amount = 800
- pe.references[0].allocated_amount = 800
- pe.submit()
pr.load_from_db()
+ self.assertEqual(pr.status, "Partially Paid")
+ self.assertEqual(pr.outstanding_amount, 800)
+ self.assertEqual(pr.grand_total, 1000)
+ pe = pr.create_payment_entry()
+ self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
+ self.assertEqual(pe.references[0].allocated_amount, 800)
+ self.assertEqual(pe.references[0].outstanding_amount, 800)
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+
+ pr.load_from_db()
self.assertEqual(pr.status, "Paid")
+ self.assertEqual(pr.outstanding_amount, 0)
+ self.assertEqual(pr.grand_total, 1000)
+
+ # creating a more payment Request must not allowed
+ 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,
+ )
def test_multiple_payment_if_partially_paid_for_multi_currency(self):
si = create_sales_invoice(currency="USD", conversion_rate=50, qty=1, rate=100)
@@ -454,7 +480,13 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
return_doc=1,
)
- # 50 USD -> 5000 INR
+ # 100 USD -> 5000 INR
+ self.assertEqual(pr.grand_total, 100)
+ self.assertEqual(pr.outstanding_amount, 5000)
+ self.assertEqual(pr.currency, "USD")
+ self.assertEqual(pr.party_account_currency, "INR")
+ self.assertEqual(pr.status, "Requested")
+
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 2000
pe.references[0].allocated_amount = 2000
@@ -462,6 +494,8 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
pr.load_from_db()
self.assertEqual(pr.status, "Partially Paid")
+ self.assertEqual(pr.outstanding_amount, 3000)
+ self.assertEqual(pr.grand_total, 100)
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 3000
@@ -470,3 +504,5 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
pr.load_from_db()
self.assertEqual(pr.status, "Paid")
+ self.assertEqual(pr.outstanding_amount, 0)
+ self.assertEqual(pr.grand_total, 100)
From f809548927457df0557e34b50c4feeba5ec54594 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 11:27:55 +0530
Subject: [PATCH 52/60] refactor: Wider the `msgprint` dialog for after save PE
---
erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 3e5d643e775e..b7ff852dcdee 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1673,6 +1673,7 @@ frappe.ui.form.on("Payment Entry", {
title: __("Unset Matched Payment Request"),
message: COLUMN_LABEL.concat(matched_payment_requests),
as_table: true,
+ wide:true,
primary_action: {
label: __("Allocate Payment Request"),
action() {
From 133791dd01ad3fd0ca81ef811763608b3ab5f770 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 11:32:52 +0530
Subject: [PATCH 53/60] test: Update PR test cases
---
.../payment_request/test_payment_request.py | 35 +++++++++++++++----
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index a571bd9d2c5a..9b2b51699e7d 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -446,10 +446,14 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
self.assertEqual(pr.outstanding_amount, 800)
self.assertEqual(pr.grand_total, 1000)
+ # complete payment
pe = pr.create_payment_entry()
+
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
self.assertEqual(pe.references[0].allocated_amount, 800)
- self.assertEqual(pe.references[0].outstanding_amount, 800)
+ self.assertEqual(
+ pe.references[0].outstanding_amount, 800
+ ) # for orders it should be same as allocated amount
self.assertEqual(pe.references[0].payment_request, pr.name)
pr.load_from_db()
@@ -487,22 +491,39 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
self.assertEqual(pr.party_account_currency, "INR")
self.assertEqual(pr.status, "Requested")
+ # to make partial payment
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 2000
pe.references[0].allocated_amount = 2000
pe.submit()
- pr.load_from_db()
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+
+ pr.load_from_db()
self.assertEqual(pr.status, "Partially Paid")
self.assertEqual(pr.outstanding_amount, 3000)
self.assertEqual(pr.grand_total, 100)
- pe = pr.create_payment_entry(submit=False)
- pe.paid_amount = 3000
- pe.references[0].allocated_amount = 3000
- pe.submit()
- pr.load_from_db()
+ # complete payment
+ pe = pr.create_payment_entry()
+ self.assertEqual(pe.paid_amount, 3000) # paid amount set from pr's outstanding amount
+ self.assertEqual(pe.references[0].allocated_amount, 3000)
+ self.assertEqual(pe.references[0].outstanding_amount, 0) # for Invoices it will zero
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+ pr.load_from_db()
self.assertEqual(pr.status, "Paid")
self.assertEqual(pr.outstanding_amount, 0)
self.assertEqual(pr.grand_total, 100)
+
+ # creating a more payment Request must not allowed
+ self.assertRaisesRegex(
+ frappe.exceptions.ValidationError,
+ re.compile(r"Payment Request is already created"),
+ make_payment_request,
+ dt="Sales Invoice",
+ dn=si.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
From d048123c0ed906b212d2b44418fe97780a708572 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 11:48:32 +0530
Subject: [PATCH 54/60] chore: Remove dirty lines
---
erpnext/accounts/doctype/payment_entry/payment_entry.js | 3 +--
erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index b7ff852dcdee..c4f14d71036d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -203,7 +203,6 @@ frappe.ui.form.on("Payment Entry", {
};
});
- // todo: fetch payment term outstanding amount also
frm.add_fetch(
"payment_request",
"outstanding_amount",
@@ -1673,7 +1672,7 @@ frappe.ui.form.on("Payment Entry", {
title: __("Unset Matched Payment Request"),
message: COLUMN_LABEL.concat(matched_payment_requests),
as_table: true,
- wide:true,
+ wide: true,
primary_action: {
label: __("Allocate Payment Request"),
action() {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f4864d059dfa..572d39db4b09 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -333,7 +333,7 @@ def validate_allocated_amount(self):
def validate_allocated_amount_as_per_payment_request(self):
"""
- Allocated amount should not be greater than the outstanding amount of the Payment Request.f
+ Allocated amount should not be greater than the outstanding amount of the Payment Request.
"""
if not self.references:
return
From c370e725c3fd853ccdb5330b84e957d60b7b3eca Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 12:19:16 +0530
Subject: [PATCH 55/60] test: Checking `Advance Payment Status`
---
.../payment_request/test_payment_request.py | 27 ++++++++++++-------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 9b2b51699e7d..f88d9e3dc30d 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -420,6 +420,8 @@ def test_conversion_on_foreign_currency_accounts(self):
def test_multiple_payment_if_partially_paid_for_same_currency(self):
so = make_sales_order(currency="INR", qty=1, rate=1000)
+ self.assertEqual(so.advance_payment_status, "Not Requested")
+
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
@@ -433,6 +435,9 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
self.assertEqual(pr.party_account_currency, pr.currency) # INR
self.assertEqual(pr.status, "Requested")
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Requested")
+
# to make partial payment
pe = pr.create_payment_entry(submit=False)
pe.paid_amount = 200
@@ -441,6 +446,9 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
self.assertEqual(pe.references[0].payment_request, pr.name)
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Partially Paid")
+
pr.load_from_db()
self.assertEqual(pr.status, "Partially Paid")
self.assertEqual(pr.outstanding_amount, 800)
@@ -451,11 +459,12 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
self.assertEqual(pe.references[0].allocated_amount, 800)
- self.assertEqual(
- pe.references[0].outstanding_amount, 800
- ) # for orders it should be same as allocated amount
+ self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
self.assertEqual(pe.references[0].payment_request, pr.name)
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Fully Paid")
+
pr.load_from_db()
self.assertEqual(pr.status, "Paid")
self.assertEqual(pr.outstanding_amount, 0)
@@ -474,11 +483,11 @@ def test_multiple_payment_if_partially_paid_for_same_currency(self):
)
def test_multiple_payment_if_partially_paid_for_multi_currency(self):
- si = create_sales_invoice(currency="USD", conversion_rate=50, qty=1, rate=100)
+ pi = make_purchase_invoice(currency="USD", conversion_rate=50, qty=1, rate=100)
pr = make_payment_request(
- dt="Sales Invoice",
- dn=si.name,
+ dt="Purchase Invoice",
+ dn=pi.name,
mute_email=1,
submit_doc=1,
return_doc=1,
@@ -489,7 +498,7 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
self.assertEqual(pr.outstanding_amount, 5000)
self.assertEqual(pr.currency, "USD")
self.assertEqual(pr.party_account_currency, "INR")
- self.assertEqual(pr.status, "Requested")
+ self.assertEqual(pr.status, "Initiated")
# to make partial payment
pe = pr.create_payment_entry(submit=False)
@@ -521,8 +530,8 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
frappe.exceptions.ValidationError,
re.compile(r"Payment Request is already created"),
make_payment_request,
- dt="Sales Invoice",
- dn=si.name,
+ dt="Purchase Invoice",
+ dn=pi.name,
mute_email=1,
submit_doc=1,
return_doc=1,
From 9fa760fc700944ef5817293bdf7931bdc6847d0d Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 13:06:40 +0530
Subject: [PATCH 56/60] fix: formatting update
---
.../accounts/doctype/payment_request/test_payment_request.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index f88d9e3dc30d..0f64d64357e7 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -337,8 +337,8 @@ def test_payment_entry(self):
gl_entries = frappe.db.sql(
"""select account, debit, credit, against_voucher
- from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""",
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""",
pe.name,
as_dict=1,
)
From b83931989bae71ba77d125f7a0487ba0bad911ef Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 16:20:46 +0530
Subject: [PATCH 57/60] fix: Use `flt` where doing subtraction
---
.../doctype/payment_entry/payment_entry.py | 53 +++++++++++++------
.../payment_request/payment_request.py | 13 +++--
2 files changed, 44 insertions(+), 22 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 572d39db4b09..8a7115ebb805 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1758,12 +1758,13 @@ def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocat
return
# calculating outstanding amounts
+ precision = self.precision("paid_amount")
total_positive_outstanding_including_order = 0
total_negative_outstanding = 0
- paid_amount -= sum(flt(d.amount, self.precision("paid_amount")) for d in self.deductions)
+ paid_amount -= sum(flt(d.amount, precision) for d in self.deductions)
for ref in self.references:
- reference_outstanding_amount = flt(ref.outstanding_amount, self.precision("paid_amount"))
+ reference_outstanding_amount = flt(ref.outstanding_amount, precision)
abs_outstanding_amount = abs(reference_outstanding_amount)
if reference_outstanding_amount > 0:
@@ -1780,7 +1781,9 @@ def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocat
self.payment_type == "Pay" and self.party_type in ("Supplier", "Employee")
):
if total_positive_outstanding_including_order > paid_amount:
- remaining_outstanding = total_positive_outstanding_including_order - paid_amount
+ remaining_outstanding = flt(
+ total_positive_outstanding_including_order - paid_amount, precision
+ )
allocated_negative_outstanding = min(remaining_outstanding, total_negative_outstanding)
allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
@@ -1804,7 +1807,7 @@ def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocat
return
else:
- allocated_positive_outstanding = total_negative_outstanding - paid_amount
+ allocated_positive_outstanding = flt(total_negative_outstanding - paid_amount, precision)
allocated_negative_outstanding = paid_amount + min(
total_positive_outstanding_including_order, allocated_positive_outstanding
)
@@ -1815,10 +1818,14 @@ def _allocation_to_unset_pr_row(
):
if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
row.allocated_amount = min(allocated_positive_outstanding, outstanding_amount)
- allocated_positive_outstanding -= flt(row.allocated_amount)
+ allocated_positive_outstanding = flt(
+ allocated_positive_outstanding - row.allocated_amount, precision
+ )
elif outstanding_amount < 0 and allocated_negative_outstanding:
row.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
- allocated_negative_outstanding -= abs(flt(row.allocated_amount))
+ allocated_negative_outstanding = flt(
+ allocated_negative_outstanding - abs(row.allocated_amount), precision
+ )
return allocated_positive_outstanding, allocated_negative_outstanding
# allocate amount based on `paid_amount` is changed or not
@@ -1831,7 +1838,7 @@ def _allocation_to_unset_pr_row(
allocated_negative_outstanding,
)
- allocate_open_payment_requests_to_references(self.references)
+ allocate_open_payment_requests_to_references(self.references, self.precision("paid_amount"))
else:
payment_request_outstanding_amounts = (
@@ -1862,9 +1869,15 @@ def _allocation_to_unset_pr_row(
# update amounts to track allocation
allocated_amount = flt(ref.allocated_amount)
- allocated_positive_outstanding -= allocated_amount
- remaining_references_allocated_amounts[key] -= allocated_amount
- payment_request_outstanding_amounts[ref.payment_request] -= allocated_amount
+ allocated_positive_outstanding = flt(
+ allocated_positive_outstanding - allocated_amount, precision
+ )
+ remaining_references_allocated_amounts[key] = flt(
+ remaining_references_allocated_amounts[key] - allocated_amount, precision
+ )
+ payment_request_outstanding_amounts[ref.payment_request] = flt(
+ payment_request_outstanding_amounts[ref.payment_request] - allocated_amount, precision
+ )
elif reference_outstanding_amount < 0 and allocated_negative_outstanding:
# allocate amount according to outstanding amounts
@@ -1878,10 +1891,13 @@ def _allocation_to_unset_pr_row(
# update amounts to track allocation
allocated_amount = abs(flt(ref.allocated_amount))
- allocated_negative_outstanding -= allocated_amount
+ allocated_negative_outstanding = flt(
+ allocated_negative_outstanding - allocated_amount, precision
+ )
remaining_references_allocated_amounts[key] += allocated_amount # negative amount
- payment_request_outstanding_amounts[ref.payment_request] -= allocated_amount
-
+ payment_request_outstanding_amounts[ref.payment_request] = flt(
+ payment_request_outstanding_amounts[ref.payment_request] - allocated_amount, precision
+ )
# Re allocate amount to those references which have no PR (Lower priority)
for ref in self.references:
if ref.payment_request:
@@ -2865,7 +2881,7 @@ def get_payment_entry(
# If PE is created from PR directly, then no need to find open PRs for the references
if not created_from_payment_request:
- allocate_open_payment_requests_to_references(pe.references)
+ allocate_open_payment_requests_to_references(pe.references, pe.precision("paid_amount"))
return pe
@@ -2916,7 +2932,7 @@ def get_open_payment_requests_for_references(references=None):
return reference_payment_requests
-def allocate_open_payment_requests_to_references(references=None):
+def allocate_open_payment_requests_to_references(references=None, precision=None):
"""
Allocate unpaid Payment Requests to the references. \n
---
@@ -2954,6 +2970,9 @@ def allocate_open_payment_requests_to_references(references=None):
if not references_open_payment_requests:
return
+ if not precision:
+ precision = references[0].precision("allocated_amount")
+
# to manage new rows
row_number = 1
MOVE_TO_NEXT_ROW = 1
@@ -2993,7 +3012,7 @@ def allocate_open_payment_requests_to_references(references=None):
# split the reference row to allocate the remaining amount
del reference_payment_requests[payment_request]
row.allocated_amount = pr_outstanding_amount
- allocated_amount -= pr_outstanding_amount
+ allocated_amount = flt(allocated_amount - pr_outstanding_amount, precision)
# set the remaining amount to the next row
while allocated_amount:
@@ -3028,7 +3047,7 @@ def allocate_open_payment_requests_to_references(references=None):
break
else:
- allocated_amount -= pr_outstanding_amount
+ allocated_amount = flt(allocated_amount - pr_outstanding_amount, precision)
del reference_payment_requests[payment_request]
row_number += MOVE_TO_NEXT_ROW
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index dbdd77c39fbb..87cd23c25ba5 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -472,6 +472,7 @@ def _allocate_payment_request_to_pe_references(self, references):
references[0].payment_request = self.name
return
+ precision = references[0].precision("allocated_amount")
outstanding_amount = self.outstanding_amount
# to manage rows
@@ -497,10 +498,10 @@ def _allocate_payment_request_to_pe_references(self, references):
row.payment_request = self.name
if row.allocated_amount <= outstanding_amount:
- outstanding_amount -= row.allocated_amount
+ outstanding_amount = flt(outstanding_amount - row.allocated_amount, precision)
row_number += MOVE_TO_NEXT_ROW
else:
- remaining_allocated_amount = row.allocated_amount - outstanding_amount
+ remaining_allocated_amount = flt(row.allocated_amount - outstanding_amount, precision)
row.allocated_amount = outstanding_amount
outstanding_amount = 0
@@ -744,6 +745,8 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
if not references:
return
+ precision = references[0].precision("allocated_amount")
+
referenced_payment_requests = frappe.get_all(
"Payment Request",
filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]},
@@ -762,12 +765,12 @@ def update_payment_requests_as_per_pe_references(references=None, cancel=False):
continue
payment_request = referenced_payment_requests[ref.payment_request]
+ pr_outstanding = payment_request["outstanding_amount"]
# update outstanding amount
new_outstanding_amount = flt(
- payment_request["outstanding_amount"] + ref.allocated_amount
- if cancel
- else payment_request["outstanding_amount"] - ref.allocated_amount
+ pr_outstanding + ref.allocated_amount if cancel else pr_outstanding - ref.allocated_amount,
+ precision,
)
# to handle same payment request for the multiple allocations
From b7cb361a717bc559ca6c6111670034deb0da063c Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 16:23:13 +0530
Subject: [PATCH 58/60] test: PR test case with Payment Term for same currency
---
.../payment_request/test_payment_request.py | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 0f64d64357e7..96a024253c6b 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -8,6 +8,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -536,3 +537,50 @@ def test_multiple_payment_if_partially_paid_for_multi_currency(self):
submit_doc=1,
return_doc=1,
)
+
+ def test_single_payment_with_payment_term_for_same_currency(self):
+ create_payment_terms_template()
+
+ po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=20000)
+ po.payment_terms_template = "Test Receivable Template" # 84.746 and 15.254
+ po.save()
+ po.submit()
+
+ self.assertEqual(po.advance_payment_status, "Not Initiated")
+
+ pr = make_payment_request(
+ dt="Purchase Order",
+ dn=po.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
+
+ self.assertEqual(pr.grand_total, 20000)
+ self.assertEqual(pr.outstanding_amount, pr.grand_total)
+ self.assertEqual(pr.party_account_currency, pr.currency) # INR
+ self.assertEqual(pr.status, "Initiated")
+
+ po.load_from_db()
+ self.assertEqual(po.advance_payment_status, "Initiated")
+
+ pe = pr.create_payment_entry()
+
+ self.assertEqual(len(pe.references), 2)
+ self.assertEqual(pe.paid_amount, 20000)
+
+ # check 1st payment term
+ self.assertEqual(pe.references[0].allocated_amount, 16949.2)
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+
+ # check 2nd payment term
+ self.assertEqual(pe.references[1].allocated_amount, 3050.8)
+ self.assertEqual(pe.references[1].payment_request, pr.name)
+
+ po.load_from_db()
+ self.assertEqual(po.advance_payment_status, "Fully Paid")
+
+ pr.load_from_db()
+ self.assertEqual(pr.status, "Paid")
+ self.assertEqual(pr.outstanding_amount, 0)
+ self.assertEqual(pr.grand_total, 20000)
From 943e1b7b988bb10653e64bbf5864e1cf365884ef Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 18:07:59 +0530
Subject: [PATCH 59/60] fix: remove redundant `flt`
---
erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 8a7115ebb805..d688b1fa3e07 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1764,7 +1764,7 @@ def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocat
paid_amount -= sum(flt(d.amount, precision) for d in self.deductions)
for ref in self.references:
- reference_outstanding_amount = flt(ref.outstanding_amount, precision)
+ reference_outstanding_amount = ref.outstanding_amount
abs_outstanding_amount = abs(reference_outstanding_amount)
if reference_outstanding_amount > 0:
@@ -1868,7 +1868,7 @@ def _allocation_to_unset_pr_row(
ref.allocated_amount = min(outstanding_amounts)
# update amounts to track allocation
- allocated_amount = flt(ref.allocated_amount)
+ allocated_amount = ref.allocated_amount
allocated_positive_outstanding = flt(
allocated_positive_outstanding - allocated_amount, precision
)
@@ -1890,7 +1890,7 @@ def _allocation_to_unset_pr_row(
ref.allocated_amount = min(outstanding_amounts) * -1
# update amounts to track allocation
- allocated_amount = abs(flt(ref.allocated_amount))
+ allocated_amount = abs(ref.allocated_amount)
allocated_negative_outstanding = flt(
allocated_negative_outstanding - allocated_amount, precision
)
From f1d89460000e43451419b0cca7a3bb8f6b786a60 Mon Sep 17 00:00:00 2001
From: Abdeali Chharchhoda
Date: Fri, 20 Sep 2024 18:20:02 +0530
Subject: [PATCH 60/60] test: Add test cases for PR
---
.../payment_request/test_payment_request.py | 86 +++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 96a024253c6b..0b2cdef8b541 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -584,3 +584,89 @@ def test_single_payment_with_payment_term_for_same_currency(self):
self.assertEqual(pr.status, "Paid")
self.assertEqual(pr.outstanding_amount, 0)
self.assertEqual(pr.grand_total, 20000)
+
+ def test_single_payment_with_payment_term_for_multi_currency(self):
+ create_payment_terms_template()
+
+ si = create_sales_invoice(do_not_save=1, currency="USD", qty=1, rate=200, conversion_rate=50)
+ si.payment_terms_template = "Test Receivable Template" # 84.746 and 15.254
+ si.save()
+ si.submit()
+
+ pr = make_payment_request(
+ dt="Sales Invoice",
+ dn=si.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
+
+ # 200 USD -> 10000 INR
+ self.assertEqual(pr.grand_total, 200)
+ self.assertEqual(pr.outstanding_amount, 10000)
+ self.assertEqual(pr.currency, "USD")
+ self.assertEqual(pr.party_account_currency, "INR")
+ self.assertEqual(pr.status, "Requested")
+
+ pe = pr.create_payment_entry()
+ self.assertEqual(len(pe.references), 2)
+ self.assertEqual(pe.paid_amount, 10000)
+
+ # check 1st payment term
+ # convert it via dollar and conversion_rate
+ self.assertEqual(pe.references[0].allocated_amount, 8474.5) # multi currency conversion
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+
+ # check 2nd payment term
+ self.assertEqual(pe.references[1].allocated_amount, 1525.5) # multi currency conversion
+ self.assertEqual(pe.references[1].payment_request, pr.name)
+
+ pr.load_from_db()
+ self.assertEqual(pr.status, "Paid")
+ self.assertEqual(pr.outstanding_amount, 0)
+ self.assertEqual(pr.grand_total, 200)
+
+ def test_payment_cancel_process(self):
+ so = make_sales_order(currency="INR", qty=1, rate=1000)
+ self.assertEqual(so.advance_payment_status, "Not Requested")
+
+ pr = make_payment_request(
+ dt="Sales Order",
+ dn=so.name,
+ mute_email=1,
+ submit_doc=1,
+ return_doc=1,
+ )
+
+ self.assertEqual(pr.status, "Requested")
+ self.assertEqual(pr.grand_total, 1000)
+ self.assertEqual(pr.outstanding_amount, pr.grand_total)
+
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Requested")
+
+ pe = pr.create_payment_entry(submit=False)
+ pe.paid_amount = 800
+ pe.references[0].allocated_amount = 800
+ pe.submit()
+
+ self.assertEqual(pe.references[0].payment_request, pr.name)
+
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Partially Paid")
+
+ pr.load_from_db()
+ self.assertEqual(pr.status, "Partially Paid")
+ self.assertEqual(pr.outstanding_amount, 200)
+ self.assertEqual(pr.grand_total, 1000)
+
+ # cancelling PE
+ pe.cancel()
+
+ pr.load_from_db()
+ self.assertEqual(pr.status, "Requested")
+ self.assertEqual(pr.outstanding_amount, 1000)
+ self.assertEqual(pr.grand_total, 1000)
+
+ so.load_from_db()
+ self.assertEqual(so.advance_payment_status, "Requested")