From b193cde7c03cdbe928c9cf2ba24e0ebe447396bb Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 11 Jul 2024 19:11:29 +0530 Subject: [PATCH] feat: allow creating `Days Before / After` notifications for child table (#26982) --- .../doctype/notification/notification.js | 36 +++++++------- .../doctype/notification/notification.json | 13 +++-- .../doctype/notification/notification.py | 48 ++++++++++++------- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index b88d10ea0d..bede0b429b 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -1,6 +1,8 @@ // Copyright (c) 2018, Frappe Technologies and contributors // For license information, please see license.txt +const DATE_BASED_EVENTS = ["Days Before", "Days After"]; + frappe.notification = { setup_fieldname_select: function (frm) { // get the doctype to update fields @@ -129,6 +131,8 @@ Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }} frappe.ui.form.on("Notification", { onload: function (frm) { frm.set_query("document_type", function () { + if (DATE_BASED_EVENTS.includes(frm.doc.event)) return; + return { filters: { istable: 0, @@ -166,23 +170,23 @@ frappe.ui.form.on("Notification", { frappe.set_route("Form", "Customize Form"); }, event: function (frm) { - if (["Days Before", "Days After"].includes(frm.doc.event)) { - frm.add_custom_button(__("Get Alerts for Today"), function () { - frappe.call({ - method: "frappe.email.doctype.notification.notification.get_documents_for_today", - args: { - notification: frm.doc.name, - }, - callback: function (r) { - if (r.message && r.message.length > 0) { - frappe.msgprint(r.message.toString()); - } else { - frappe.msgprint(__("No alerts for today")); - } - }, - }); + if (!DATE_BASED_EVENTS.includes(frm.doc.event) || frm.is_new()) return; + + frm.add_custom_button(__("Get Alerts for Today"), function () { + frappe.call({ + method: "frappe.email.doctype.notification.notification.get_documents_for_today", + args: { + notification: frm.doc.name, + }, + callback: function (r) { + if (r.message && r.message.length > 0) { + frappe.msgprint(r.message.toString()); + } else { + frappe.msgprint(__("No alerts for today")); + } + }, }); - } + }); }, channel: function (frm) { frm.toggle_reqd("recipients", frm.doc.channel == "Email"); diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index b50c8d8468..c05ea05b01 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -8,16 +8,16 @@ "engine": "InnoDB", "field_order": [ "enabled", + "is_standard", + "module", "column_break_2", "channel", "slack_webhook_url", "filters", "subject", - "document_type", - "is_standard", - "module", - "col_break_1", "event", + "document_type", + "col_break_1", "method", "date_changed", "days_in_advance", @@ -119,7 +119,6 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval: doc.document_type", "fieldname": "event", "fieldtype": "Select", "in_list_view": 1, @@ -292,7 +291,7 @@ "icon": "fa fa-envelope", "index_web_pages_for_search": 1, "links": [], - "modified": "2024-06-17 04:03:22.591781", + "modified": "2024-07-04 05:53:40.595130", "modified_by": "Administrator", "module": "Email", "name": "Notification", @@ -315,4 +314,4 @@ "states": [], "title_field": "subject", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 72c95cf194..c9e9f44a53 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -18,6 +18,8 @@ from frappe.utils.jinja import validate_template from frappe.utils.safe_exec import get_safe_globals FORMATS = {"HTML": ".html", "Markdown": ".md", "Plain Text": ".txt"} +FORBIDDEN_DOCUMENT_TYPES = frozenset(("Email Queue",)) +DATE_BASED_EVENTS = frozenset(("Days Before", "Days After")) class Notification(Document): @@ -27,9 +29,7 @@ class Notification(Document): from typing import TYPE_CHECKING if TYPE_CHECKING: - from frappe.email.doctype.notification_recipient.notification_recipient import ( - NotificationRecipient, - ) + from frappe.email.doctype.notification_recipient.notification_recipient import NotificationRecipient from frappe.types import DF attach_print: DF.Check @@ -90,7 +90,7 @@ class Notification(Document): if self.event == "Value Change" and not self.value_changed: frappe.throw(_("Please specify which value field must be checked")) - self.validate_forbidden_types() + self.validate_forbidden_document_types() self.validate_condition() self.validate_standard() frappe.cache.hdel("notifications", self.document_type) @@ -130,12 +130,16 @@ def get_context(context): except Exception: frappe.throw(_("The Condition '{0}' is invalid").format(self.condition)) - def validate_forbidden_types(self): - forbidden_document_types = ("Email Queue",) - if self.document_type in forbidden_document_types or frappe.get_meta(self.document_type).istable: - # currently notifications don't work on child tables as events are not fired for each record of child table - - frappe.throw(_("Cannot set Notification on Document Type {0}").format(self.document_type)) + def validate_forbidden_document_types(self): + if self.document_type in FORBIDDEN_DOCUMENT_TYPES or ( + frappe.get_meta(self.document_type).istable and self.event not in DATE_BASED_EVENTS + ): + # only date based events are allowed for child tables + frappe.throw( + _("Cannot set Notification with event {0} on Document Type {1}").format( + _(self.event), _(self.document_type) + ) + ) def get_documents_for_today(self): """get list of documents that will be triggered today""" @@ -237,8 +241,8 @@ def get_context(context): notification_doc = { "type": "Alert", - "document_type": doc.doctype, - "document_name": doc.name, + "document_type": get_reference_doctype(doc), + "document_name": get_reference_name(doc), "subject": subject, "from_user": doc.modified_by or doc.owner, "email_content": frappe.render_template(self.message, context), @@ -270,8 +274,8 @@ def get_context(context): # No need to add if it is already a communication. if doc.doctype != "Communication": communication = make_communication( - doctype=doc.doctype, - name=doc.name, + doctype=get_reference_doctype(doc), + name=get_reference_name(doc), content=message, subject=subject, sender=sender, @@ -294,8 +298,8 @@ def get_context(context): cc=cc, bcc=bcc, message=message, - reference_doctype=doc.doctype, - reference_name=doc.name, + reference_doctype=get_reference_doctype(doc), + reference_name=get_reference_name(doc), attachments=attachments, expose_recipients="header", print_letterhead=((attachments and attachments[0].get("print_letterhead")) or False), @@ -306,8 +310,8 @@ def get_context(context): send_slack_message( webhook_url=self.slack_webhook_url, message=frappe.render_template(self.message, context), - reference_doctype=doc.doctype, - reference_name=doc.name, + reference_doctype=get_reference_doctype(doc), + reference_name=get_reference_name(doc), ) def send_sms(self, doc, context): @@ -543,3 +547,11 @@ def get_emails_from_template(template, context): emails = frappe.render_template(template, context) if "{" in template else template return filter(None, emails.replace(",", "\n").split("\n")) + + +def get_reference_doctype(doc): + return doc.parenttype if doc.meta.istable else doc.doctype + + +def get_reference_name(doc): + return doc.parent if doc.meta.istable else doc.name