Compare commits

...

9 Commits

Author SHA1 Message Date
Abdeali Chharchhoda
a5f0dfacb4 fix: replace locals with get_doc in set_query 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
2af7ed84cd refactor: replace sql query of matched_payment_requests to query builder 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
527c3e0b24 fix: changes as per review 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
b36fb8b218 fix: removed bug of set_advance_payment_status 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
cbed1d7826 fix: update set_advance_payment_status() logic 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
4f0541dc16 fix: replace round with flt 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
290dc7d2b2 fix: remove bug 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
4d1cb318dd chore: minor changes 2024-08-12 10:50:38 +05:30
Abdeali Chharchhoda
552c46db98 fix: multiple issues in Payment Request 2024-08-12 10:50:38 +05:30
8 changed files with 348 additions and 108 deletions

View File

@@ -166,6 +166,18 @@ frappe.ui.form.on("Payment Entry", {
}; };
}); });
frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
const row = frappe.get_doc(cdt, cdn);
return {
filters: {
docstatus: 1,
status: ["!=", "Paid"],
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
},
};
});
frm.set_query("sales_taxes_and_charges_template", function () { frm.set_query("sales_taxes_and_charges_template", function () {
return { return {
filters: { filters: {
@@ -1117,6 +1129,7 @@ frappe.ui.form.on("Payment Entry", {
}); });
frm.refresh_fields(); frm.refresh_fields();
if (frappe.flags.allocate_payment_amount) frm.call("set_matched_payment_requests");
frm.events.set_total_allocated_amount(frm); frm.events.set_total_allocated_amount(frm);
}, },
@@ -1706,8 +1719,15 @@ frappe.ui.form.on("Payment Entry Reference", {
} }
}, },
allocated_amount: function (frm) { allocated_amount: function (frm, cdt, cdn) {
frm.events.set_total_allocated_amount(frm); frm.events.set_total_allocated_amount(frm);
const row = frappe.get_doc(cdt, cdn);
if (row.payment_request || !row.reference_name || !row.reference_doctype || !row.allocated_amount)
return;
frm.call("set_matched_payment_request", { row_idx: row.idx });
}, },
references_remove: function (frm) { references_remove: function (frm) {

View File

@@ -7,8 +7,10 @@ from functools import reduce
import frappe import frappe
from frappe import ValidationError, _, qb, scrub, throw 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 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 import Case
from pypika.functions import Coalesce, Sum from pypika.functions import Coalesce, Sum
@@ -180,6 +182,9 @@ class PaymentEntry(AccountsController):
self.set_status() self.set_status()
self.set_total_in_words() self.set_total_in_words()
def before_save(self):
self.check_references_for_unset_payment_request()
def on_submit(self): def on_submit(self):
if self.difference_amount: if self.difference_amount:
frappe.throw(_("Difference Amount must be zero")) frappe.throw(_("Difference Amount must be zero"))
@@ -187,7 +192,8 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
self.update_payment_schedule() self.update_payment_schedule()
self.set_payment_req_status() self.update_payment_requests()
self.update_references_advance_payment_status()
self.set_status() self.set_status()
def set_liability_account(self): def set_liability_account(self):
@@ -262,13 +268,26 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() self.update_advance_paid()
self.delink_advance_entry_references() self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1) self.update_payment_schedule(cancel=1)
self.set_payment_req_status() self.update_payment_requests(cancel=True)
self.update_references_advance_payment_status()
self.set_status() self.set_status()
def set_payment_req_status(self): def update_payment_requests(self, cancel=False):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status from erpnext.accounts.doctype.payment_request.payment_request import (
update_payment_requests_as_per_pe_references,
)
update_payment_req_status(self, None) 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): def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True) self.set_missing_ref_details(force=True)
@@ -308,6 +327,8 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Internal Transfer": if self.payment_type == "Internal Transfer":
return return
self.validate_allocated_amount_as_per_payment_request()
if self.party_type in ("Customer", "Supplier"): if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data() self.validate_allocated_amount_with_latest_data()
else: else:
@@ -320,6 +341,22 @@ class PaymentEntry(AccountsController):
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx)) frappe.throw(fail_message.format(d.idx))
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,
)
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(
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( def term_based_allocation_enabled_for_reference(
self, reference_doctype: str, reference_name: str self, reference_doctype: str, reference_name: str
) -> bool: ) -> bool:
@@ -1692,6 +1729,137 @@ class PaymentEntry(AccountsController):
return current_tax_fraction return current_tax_fraction
def check_references_for_unset_payment_request(self):
if not self.references:
return
matched_payment_requests = get_matched_payment_requests_of_references(
[row for row in self.references if not row.payment_request]
)
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. <br><br>")
message += _("<details><summary><strong>View Details</strong></summary><ul>")
for idx, pr in unset_pr_rows.items():
message += _("<li>Row #{0}: {1}</li>").format(idx, get_link_to_form("Payment Request", pr))
message += _("</ul></details>")
frappe.msgprint(
msg=message,
indicator="yellow",
)
@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
):
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_of_references([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 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
if row.reference_doctype and row.reference_name and row.allocated_amount
]
if not refs:
return
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)
)
# 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 {(pr.reference_doctype, pr.reference_name, pr.outstanding_amount): pr.name for pr in matched_prs}
def validate_inclusive_tax(tax, doc): def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range): def _on_previous_row_error(row_range):

View File

@@ -18,7 +18,8 @@
"allocated_amount", "allocated_amount",
"exchange_rate", "exchange_rate",
"exchange_gain_loss", "exchange_gain_loss",
"account" "account",
"payment_request"
], ],
"fields": [ "fields": [
{ {
@@ -120,12 +121,18 @@
"fieldname": "payment_type", "fieldname": "payment_type",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Payment Type" "label": "Payment Type"
},
{
"fieldname": "payment_request",
"fieldtype": "Link",
"label": "Payment Request",
"options": "Payment Request"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-04-05 09:44:08.310593", "modified": "2024-07-20 17:57:32.866780",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",

View File

@@ -25,6 +25,7 @@ class PaymentEntryReference(Document):
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data
parenttype: DF.Data parenttype: DF.Data
payment_request: DF.Link | None
payment_term: DF.Link | None payment_term: DF.Link | None
payment_type: DF.Data | None payment_type: DF.Data | None
reference_doctype: DF.Link reference_doctype: DF.Link

View File

@@ -52,8 +52,8 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
} }
if ( if (
(!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") && frm.doc.payment_request_type == "Outward" &&
frm.doc.status == "Initiated" ["Initiated", "Partially Paid"].includes(frm.doc.status)
) { ) {
frm.add_custom_button(__("Create Payment Entry"), function () { frm.add_custom_button(__("Create Payment Entry"), function () {
frappe.call({ frappe.call({

View File

@@ -21,6 +21,7 @@
"grand_total", "grand_total",
"is_a_subscription", "is_a_subscription",
"column_break_18", "column_break_18",
"outstanding_amount",
"currency", "currency",
"subscription_section", "subscription_section",
"subscription_plans", "subscription_plans",
@@ -133,7 +134,8 @@
"no_copy": 1, "no_copy": 1,
"options": "reference_doctype", "options": "reference_doctype",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "transaction_details", "fieldname": "transaction_details",
@@ -146,7 +148,8 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Amount", "label": "Amount",
"non_negative": 1, "non_negative": 1,
"options": "currency" "options": "currency",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@@ -400,13 +403,21 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 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, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2024-06-20 13:54:55.245774", "modified": "2024-07-23 19:02:07.754296",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Request", "name": "Payment Request",
@@ -444,4 +455,4 @@
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -49,6 +49,7 @@ class PaymentRequest(Document):
cost_center: DF.Link | None cost_center: DF.Link | None
currency: DF.Link | None currency: DF.Link | None
email_to: DF.Data | None email_to: DF.Data | None
failed_reason: DF.Data | None
grand_total: DF.Currency grand_total: DF.Currency
iban: DF.ReadOnly | None iban: DF.ReadOnly | None
is_a_subscription: DF.Check is_a_subscription: DF.Check
@@ -57,16 +58,17 @@ class PaymentRequest(Document):
mode_of_payment: DF.Link | None mode_of_payment: DF.Link | None
mute_email: DF.Check mute_email: DF.Check
naming_series: DF.Literal["ACC-PRQ-.YYYY.-"] naming_series: DF.Literal["ACC-PRQ-.YYYY.-"]
outstanding_amount: DF.Currency
party: DF.DynamicLink | None party: DF.DynamicLink | None
party_type: DF.Link | None party_type: DF.Link | None
payment_account: DF.ReadOnly | 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: DF.ReadOnly | None
payment_gateway_account: DF.Link | None payment_gateway_account: DF.Link | None
payment_order: DF.Link | None payment_order: DF.Link | None
payment_request_type: DF.Literal["Outward", "Inward"] payment_request_type: DF.Literal["Outward", "Inward"]
payment_url: DF.Data | None payment_url: DF.Data | None
print_format: DF.Literal print_format: DF.Literal[None]
project: DF.Link | None project: DF.Link | None
reference_doctype: DF.Link | None reference_doctype: DF.Link | None
reference_name: DF.DynamicLink | None reference_name: DF.DynamicLink | None
@@ -100,6 +102,12 @@ class PaymentRequest(Document):
frappe.throw(_("To create a Payment Request reference document is required")) frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self): def validate_payment_request_amount(self):
if self.grand_total == 0:
frappe.throw(
_("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
title=_("Invalid Amount"),
)
existing_payment_request_amount = flt( existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name) get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
) )
@@ -149,16 +157,9 @@ class PaymentRequest(Document):
).format(self.grand_total, amount) ).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): def before_submit(self):
self.outstanding_amount = self.grand_total
if self.payment_request_type == "Outward": if self.payment_request_type == "Outward":
self.status = "Initiated" self.status = "Initiated"
elif self.payment_request_type == "Inward": elif self.payment_request_type == "Inward":
@@ -173,6 +174,9 @@ class PaymentRequest(Document):
self.send_email() self.send_email()
self.make_communication_entry() self.make_communication_entry()
def on_submit(self):
self.update_reference_advance_payment_status()
def request_phone_payment(self): def request_phone_payment(self):
controller = _get_payment_gateway_controller(self.payment_gateway) controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount() request_amount = self.get_request_amount()
@@ -210,6 +214,7 @@ class PaymentRequest(Document):
def on_cancel(self): def on_cancel(self):
self.check_if_payment_entry_exists() self.check_if_payment_entry_exists()
self.set_as_cancelled() self.set_as_cancelled()
self.update_reference_advance_payment_status()
def make_invoice(self): def make_invoice(self):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
@@ -266,7 +271,7 @@ class PaymentRequest(Document):
def set_as_paid(self): def set_as_paid(self):
if self.payment_channel == "Phone": if self.payment_channel == "Phone":
self.db_set("status", "Paid") self.db_set({"status": "Paid", "outstanding_amount": 0})
else: else:
payment_entry = self.create_payment_entry() payment_entry = self.create_payment_entry()
@@ -290,11 +295,14 @@ class PaymentRequest(Document):
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account) 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: 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 = flt(self.outstanding_amount / total * base_total, self.precision("grand_total"))
else: else:
party_amount = self.grand_total party_amount = self.outstanding_amount
payment_entry = get_payment_entry( payment_entry = get_payment_entry(
self.reference_doctype, self.reference_doctype,
@@ -307,7 +315,6 @@ class PaymentRequest(Document):
payment_entry.update( payment_entry.update(
{ {
"mode_of_payment": self.mode_of_payment, "mode_of_payment": self.mode_of_payment,
"reference_no": self.name,
"reference_date": nowdate(), "reference_date": nowdate(),
"remarks": "Payment Entry against {} {} via Payment Request {}".format( "remarks": "Payment Entry against {} {} via Payment Request {}".format(
self.reference_doctype, self.reference_name, self.name self.reference_doctype, self.reference_name, self.name
@@ -315,6 +322,9 @@ class PaymentRequest(Document):
} }
) )
# Add reference of Payment Request
payment_entry.references[0].payment_request = self.name
# Update dimensions # Update dimensions
payment_entry.update( payment_entry.update(
{ {
@@ -323,14 +333,6 @@ class PaymentRequest(Document):
} }
) )
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
for dimension in get_accounting_dimensions(): for dimension in get_accounting_dimensions():
payment_entry.update({dimension: self.get(dimension)}) payment_entry.update({dimension: self.get(dimension)})
@@ -414,6 +416,14 @@ class PaymentRequest(Document):
return create_stripe_subscription(gateway_controller, data) return create_stripe_subscription(gateway_controller, data)
def update_reference_advance_payment_status(self):
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:
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
ref_doc.set_advance_payment_status()
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def make_payment_request(**args): def make_payment_request(**args):
@@ -455,11 +465,15 @@ def make_payment_request(**args):
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0}, {"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) existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount: if existing_payment_request_amount:
grand_total -= 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: if draft_payment_request:
frappe.db.set_value( frappe.db.set_value(
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False "Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
@@ -472,7 +486,6 @@ def make_payment_request(**args):
args["payment_request_type"] = ( args["payment_request_type"] = (
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward" "Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
) )
pr.update( pr.update(
{ {
"payment_gateway_account": gateway_account.get("name"), "payment_gateway_account": gateway_account.get("name"),
@@ -535,7 +548,6 @@ def get_amount(ref_doc, payment_account=None):
dt = ref_doc.doctype dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]: if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total) 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"]: elif dt in ["Sales Invoice", "Purchase Invoice"]:
if not ref_doc.get("is_pos"): if not ref_doc.get("is_pos"):
if ref_doc.party_account_currency == ref_doc.currency: if ref_doc.party_account_currency == ref_doc.currency:
@@ -560,24 +572,20 @@ def get_amount(ref_doc, payment_account=None):
def get_existing_payment_request_amount(ref_dt, ref_dn): 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 Return the total amount of Payment Requests against a reference document.
and get the summation of existing paid payment request for Phone payment channel.
""" """
existing_payment_request_amount = frappe.db.sql( PR = frappe.qb.DocType("Payment Request")
"""
select sum(grand_total) response = (
from `tabPayment Request` frappe.qb.from_(PR)
where .select(Sum(PR.grand_total))
reference_doctype = %s .where(PR.reference_doctype == ref_dt)
and reference_name = %s .where(PR.reference_name == ref_dn)
and docstatus = 1 .where(PR.docstatus == 1)
and (status != 'Paid' .run()
or (payment_channel = 'Phone'
and status = 'Paid'))
""",
(ref_dt, ref_dn),
) )
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
return response[0][0] if response else 0
def get_gateway_details(args): # nosemgrep def get_gateway_details(args): # nosemgrep
@@ -619,41 +627,75 @@ def make_payment_entry(docname):
return doc.create_payment_entry(submit=False).as_dict() return doc.create_payment_entry(submit=False).as_dict()
def update_payment_req_status(doc, method): def update_payment_requests_as_per_pe_references(references=None, cancel=False):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details if not references:
return
for ref in doc.references: payment_requests = frappe.get_all(
payment_request_name = frappe.db.get_value( "Payment Request",
"Payment Request", filters={"name": ["in", get_referenced_payment_requests(references)]},
{ fields=[
"reference_doctype": ref.reference_doctype, "name",
"reference_name": ref.reference_name, "grand_total",
"docstatus": 1, "outstanding_amount",
}, "payment_request_type",
],
)
payment_requests = {pr.name: pr for pr in payment_requests}
for ref in references:
if not ref.payment_request:
continue
payment_request = payment_requests[ref.payment_request]
# update outstanding amount
new_outstanding_amount = (
payment_request["outstanding_amount"] + ref.allocated_amount
if cancel
else payment_request["outstanding_amount"] - ref.allocated_amount
) )
if payment_request_name: if not cancel and new_outstanding_amount < 0:
ref_details = get_reference_details( frappe.throw(
ref.reference_doctype, msg=_(
ref.reference_name, "The allocated amount is greater than the outstanding amount of Payment Request {0}"
doc.party_account_currency, ).format(ref.payment_request),
doc.party_type, title=_("Invalid Allocated Amount"),
doc.party,
) )
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: # update status
status = "Paid" if new_outstanding_amount == payment_request["grand_total"]:
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount: status = "Initiated" if payment_request["payment_request_type"] == "Outward" else "Requested"
status = "Partially Paid" elif new_outstanding_amount == 0:
elif ref_details.outstanding_amount == ref_details.total_amount: status = "Paid"
if pay_req_doc.payment_request_type == "Outward": elif new_outstanding_amount > 0:
status = "Initiated" status = "Partially Paid"
elif pay_req_doc.payment_request_type == "Inward":
status = "Requested"
pay_req_doc.db_set("status", status) # update database
frappe.db.set_value(
"Payment Request",
ref.payment_request,
{"outstanding_amount": new_outstanding_amount, "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): def get_dummy_message(doc):

View File

@@ -1946,33 +1946,24 @@ class AccountsController(TransactionBase):
def set_advance_payment_status(self): def set_advance_payment_status(self):
new_status = None new_status = None
stati = frappe.get_all( paid_amount = frappe.get_value(
"Payment Request", doctype="Payment Request",
{ filters={
"reference_doctype": self.doctype, "reference_doctype": self.doctype,
"reference_name": self.name, "reference_name": self.name,
"docstatus": 1, "docstatus": 1,
}, },
pluck="status", fieldname="sum(grand_total - outstanding_amount)",
) )
if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
if not stati: if not paid_amount:
new_status = "Not Requested" if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
elif "Requested" in stati or "Failed" in stati: new_status = "Not Requested" if paid_amount is None else "Requested"
new_status = "Requested" elif self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
elif "Partially Paid" in stati: new_status = "Not Initiated" if paid_amount is None else "Initiated"
new_status = "Partially Paid" else:
elif "Paid" in stati: total_amount = self.get("rounded_total") or self.get("grand_total")
new_status = "Fully Paid" new_status = "Fully Paid" if paid_amount == total_amount else "Partially Paid"
if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
if not stati:
new_status = "Not Initiated"
elif "Initiated" in stati or "Failed" in stati or "Payment Ordered" in stati:
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: if new_status == self.advance_payment_status:
return return