diff --git a/crm/api/whatsapp.py b/crm/api/whatsapp.py index 3e04b5b62..163a75e43 100644 --- a/crm/api/whatsapp.py +++ b/crm/api/whatsapp.py @@ -1,353 +1,339 @@ -import frappe import json + +import frappe from frappe import _ + from crm.api.doc import get_assigned_users from crm.fcrm.doctype.crm_notification.crm_notification import notify_user 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) + 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""" + 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: - notify_user( - { - "owner": doc.owner, - "assigned_to": user, - "notification_type": "WhatsApp", - "message": doc.message, - "notification_text": notification_text, - "reference_doctype": "WhatsApp Message", - "reference_docname": doc.name, - "redirect_to_doctype": doc.reference_doctype, - "redirect_to_docname": doc.reference_name, - } - ) + assigned_users = get_assigned_users(doc.reference_doctype, doc.reference_name) + for user in assigned_users: + notify_user( + { + "owner": doc.owner, + "assigned_to": user, + "notification_type": "WhatsApp", + "message": doc.message, + "notification_text": notification_text, + "reference_doctype": "WhatsApp Message", + "reference_docname": doc.name, + "redirect_to_doctype": doc.reference_doctype, + "redirect_to_docname": doc.reference_name, + } + ) 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) + def find_record(doctype, mobile_no, where=""): + mobile_no = parse_mobile_no(mobile_no) - query = f""" + 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 is_whatsapp_installed(): - if not frappe.db.exists("DocType", "WhatsApp Settings"): - return False - return True + if not frappe.db.exists("DocType", "WhatsApp Settings"): + return False + return True @frappe.whitelist() def get_whatsapp_messages(reference_doctype, reference_name): - if not frappe.db.exists("DocType", "WhatsApp Message"): - return [] - messages = [] - - if reference_doctype == "CRM Deal": - lead = frappe.db.get_value(reference_doctype, reference_name, "lead") - if lead: - messages = frappe.get_all( - "WhatsApp Message", - filters={ - "reference_doctype": "CRM Lead", - "reference_name": lead, - }, - 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", - ], - ) - - 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" - ] - - # 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 - ) - - 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 - - # Filter messages to get only reaction messages - reaction_messages = [ - message for message in messages if message["content_type"] == "reaction" - ] - - # 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"] - - 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"] + # twilio integration app is not compatible with crm app + # crm has its own twilio integration in built + if "twilio-integration" in frappe.get_installed_apps(): + return [] + if not frappe.db.exists("DocType", "WhatsApp Message"): + return [] + messages = [] + + if reference_doctype == "CRM Deal": + lead = frappe.db.get_value(reference_doctype, reference_name, "lead") + if lead: + messages = frappe.get_all( + "WhatsApp Message", + filters={ + "reference_doctype": "CRM Lead", + "reference_name": lead, + }, + 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", + ], + ) + + 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"] + + # 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) + + 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 + + # Filter messages to get only reaction messages + reaction_messages = [message for message in messages if message["content_type"] == "reaction"] + + # 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"] + + 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"] @frappe.whitelist() def create_whatsapp_message( - reference_doctype, - reference_name, - message, - to, - attach, - reply_to, - content_type="text", + 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, - } - ) - - 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 = 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, + } + ) + + 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 + 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