feat: notify assigned agent if whatsapp message is received

This commit is contained in:
Shariq Ansari 2024-04-25 16:39:24 +05:30
parent 7b08359624
commit ace509dd80
4 changed files with 276 additions and 184 deletions

View File

@ -21,14 +21,10 @@ def notify_mentions(doc):
if doctype.startswith("CRM "): if doctype.startswith("CRM "):
doctype = doctype[4:].lower() doctype = doctype[4:].lower()
notification_text = f""" notification_text = f"""
<div class="mb-2 space-x-1 leading-5 text-gray-600"> <div class="mb-2 leading-5 text-gray-600">
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">{ owner }</span>
{ owner }
</span>
<span>{ _('mentioned you in {0}').format(doctype) }</span> <span>{ _('mentioned you in {0}').format(doctype) }</span>
<span class="font-medium text-gray-900"> <span class="font-medium text-gray-900">{ doc.reference_name }</span>
{ doc.reference_name }
</span>
</div> </div>
""" """
values = frappe._dict( values = frappe._dict(

View File

@ -1,224 +1,314 @@
import frappe import frappe
import json import json
from frappe import _ from frappe import _
from crm.api.doc import get_assigned_users
def validate(doc, method): def validate(doc, method):
if doc.type == "Incoming" and doc.get("from"): if doc.type == "Incoming" and doc.get("from"):
name, doctype = get_lead_or_deal_from_number(doc.get("from")) name, doctype = get_lead_or_deal_from_number(doc.get("from"))
doc.reference_doctype = doctype doc.reference_doctype = doctype
doc.reference_name = name doc.reference_name = name
def on_update(doc, method): def on_update(doc, method):
frappe.publish_realtime("whatsapp_message", { frappe.publish_realtime(
'reference_doctype': doc.reference_doctype, "whatsapp_message",
'reference_name': doc.reference_name, {
}) "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"""
<div class="mb-2 leading-5 text-gray-600">
<span class="font-medium text-gray-900">{ _('You') }</span>
<span>{ _('received a whatsapp message in {0}').format(doctype) }</span>
<span class="font-medium text-gray-900">{ doc.reference_name }</span>
</div>
"""
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): 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=''): def find_record(doctype, mobile_no, where=""):
mobile_no = parse_mobile_no(mobile_no) mobile_no = parse_mobile_no(mobile_no)
query = f""" query = f"""
SELECT name, mobile_no SELECT name, mobile_no
FROM `tab{doctype}` FROM `tab{doctype}`
WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no} WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no}
""" """
data = frappe.db.sql(query + where, as_dict=True) data = frappe.db.sql(query + where, as_dict=True)
return data[0].name if data else None return data[0].name if data else None
doctype = "CRM Deal" doctype = "CRM Deal"
doc = find_record(doctype, number) or None doc = find_record(doctype, number) or None
if not doc: if not doc:
doctype = "CRM Lead" doctype = "CRM Lead"
doc = find_record(doctype, number, 'AND converted is not True') doc = find_record(doctype, number, "AND converted is not True")
if not doc: if not doc:
doc = find_record(doctype, number) doc = find_record(doctype, number)
return doc, doctype
return doc, doctype
def parse_mobile_no(mobile_no: str): def parse_mobile_no(mobile_no: str):
"""Parse mobile number to remove spaces, brackets, etc. """Parse mobile number to remove spaces, brackets, etc.
>>> parse_mobile_no('+91 (766) 667 6666') >>> parse_mobile_no('+91 (766) 667 6666')
... '+917666676666' ... '+917666676666'
""" """
return ''.join([c for c in mobile_no if c.isdigit() or c == '+']) return "".join([c for c in mobile_no if c.isdigit() or c == "+"])
@frappe.whitelist() @frappe.whitelist()
def is_whatsapp_enabled(): def is_whatsapp_enabled():
if not frappe.db.exists('DocType', 'WhatsApp Settings'): if not frappe.db.exists("DocType", "WhatsApp Settings"):
return False return False
return frappe.get_cached_value('WhatsApp Settings', 'WhatsApp Settings', 'enabled') return frappe.get_cached_value("WhatsApp Settings", "WhatsApp Settings", "enabled")
@frappe.whitelist() @frappe.whitelist()
def get_whatsapp_messages(reference_doctype, reference_name): def get_whatsapp_messages(reference_doctype, reference_name):
if not frappe.db.exists('DocType', 'WhatsApp Message'): if not frappe.db.exists("DocType", "WhatsApp Message"):
return [] return []
messages = frappe.get_all( messages = frappe.get_all(
"WhatsApp Message", "WhatsApp Message",
filters={ filters={
"reference_doctype": reference_doctype, "reference_doctype": reference_doctype,
"reference_name": reference_name "reference_name": reference_name,
}, },
fields=[ fields=[
'name', "name",
'type', "type",
'to', "to",
'from', "from",
'content_type', "content_type",
'message_type', "message_type",
'attach', "attach",
'template', "template",
'use_template', "use_template",
'message_id', "message_id",
'is_reply', "is_reply",
'reply_to_message_id', "reply_to_message_id",
'creation', "creation",
'message', "message",
'status', "status",
'reference_doctype', "reference_doctype",
'reference_name', "reference_name",
'template_parameters', "template_parameters",
'template_header_parameters', "template_header_parameters",
], ],
) )
# Filter messages to get only Template messages # Filter messages to get only Template messages
template_messages = [message for message in messages if message['message_type'] == 'Template'] template_messages = [
message for message in messages if message["message_type"] == "Template"
]
# Iterate through template messages # Iterate through template messages
for template_message in template_messages: for template_message in template_messages:
# Find the template that this message is using # Find the template that this message is using
template = frappe.get_doc("WhatsApp Templates", template_message['template']) 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)
template_message['template'] = template.template # If the template is found, add the template details to the template message
if template_message['template_header_parameters']: if template:
header_parameters = json.loads(template_message['template_header_parameters']) template_message["template_name"] = template.template_name
template.header = parse_template_parameters(template.header, header_parameters) if template_message["template_parameters"]:
template_message['header'] = template.header parameters = json.loads(template_message["template_parameters"])
template_message['footer'] = template.footer template.template = parse_template_parameters(
template.template, parameters
)
# Filter messages to get only reaction messages template_message["template"] = template.template
reaction_messages = [message for message in messages if message['content_type'] == 'reaction'] 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 # Filter messages to get only reaction messages
for reaction_message in reaction_messages: reaction_messages = [
# Find the message that this reaction is reacting to message for message in messages if message["content_type"] == "reaction"
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']
for message in messages: # Iterate through reaction messages
from_name = get_from_name(message) if message['from'] else _('You') for reaction_message in reaction_messages:
message['from_name'] = from_name # Find the message that this reaction is reacting to
# Filter messages to get only replies reacted_message = next(
reply_messages = [message for message in messages if message['is_reply']] (
m
for m in messages
if m["message_id"] == reaction_message["reply_to_message_id"]
),
None,
)
# Iterate through reply messages # If the reacted message is found, add the reaction to it
for reply_message in reply_messages: if reacted_message:
# Find the message that this message is replying to reacted_message["reaction"] = reaction_message["message"]
replied_message = next((m for m in messages if m['message_id'] == reply_message['reply_to_message_id']), None)
for message in messages:
# If the replied message is found, add the reply details to the reply message from_name = get_from_name(message) if message["from"] else _("You")
from_name = get_from_name(reply_message) if replied_message['from'] else _('You') message["from_name"] = from_name
if replied_message: # Filter messages to get only replies
message = replied_message['message'] reply_messages = [message for message in messages if message["is_reply"]]
if replied_message['message_type'] == 'Template':
message = replied_message['template'] # Iterate through reply messages
reply_message['reply_message'] = message for reply_message in reply_messages:
reply_message['header'] = replied_message.get('header') or '' # Find the message that this message is replying to
reply_message['footer'] = replied_message.get('footer') or '' replied_message = next(
reply_message['reply_to'] = replied_message['name'] (
reply_message['reply_to_type'] = replied_message['type'] m
reply_message['reply_to_from'] = from_name 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() @frappe.whitelist()
def create_whatsapp_message(reference_doctype, reference_name, message, to, attach, reply_to, content_type="text"): def create_whatsapp_message(
doc = frappe.new_doc("WhatsApp Message") reference_doctype,
reference_name,
message,
to,
attach,
reply_to,
content_type="text",
):
doc = frappe.new_doc("WhatsApp Message")
if reply_to: if reply_to:
reply_doc = frappe.get_doc("WhatsApp Message", reply_to) reply_doc = frappe.get_doc("WhatsApp Message", reply_to)
doc.update({ doc.update(
"is_reply": True, {
"reply_to_message_id": reply_doc.message_id, "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() @frappe.whitelist()
def send_whatsapp_template(reference_doctype, reference_name, template, to): def send_whatsapp_template(reference_doctype, reference_name, template, to):
doc = frappe.new_doc("WhatsApp Message") doc = frappe.new_doc("WhatsApp Message")
doc.update({ doc.update(
"reference_doctype": reference_doctype, {
"reference_name": reference_name, "reference_doctype": reference_doctype,
"message_type": "Template", "reference_name": reference_name,
"message": "Template message", "message_type": "Template",
"content_type": "text", "message": "Template message",
"use_template": True, "content_type": "text",
"template": template, "use_template": True,
"to": to, "template": template,
}) "to": to,
doc.insert(ignore_permissions=True) }
return doc.name )
doc.insert(ignore_permissions=True)
return doc.name
@frappe.whitelist() @frappe.whitelist()
def react_on_whatsapp_message(emoji, reply_to_name): def react_on_whatsapp_message(emoji, reply_to_name):
reply_to_doc = frappe.get_doc("WhatsApp Message", 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 to = reply_to_doc.type == "Incoming" and reply_to_doc.get("from") or reply_to_doc.to
doc = frappe.new_doc("WhatsApp Message") doc = frappe.new_doc("WhatsApp Message")
doc.update({ doc.update(
"reference_doctype": reply_to_doc.reference_doctype, {
"reference_name": reply_to_doc.reference_name, "reference_doctype": reply_to_doc.reference_doctype,
"message": emoji, "reference_name": reply_to_doc.reference_name,
"to": to, "message": emoji,
"reply_to_message_id": reply_to_doc.message_id, "to": to,
"content_type": "reaction", "reply_to_message_id": reply_to_doc.message_id,
}) "content_type": "reaction",
doc.insert(ignore_permissions=True) }
return doc.name )
doc.insert(ignore_permissions=True)
return doc.name
def parse_template_parameters(string, parameters): def parse_template_parameters(string, parameters):
for i, parameter in enumerate(parameters, start=1): for i, parameter in enumerate(parameters, start=1):
placeholder = "{{" + str(i) + "}}" placeholder = "{{" + str(i) + "}}"
string = string.replace(placeholder, parameter) string = string.replace(placeholder, parameter)
return string
return string
def get_from_name(message): def get_from_name(message):
doc = frappe.get_doc(message['reference_doctype'], message['reference_name']) doc = frappe.get_doc(message["reference_doctype"], message["reference_name"])
from_name = '' from_name = ""
if message['reference_doctype'] == "CRM Deal": if message["reference_doctype"] == "CRM Deal":
if doc.get("contacts"): if doc.get("contacts"):
for c in doc.get("contacts"): for c in doc.get("contacts"):
if c.is_primary: if c.is_primary:
from_name = c.full_name or c.mobile_no from_name = c.full_name or c.mobile_no
break break
else: else:
from_name = doc.get("lead_name") from_name = doc.get("lead_name")
else: else:
from_name = doc.get("first_name") + " " + doc.get("last_name") from_name = doc.get("first_name") + " " + doc.get("last_name")
return from_name return from_name

View File

@ -69,6 +69,7 @@
{ {
"fieldname": "message", "fieldname": "message",
"fieldtype": "HTML Editor", "fieldtype": "HTML Editor",
"in_list_view": 1,
"label": "Message" "label": "Message"
}, },
{ {
@ -115,7 +116,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-04-25 13:55:23.014268", "modified": "2024-04-25 16:26:07.484857",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Notification", "name": "CRM Notification",

View File

@ -55,7 +55,11 @@
class="h-[5px] w-[5px] rounded-full" class="h-[5px] w-[5px] rounded-full"
:class="[n.read ? 'bg-transparent' : 'bg-gray-900']" :class="[n.read ? 'bg-transparent' : 'bg-gray-900']"
/> />
<UserAvatar :user="n.from_user.name" size="lg" /> <WhatsAppIcon
v-if="n.type == 'WhatsApp'"
class="size-7 rounded-full"
/>
<UserAvatar v-else :user="n.from_user.name" size="lg" />
</div> </div>
<div> <div>
<div v-if="n.notification_text" v-html="n.notification_text" /> <div v-if="n.notification_text" v-html="n.notification_text" />
@ -89,6 +93,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue' import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue' import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'