diff --git a/crm/api/comment.py b/crm/api/comment.py index db90cb54..378a01b8 100644 --- a/crm/api/comment.py +++ b/crm/api/comment.py @@ -21,14 +21,10 @@ def notify_mentions(doc): if doctype.startswith("CRM "): doctype = doctype[4:].lower() notification_text = f""" -
- - { owner } - +
+ { owner } { _('mentioned you in {0}').format(doctype) } - - { doc.reference_name } - + { doc.reference_name }
""" values = frappe._dict( diff --git a/crm/api/whatsapp.py b/crm/api/whatsapp.py index a9fea2f6..d871fc90 100644 --- a/crm/api/whatsapp.py +++ b/crm/api/whatsapp.py @@ -1,224 +1,314 @@ import frappe import json from frappe import _ +from crm.api.doc import get_assigned_users + def validate(doc, method): - if doc.type == "Incoming" and doc.get("from"): - name, doctype = get_lead_or_deal_from_number(doc.get("from")) - doc.reference_doctype = doctype - doc.reference_name = name + if doc.type == "Incoming" and doc.get("from"): + name, doctype = get_lead_or_deal_from_number(doc.get("from")) + doc.reference_doctype = doctype + doc.reference_name = name + def on_update(doc, method): - frappe.publish_realtime("whatsapp_message", { - 'reference_doctype': doc.reference_doctype, - 'reference_name': doc.reference_name, - }) + frappe.publish_realtime( + "whatsapp_message", + { + "reference_doctype": doc.reference_doctype, + "reference_name": doc.reference_name, + }, + ) + + notify_agent(doc) + + +def notify_agent(doc): + if doc.type == "Incoming": + doctype = doc.reference_doctype + if doctype.startswith("CRM "): + doctype = doctype[4:].lower() + notification_text = f""" +
+ { _('You') } + { _('received a whatsapp message in {0}').format(doctype) } + { doc.reference_name } +
+ """ + assigned_users = get_assigned_users(doc.reference_doctype, doc.reference_name) + for user in assigned_users: + values = frappe._dict( + doctype="CRM Notification", + from_user=doc.owner, + to_user=user, + type="WhatsApp", + message=doc.message, + notification_text=notification_text, + notification_type_doctype="WhatsApp Message", + notification_type_doc=doc.name, + reference_doctype=doc.reference_doctype, + reference_name=doc.reference_name, + ) + + if frappe.db.exists("CRM Notification", values): + return + frappe.get_doc(values).insert(ignore_permissions=True) + def get_lead_or_deal_from_number(number): - """Get lead/deal from the given number. - """ + """Get lead/deal from the given number.""" - def find_record(doctype, mobile_no, where=''): - mobile_no = parse_mobile_no(mobile_no) - - query = f""" + def find_record(doctype, mobile_no, where=""): + mobile_no = parse_mobile_no(mobile_no) + + query = f""" SELECT name, mobile_no FROM `tab{doctype}` WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no} """ - data = frappe.db.sql(query + where, as_dict=True) - return data[0].name if data else None + data = frappe.db.sql(query + where, as_dict=True) + return data[0].name if data else None - doctype = "CRM Deal" + doctype = "CRM Deal" - doc = find_record(doctype, number) or None - if not doc: - doctype = "CRM Lead" - doc = find_record(doctype, number, 'AND converted is not True') - if not doc: - doc = find_record(doctype, number) + doc = find_record(doctype, number) or None + if not doc: + doctype = "CRM Lead" + doc = find_record(doctype, number, "AND converted is not True") + if not doc: + doc = find_record(doctype, number) + + return doc, doctype - return doc, doctype def parse_mobile_no(mobile_no: str): - """Parse mobile number to remove spaces, brackets, etc. - >>> parse_mobile_no('+91 (766) 667 6666') - ... '+917666676666' - """ - return ''.join([c for c in mobile_no if c.isdigit() or c == '+']) + """Parse mobile number to remove spaces, brackets, etc. + >>> parse_mobile_no('+91 (766) 667 6666') + ... '+917666676666' + """ + return "".join([c for c in mobile_no if c.isdigit() or c == "+"]) + @frappe.whitelist() def is_whatsapp_enabled(): - if not frappe.db.exists('DocType', 'WhatsApp Settings'): - return False - return frappe.get_cached_value('WhatsApp Settings', 'WhatsApp Settings', 'enabled') + if not frappe.db.exists("DocType", "WhatsApp Settings"): + return False + return frappe.get_cached_value("WhatsApp Settings", "WhatsApp Settings", "enabled") + @frappe.whitelist() def get_whatsapp_messages(reference_doctype, reference_name): - if not frappe.db.exists('DocType', 'WhatsApp Message'): - return [] - messages = frappe.get_all( - "WhatsApp Message", - filters={ - "reference_doctype": reference_doctype, - "reference_name": reference_name - }, - fields=[ - 'name', - 'type', - 'to', - 'from', - 'content_type', - 'message_type', - 'attach', - 'template', - 'use_template', - 'message_id', - 'is_reply', - 'reply_to_message_id', - 'creation', - 'message', - 'status', - 'reference_doctype', - 'reference_name', - 'template_parameters', - 'template_header_parameters', - ], - ) + if not frappe.db.exists("DocType", "WhatsApp Message"): + return [] + messages = frappe.get_all( + "WhatsApp Message", + filters={ + "reference_doctype": reference_doctype, + "reference_name": reference_name, + }, + fields=[ + "name", + "type", + "to", + "from", + "content_type", + "message_type", + "attach", + "template", + "use_template", + "message_id", + "is_reply", + "reply_to_message_id", + "creation", + "message", + "status", + "reference_doctype", + "reference_name", + "template_parameters", + "template_header_parameters", + ], + ) - # Filter messages to get only Template messages - template_messages = [message for message in messages if message['message_type'] == 'Template'] + # Filter messages to get only Template messages + template_messages = [ + message for message in messages if message["message_type"] == "Template" + ] - # Iterate through template messages - for template_message in template_messages: - # Find the template that this message is using - template = frappe.get_doc("WhatsApp Templates", template_message['template']) - - # If the template is found, add the template details to the template message - if template: - template_message['template_name'] = template.template_name - if template_message['template_parameters']: - parameters = json.loads(template_message['template_parameters']) - template.template = parse_template_parameters(template.template, parameters) + # Iterate through template messages + for template_message in template_messages: + # Find the template that this message is using + template = frappe.get_doc("WhatsApp Templates", template_message["template"]) - template_message['template'] = template.template - if template_message['template_header_parameters']: - header_parameters = json.loads(template_message['template_header_parameters']) - template.header = parse_template_parameters(template.header, header_parameters) - template_message['header'] = template.header - template_message['footer'] = template.footer + # If the template is found, add the template details to the template message + if template: + template_message["template_name"] = template.template_name + if template_message["template_parameters"]: + parameters = json.loads(template_message["template_parameters"]) + template.template = parse_template_parameters( + template.template, parameters + ) - # Filter messages to get only reaction messages - reaction_messages = [message for message in messages if message['content_type'] == 'reaction'] + template_message["template"] = template.template + if template_message["template_header_parameters"]: + header_parameters = json.loads( + template_message["template_header_parameters"] + ) + template.header = parse_template_parameters( + template.header, header_parameters + ) + template_message["header"] = template.header + template_message["footer"] = template.footer - # Iterate through reaction messages - for reaction_message in reaction_messages: - # Find the message that this reaction is reacting to - reacted_message = next((m for m in messages if m['message_id'] == reaction_message['reply_to_message_id']), None) - - # If the reacted message is found, add the reaction to it - if reacted_message: - reacted_message['reaction'] = reaction_message['message'] + # Filter messages to get only reaction messages + reaction_messages = [ + message for message in messages if message["content_type"] == "reaction" + ] - for message in messages: - from_name = get_from_name(message) if message['from'] else _('You') - message['from_name'] = from_name - # Filter messages to get only replies - reply_messages = [message for message in messages if message['is_reply']] + # Iterate through reaction messages + for reaction_message in reaction_messages: + # Find the message that this reaction is reacting to + reacted_message = next( + ( + m + for m in messages + if m["message_id"] == reaction_message["reply_to_message_id"] + ), + None, + ) - # Iterate through reply messages - for reply_message in reply_messages: - # Find the message that this message is replying to - replied_message = next((m for m in messages if m['message_id'] == reply_message['reply_to_message_id']), None) - - # If the replied message is found, add the reply details to the reply message - from_name = get_from_name(reply_message) if replied_message['from'] else _('You') - if replied_message: - message = replied_message['message'] - if replied_message['message_type'] == 'Template': - message = replied_message['template'] - reply_message['reply_message'] = message - reply_message['header'] = replied_message.get('header') or '' - reply_message['footer'] = replied_message.get('footer') or '' - reply_message['reply_to'] = replied_message['name'] - reply_message['reply_to_type'] = replied_message['type'] - reply_message['reply_to_from'] = from_name + # If the reacted message is found, add the reaction to it + if reacted_message: + reacted_message["reaction"] = reaction_message["message"] + + for message in messages: + from_name = get_from_name(message) if message["from"] else _("You") + message["from_name"] = from_name + # Filter messages to get only replies + reply_messages = [message for message in messages if message["is_reply"]] + + # Iterate through reply messages + for reply_message in reply_messages: + # Find the message that this message is replying to + replied_message = next( + ( + m + for m in messages + if m["message_id"] == reply_message["reply_to_message_id"] + ), + None, + ) + + # If the replied message is found, add the reply details to the reply message + from_name = ( + get_from_name(reply_message) if replied_message["from"] else _("You") + ) + if replied_message: + message = replied_message["message"] + if replied_message["message_type"] == "Template": + message = replied_message["template"] + reply_message["reply_message"] = message + reply_message["header"] = replied_message.get("header") or "" + reply_message["footer"] = replied_message.get("footer") or "" + reply_message["reply_to"] = replied_message["name"] + reply_message["reply_to_type"] = replied_message["type"] + reply_message["reply_to_from"] = from_name + + return [message for message in messages if message["content_type"] != "reaction"] - return [message for message in messages if message['content_type'] != 'reaction'] @frappe.whitelist() -def create_whatsapp_message(reference_doctype, reference_name, message, to, attach, reply_to, content_type="text"): - doc = frappe.new_doc("WhatsApp Message") +def create_whatsapp_message( + reference_doctype, + reference_name, + message, + to, + attach, + reply_to, + content_type="text", +): + doc = frappe.new_doc("WhatsApp Message") - if reply_to: - reply_doc = frappe.get_doc("WhatsApp Message", reply_to) - doc.update({ - "is_reply": True, - "reply_to_message_id": reply_doc.message_id, - }) + if reply_to: + reply_doc = frappe.get_doc("WhatsApp Message", reply_to) + doc.update( + { + "is_reply": True, + "reply_to_message_id": reply_doc.message_id, + } + ) + + doc.update( + { + "reference_doctype": reference_doctype, + "reference_name": reference_name, + "message": message or attach, + "to": to, + "attach": attach, + "content_type": content_type, + } + ) + doc.insert(ignore_permissions=True) + return doc.name - doc.update({ - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "message": message or attach, - "to": to, - "attach": attach, - "content_type": content_type, - }) - doc.insert(ignore_permissions=True) - return doc.name @frappe.whitelist() def send_whatsapp_template(reference_doctype, reference_name, template, to): - doc = frappe.new_doc("WhatsApp Message") - doc.update({ - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "message_type": "Template", - "message": "Template message", - "content_type": "text", - "use_template": True, - "template": template, - "to": to, - }) - doc.insert(ignore_permissions=True) - return doc.name + doc = frappe.new_doc("WhatsApp Message") + doc.update( + { + "reference_doctype": reference_doctype, + "reference_name": reference_name, + "message_type": "Template", + "message": "Template message", + "content_type": "text", + "use_template": True, + "template": template, + "to": to, + } + ) + doc.insert(ignore_permissions=True) + return doc.name + @frappe.whitelist() def react_on_whatsapp_message(emoji, reply_to_name): - reply_to_doc = frappe.get_doc("WhatsApp Message", reply_to_name) - to = reply_to_doc.type == "Incoming" and reply_to_doc.get("from") or reply_to_doc.to - doc = frappe.new_doc("WhatsApp Message") - doc.update({ - "reference_doctype": reply_to_doc.reference_doctype, - "reference_name": reply_to_doc.reference_name, - "message": emoji, - "to": to, - "reply_to_message_id": reply_to_doc.message_id, - "content_type": "reaction", - }) - doc.insert(ignore_permissions=True) - return doc.name + reply_to_doc = frappe.get_doc("WhatsApp Message", reply_to_name) + to = reply_to_doc.type == "Incoming" and reply_to_doc.get("from") or reply_to_doc.to + doc = frappe.new_doc("WhatsApp Message") + doc.update( + { + "reference_doctype": reply_to_doc.reference_doctype, + "reference_name": reply_to_doc.reference_name, + "message": emoji, + "to": to, + "reply_to_message_id": reply_to_doc.message_id, + "content_type": "reaction", + } + ) + doc.insert(ignore_permissions=True) + return doc.name + def parse_template_parameters(string, parameters): - for i, parameter in enumerate(parameters, start=1): - placeholder = "{{" + str(i) + "}}" - string = string.replace(placeholder, parameter) + for i, parameter in enumerate(parameters, start=1): + placeholder = "{{" + str(i) + "}}" + string = string.replace(placeholder, parameter) + + return string - return string def get_from_name(message): - doc = frappe.get_doc(message['reference_doctype'], message['reference_name']) - from_name = '' - if message['reference_doctype'] == "CRM Deal": - if doc.get("contacts"): - for c in doc.get("contacts"): - if c.is_primary: - from_name = c.full_name or c.mobile_no - break - else: - from_name = doc.get("lead_name") - else: - from_name = doc.get("first_name") + " " + doc.get("last_name") - return from_name \ No newline at end of file + doc = frappe.get_doc(message["reference_doctype"], message["reference_name"]) + from_name = "" + if message["reference_doctype"] == "CRM Deal": + if doc.get("contacts"): + for c in doc.get("contacts"): + if c.is_primary: + from_name = c.full_name or c.mobile_no + break + else: + from_name = doc.get("lead_name") + else: + from_name = doc.get("first_name") + " " + doc.get("last_name") + return from_name diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.json b/crm/fcrm/doctype/crm_notification/crm_notification.json index 72515feb..be7ac1f6 100644 --- a/crm/fcrm/doctype/crm_notification/crm_notification.json +++ b/crm/fcrm/doctype/crm_notification/crm_notification.json @@ -69,6 +69,7 @@ { "fieldname": "message", "fieldtype": "HTML Editor", + "in_list_view": 1, "label": "Message" }, { @@ -115,7 +116,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-04-25 13:55:23.014268", + "modified": "2024-04-25 16:26:07.484857", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Notification", diff --git a/frontend/src/components/Notifications.vue b/frontend/src/components/Notifications.vue index 8e7394ee..9eb8aa22 100644 --- a/frontend/src/components/Notifications.vue +++ b/frontend/src/components/Notifications.vue @@ -55,7 +55,11 @@ class="h-[5px] w-[5px] rounded-full" :class="[n.read ? 'bg-transparent' : 'bg-gray-900']" /> - + +
@@ -89,6 +93,7 @@