From c055de2da6ef0ef920e2078d17d84e3305d96ca8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:33:41 +0000 Subject: [PATCH] refactor(dunning): converted `get_dunning_letter_text` to doc method and `restrict_globals` on `render_template` (backport #57205) (#57214) Co-authored-by: Diptanil Saha --- erpnext/accounts/doctype/dunning/dunning.js | 21 ++---- erpnext/accounts/doctype/dunning/dunning.py | 72 ++++++++++--------- .../doctype/sales_invoice/sales_invoice.py | 12 +--- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js index e9d091f2e85..c9955c7e359 100644 --- a/erpnext/accounts/doctype/dunning/dunning.js +++ b/erpnext/accounts/doctype/dunning/dunning.js @@ -169,23 +169,10 @@ frappe.ui.form.on("Dunning", { }, get_dunning_letter_text: function (frm) { if (frm.doc.dunning_type) { - frappe.call({ - method: "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text", - args: { - dunning_type: frm.doc.dunning_type, - language: frm.doc.language, - doc: frm.doc, - }, - callback: function (r) { - if (r.message) { - frm.set_value("body_text", r.message.body_text); - frm.set_value("closing_text", r.message.closing_text); - frm.set_value("language", r.message.language); - } else { - frm.set_value("body_text", ""); - frm.set_value("closing_text", ""); - } - }, + frm.call("get_dunning_letter_text").then((r) => { + if (!r.exc) { + frm.refresh_fields(); + } }); } }, diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py index f64e957400b..70cdb99ae1d 100644 --- a/erpnext/accounts/doctype/dunning/dunning.py +++ b/erpnext/accounts/doctype/dunning/dunning.py @@ -163,6 +163,46 @@ class Dunning(AccountsController): "Serial and Batch Bundle", ] + @frappe.whitelist() + def get_dunning_letter_text(self): + DOCTYPE = "Dunning Letter Text" + FIELDS = ["body_text", "closing_text", "language"] + + if not self.dunning_type: + return + + filters = {"parent": self.dunning_type, "is_default_language": 1} + + if self.language: + filters.pop("is_default_language") + filters["language"] = self.language + + letter_text = frappe.db.get_value(DOCTYPE, filters, FIELDS, as_dict=True) + + if not letter_text: + msg = ( + _("Dunning Letter for Dunning Type {0} in language '{1}' not found.").format( + frappe.bold(self.dunning_type), frappe.bold(self.language) + ) + if self.language + else _("Dunning Letter for Dunning Type {0} not found.").format( + frappe.bold(self.dunning_type) + ) + ) + frappe.msgprint(msg, alert=True, indicator="yellow") + + self.body_text = ( + frappe.render_template(letter_text.body_text, self.as_dict(), restrict_globals=True) + if letter_text + else None + ) + self.closing_text = ( + frappe.render_template(letter_text.closing_text, self.as_dict(), restrict_globals=True) + if letter_text + else None + ) + self.language = letter_text.language if letter_text else self.language + def update_linked_dunnings(doc, previous_outstanding_amount): if ( @@ -241,35 +281,3 @@ def get_linked_dunnings_as_per_state(sales_invoice, state): & (overdue_payment.sales_invoice == sales_invoice) ) ).run(as_dict=True) - - -@frappe.whitelist() -def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str | None = None) -> dict: - DOCTYPE = "Dunning Letter Text" - FIELDS = ["body_text", "closing_text", "language"] - - if isinstance(doc, str): - doc = json.loads(doc) - - if not language: - language = doc.get("language") - - letter_text = None - if language: - letter_text = frappe.db.get_value( - DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1 - ) - - if not letter_text: - letter_text = frappe.db.get_value( - DOCTYPE, {"parent": dunning_type, "is_default_language": 1}, FIELDS, as_dict=1 - ) - - if not letter_text: - return {} - - return { - "body_text": frappe.render_template(letter_text.body_text, doc), - "closing_text": frappe.render_template(letter_text.closing_text, doc), - "language": letter_text.language, - } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0fc0303edb4..051c7d87519 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -3156,8 +3156,6 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): from frappe.model.mapper import get_mapped_doc def postprocess_dunning(source, target): - from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text - dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1, "company": source.company}) if dunning_type: dunning_type = frappe.get_doc("Dunning Type", dunning_type) @@ -3166,14 +3164,8 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False): target.dunning_fee = dunning_type.dunning_fee target.income_account = dunning_type.income_account target.cost_center = dunning_type.cost_center - letter_text = get_dunning_letter_text( - dunning_type=dunning_type.name, doc=target.as_dict(), language=source.language - ) - - if letter_text: - target.body_text = letter_text.get("body_text") - target.closing_text = letter_text.get("closing_text") - target.language = letter_text.get("language") + target.language = source.language + target.get_dunning_letter_text() # update outstanding from doc if source.payment_schedule and len(source.payment_schedule) == 1: