diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index bede0b429b..4017c9e40f 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -14,10 +14,11 @@ frappe.notification = { let get_select_options = function (df, parent_field) { // Append parent_field name along with fieldname for child table fields let select_value = parent_field ? df.fieldname + "," + parent_field : df.fieldname; + let path = parent_field ? parent_field + " > " + df.fieldname : df.fieldname; return { value: select_value, - label: df.fieldname + " (" + __(df.label, null, df.parent) + ")", + label: path + " (" + __(df.label, null, df.parent) + ")", }; }; @@ -33,6 +34,38 @@ frappe.notification = { { value: "modified", label: `modified (${__("Last Modified Date")})` }, ]); }; + let get_receiver_fields = function ( + fields, + is_extra_receiver_field = (_) => { + return false; + } + ) { + // finds receiver fields from the fields or any child table + // by default finds any link to the User doctype + // however an additional optional predicate can be passed as argument + // to find additional fields + let is_receiver_field = function (df) { + return ( + is_extra_receiver_field(df) || + (df.options == "User" && df.fieldtype == "Link") || + (df.options == "Customer" && df.fieldtype == "Link") + ); + }; + let extract_receiver_field = function (df) { + // Add recipients from child doctypes into select dropdown + if (frappe.model.table_fields.includes(df.fieldtype)) { + let child_fields = frappe.get_doc("DocType", df.options).fields; + return $.map(child_fields, function (cdf) { + return is_receiver_field(cdf) + ? get_select_options(cdf, df.fieldname) + : null; + }); + } else { + return is_receiver_field(df) ? get_select_options(df) : null; + } + }; + return $.map(fields, extract_receiver_field); + }; let fields = frappe.get_doc("DocType", frm.doc.document_type).fields; let options = $.map(fields, function (d) { @@ -50,27 +83,12 @@ frappe.notification = { let receiver_fields = []; if (frm.doc.channel === "Email") { - receiver_fields = $.map(fields, function (d) { - // Add User and Email fields from child into select dropdown - if (frappe.model.table_fields.includes(d.fieldtype)) { - let child_fields = frappe.get_doc("DocType", d.options).fields; - return $.map(child_fields, function (df) { - return df.options == "Email" || - (df.options == "User" && df.fieldtype == "Link") - ? get_select_options(df, d.fieldname) - : null; - }); - // Add User and Email fields from parent into select dropdown - } else { - return d.options == "Email" || - (d.options == "User" && d.fieldtype == "Link") - ? get_select_options(d) - : null; - } + receiver_fields = get_receiver_fields(fields, function (df) { + return df.options == "Email"; }); } else if (["WhatsApp", "SMS"].includes(frm.doc.channel)) { - receiver_fields = $.map(fields, function (d) { - return d.options == "Phone" ? get_select_options(d) : null; + receiver_fields = get_receiver_fields(fields, function (df) { + df.options == "Phone" || df.options == "Mobile"; }); } diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index c9e9f44a53..9725846496 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -176,7 +176,7 @@ def get_context(context): """Build recipients and send Notification""" context = get_context(doc) - context = {"doc": doc, "alert": self, "comments": None} + context.update({"alert": self, "comments": None}) if doc.get("_comments"): context["comments"] = json.loads(doc.get("_comments")) @@ -316,10 +316,41 @@ def get_context(context): def send_sms(self, doc, context): send_sms( - receiver_list=self.get_receiver_list(doc, context), + receiver_list=self.get_receiver_list(doc, context, "mobile_no", self.get_mobile_no), msg=frappe.utils.strip_html_tags(frappe.render_template(self.message, context)), ) + @staticmethod + def get_mobile_no(doc, field): + option = doc.meta.get_field(field).options.strip() + # users may sometimes register mobile numbers under Phone type fields + if option == "Phone" or option == "Mobile": + mobile_no = doc.get(field) + if not mobile_no: + doc.log_error( + _("Notification: document {0} has no {1} number set (field: {2})").format( + field, doc.name, option, field + ) + ) + # but on user & customer it's expected to be set on the proper field + elif option == "User": + user = doc.get(field) + mobile_no = frappe.get_value("User", user, "mobile_no") + if not mobile_no: + doc.log_error(_("Notification: user {0} has no Mobile number set").format(user)) + elif option == "Customer": + customer = doc.get(field) + mobile_no = frappe.get_value("Customer", customer, "mobile_no") + if not mobile_no: + doc.log_error(_("Notification: customer {0} has no Mobile number set").format(customer)) + else: + frappe.throw( + _( + "Field {0} on document {1} is neither a Mobile number field nor a Customer or User link" + ).format(field, doc.name) + ) + return mobile_no + def get_list_of_recipients(self, doc, context): recipients = [] cc = [] @@ -329,16 +360,17 @@ def get_context(context): if not frappe.safe_eval(recipient.condition, None, context): continue if recipient.receiver_by_document_field: - fields = recipient.receiver_by_document_field.split(",") - # fields from child table - if len(fields) > 1: - for d in doc.get(fields[1]): - email_id = d.get(fields[0]) + data_field, child_field = _parse_receiver_by_document_field( + recipient.receiver_by_document_field + ) + if child_field: + for d in doc.get(child_field): + email_id = d.get(data_field) if validate_email_address(email_id): recipients.append(email_id) - # field from parent doc + # field from current doc else: - email_ids_value = doc.get(fields[0]) + email_ids_value = doc.get(data_field) if validate_email_address(email_ids_value): email_ids = email_ids_value.replace(",", "\n") recipients = recipients + email_ids.split("\n") @@ -358,8 +390,10 @@ def get_context(context): return list(set(recipients)), list(set(cc)), list(set(bcc)) - def get_receiver_list(self, doc, context): + def get_receiver_list(self, doc, context, field_on_user="mobile_no", recipient_extractor_func=None): """return receiver list based on the doc field and role specified""" + if not recipient_extractor_func: + recipient_extractor_func = self.get_mobile_no receiver_list = [] for recipient in self.recipients: if recipient.condition: @@ -368,18 +402,28 @@ def get_context(context): # For sending messages to the owner's mobile phone number if recipient.receiver_by_document_field == "owner": - receiver_list += get_user_info([dict(user_name=doc.get("owner"))], "mobile_no") + receiver_list += get_user_info([dict(user_name=doc.get("owner"))], field_on_user) # For sending messages to the number specified in the receiver field elif recipient.receiver_by_document_field: - receiver_list.append(doc.get(recipient.receiver_by_document_field)) + data_field, child_field = _parse_receiver_by_document_field( + recipient.receiver_by_document_field + ) + if child_field: + for d in doc.get(child_field): + if recv := recipient_extractor_func(d, data_field): + receiver_list.append(recv) + # field from current doc + else: + if recv := recipient_extractor_func(doc, data_field): + receiver_list.append(recv) # For sending messages to specified role if recipient.receiver_by_role: receiver_list += get_info_based_on_role( - recipient.receiver_by_role, "mobile_no", ignore_permissions=True + recipient.receiver_by_role, field_on_user, ignore_permissions=True ) - return receiver_list + return list(set(receiver_list)) def get_attachment(self, doc): """check print settings are attach the pdf""" @@ -555,3 +599,13 @@ def get_reference_doctype(doc): def get_reference_name(doc): return doc.parent if doc.meta.istable else doc.name + + +def _parse_receiver_by_document_field(s): + fragments = s.split(",") + # fields from child table or linked doctype + if len(fragments) > 1: + data_field, child_field = fragments + else: + data_field, child_field = fragments[0], None + return data_field, child_field