From cfae3101c53f3cf5fa81ddbaf95ad56f121038dd Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Sat, 18 Jan 2025 20:34:04 +0530 Subject: [PATCH] fix: fix twilio contact viewing, note creation & linking code --- crm/integrations/api.py | 63 +++++++---- crm/integrations/twilio/api.py | 104 +++++++----------- crm/integrations/twilio/utils.py | 7 -- .../src/components/Telephony/ExotelCallUI.vue | 93 +++++++++++----- .../src/components/Telephony/TwilioCallUI.vue | 81 +++++++------- 5 files changed, 190 insertions(+), 158 deletions(-) diff --git a/crm/integrations/api.py b/crm/integrations/api.py index 7b8a1b43..145bf03a 100644 --- a/crm/integrations/api.py +++ b/crm/integrations/api.py @@ -24,34 +24,51 @@ def set_default_calling_medium(medium): @frappe.whitelist() -def create_and_add_note_to_call_log(call_sid, content): - """Add note to call log based on call sid.""" - note = frappe.get_doc( - { - "doctype": "FCRM Note", - "content": content, - } - ).insert(ignore_permissions=True) +def add_note_to_call_log(call_sid, note): + """Add/Update note to call log based on call sid.""" + _note = None + if not note.get("name"): + _note = frappe.get_doc( + { + "doctype": "FCRM Note", + "content": note.get("content"), + } + ).insert(ignore_permissions=True) + call_log = frappe.get_cached_doc("CRM Call Log", call_sid) + call_log.link_with_reference_doc("FCRM Note", _note.name) + call_log.save(ignore_permissions=True) + else: + _note = frappe.set_value("FCRM Note", note.get("name"), "content", note.get("content")) - call_log = frappe.get_doc("CRM Call Log", call_sid) - call_log.link_with_reference_doc("FCRM Note", note.name) - call_log.save(ignore_permissions=True) + return _note @frappe.whitelist() -def create_and_add_task_to_call_log(call_sid, task): - """Add task to call log based on call sid.""" - _task = frappe.get_doc( - { - "doctype": "CRM Task", - "title": task.get("title"), - "description": task.get("description"), - } - ).insert(ignore_permissions=True) +def add_task_to_call_log(call_sid, task): + """Add/Update task to call log based on call sid.""" + _task = None + if not task.get("name"): + _task = frappe.get_doc( + { + "doctype": "CRM Task", + "title": task.get("title"), + "description": task.get("description"), + } + ).insert(ignore_permissions=True) + call_log = frappe.get_doc("CRM Call Log", call_sid) + call_log.link_with_reference_doc("CRM Task", _task.name) + call_log.save(ignore_permissions=True) + else: + _task = frappe.get_doc("CRM Task", task.get("name")) + _task.update( + { + "title": task.get("title"), + "description": task.get("description"), + } + ) + _task.save(ignore_permissions=True) - call_log = frappe.get_doc("CRM Call Log", call_sid) - call_log.link_with_reference_doc("CRM Task", _task.name) - call_log.save(ignore_permissions=True) + return _task @frappe.whitelist() diff --git a/crm/integrations/twilio/api.py b/crm/integrations/twilio/api.py index c88b6a58..e78e0214 100644 --- a/crm/integrations/twilio/api.py +++ b/crm/integrations/twilio/api.py @@ -4,8 +4,9 @@ import frappe from frappe import _ from werkzeug.wrappers import Response +from crm.integrations.api import get_contact_by_phone_number + from .twilio_handler import IncomingCall, Twilio, TwilioCallDetails -from .utils import parse_mobile_no @frappe.whitelist() @@ -69,13 +70,31 @@ def twilio_incoming_call_handler(**kwargs): def create_call_log(call_details: TwilioCallDetails): - call_log = frappe.get_doc( - {**call_details.to_dict(), "doctype": "CRM Call Log", "telephony_medium": "Twilio"} - ) - call_log.reference_docname, call_log.reference_doctype = get_lead_or_deal_from_number(call_log) - call_log.flags.ignore_permissions = True - call_log.save() + details = call_details.to_dict() + + call_log = frappe.get_doc({**details, "doctype": "CRM Call Log", "telephony_medium": "Twilio"}) + + # link call log with lead/deal + contact_number = details.get("from") if details.get("type") == "Incoming" else details.get("to") + link(contact_number, call_log) + + call_log.save(ignore_permissions=True) frappe.db.commit() + return call_log + + +def link(contact_number, call_log): + contact = get_contact_by_phone_number(contact_number) + if contact.get("name"): + doctype = "Contact" + docname = contact.get("name") + if contact.get("lead"): + doctype = "CRM Lead" + docname = contact.get("lead") + elif contact.get("deal"): + doctype = "CRM Deal" + docname = contact.get("deal") + call_log.link_with_reference_doc(doctype, docname) def update_call_log(call_sid, status=None): @@ -84,18 +103,19 @@ def update_call_log(call_sid, status=None): if not (twilio and frappe.db.exists("CRM Call Log", call_sid)): return - call_details = twilio.get_call_info(call_sid) - call_log = frappe.get_doc("CRM Call Log", call_sid) - call_log.status = TwilioCallDetails.get_call_status(status or call_details.status) - call_log.duration = call_details.duration - call_log.start_time = get_datetime_from_timestamp(call_details.start_time) - call_log.end_time = get_datetime_from_timestamp(call_details.end_time) - if call_log.note and call_log.reference_docname: - frappe.db.set_value("FCRM Note", call_log.note, "reference_doctype", call_log.reference_doctype) - frappe.db.set_value("FCRM Note", call_log.note, "reference_docname", call_log.reference_docname) - call_log.flags.ignore_permissions = True - call_log.save() - frappe.db.commit() + try: + call_details = twilio.get_call_info(call_sid) + call_log = frappe.get_doc("CRM Call Log", call_sid) + call_log.status = TwilioCallDetails.get_call_status(status or call_details.status) + call_log.duration = call_details.duration + call_log.start_time = get_datetime_from_timestamp(call_details.start_time) + call_log.end_time = get_datetime_from_timestamp(call_details.end_time) + call_log.save(ignore_permissions=True) + frappe.db.commit() + return call_log + except Exception: + frappe.log_error(title="Error while updating call record") + frappe.db.commit() @frappe.whitelist(allow_guest=True) @@ -106,7 +126,7 @@ def update_recording_info(**kwargs): call_sid = args.CallSid update_call_log(call_sid) frappe.db.set_value("CRM Call Log", call_sid, "recording_url", recording_url) - except: + except Exception: frappe.log_error(title=_("Failed to capture Twilio recording")) @@ -128,7 +148,7 @@ def update_call_status_info(**kwargs): client = Twilio.get_twilio_client() client.calls(args.ParentCallSid).user_defined_messages.create(content=json.dumps(call_info)) - except: + except Exception: frappe.log_error(title=_("Failed to update Twilio call status")) @@ -144,45 +164,3 @@ def get_datetime_from_timestamp(timestamp): system_timezone = frappe.utils.get_system_timezone() converted_datetime = datetime_utc_tz.astimezone(ZoneInfo(system_timezone)) return frappe.utils.format_datetime(converted_datetime, "yyyy-MM-dd HH:mm:ss") - - -@frappe.whitelist() -def add_note_to_call_log(call_sid, note): - """Add note to call log. based on child call sid.""" - twilio = Twilio.connect() - if not twilio: - return - - call_details = twilio.get_call_info(call_sid) - sid = call_sid if call_details.direction == "inbound" else call_details.parent_call_sid - - frappe.db.set_value("CRM Call Log", sid, "note", note) - frappe.db.commit() - - -def get_lead_or_deal_from_number(call): - """Get lead/deal from the given number.""" - - 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 - - doctype = "CRM Deal" - number = call.get("to") if call.type == "Outgoing" else call.get("from") - - 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 diff --git a/crm/integrations/twilio/utils.py b/crm/integrations/twilio/utils.py index a47e604d..0430f311 100644 --- a/crm/integrations/twilio/utils.py +++ b/crm/integrations/twilio/utils.py @@ -15,10 +15,3 @@ def merge_dicts(d1: dict, d2: dict): """ return {k: {**v, **d2.get(k, {})} for k, v in d1.items()} - -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 == "+"]) diff --git a/frontend/src/components/Telephony/ExotelCallUI.vue b/frontend/src/components/Telephony/ExotelCallUI.vue index 6ff354c6..1e52af5d 100644 --- a/frontend/src/components/Telephony/ExotelCallUI.vue +++ b/frontend/src/components/Telephony/ExotelCallUI.vue @@ -146,8 +146,8 @@ ref="content" editor-class="prose-sm h-[290px] text-ink-white overflow-auto mt-1" :bubbleMenu="true" - :content="note" - @change="(val) => (note = val)" + :content="note.content" + @change="(val) => (note.content = val)" :placeholder="__('Take a note...')" /> @@ -210,8 +210,21 @@ +