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']"
/>
-
+
+