Merge branch 'develop' into exotel-fixes

This commit is contained in:
Ankush Menat
2022-03-28 19:55:39 +05:30
1641 changed files with 98266 additions and 70889 deletions

View File

@@ -11,8 +11,8 @@ from frappe.model.document import Document
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
END_CALL_STATUSES = ['No Answer', 'Completed', 'Busy', 'Failed']
ONGOING_CALL_STATUSES = ['Ringing', 'In Progress']
END_CALL_STATUSES = ["No Answer", "Completed", "Busy", "Failed"]
ONGOING_CALL_STATUSES = ["Ringing", "In Progress"]
class CallLog(Document):
@@ -20,23 +20,22 @@ class CallLog(Document):
deduplicate_dynamic_links(self)
def before_insert(self):
"""Add lead(third party person) links to the document.
"""
lead_number = self.get('from') if self.is_incoming_call() else self.get('to')
"""Add lead(third party person) links to the document."""
lead_number = self.get("from") if self.is_incoming_call() else self.get("to")
lead_number = strip_number(lead_number)
contact = get_contact_with_phone_number(strip_number(lead_number))
if contact:
self.add_link(link_type='Contact', link_name=contact)
self.add_link(link_type="Contact", link_name=contact)
lead = get_lead_with_phone_number(lead_number)
if lead:
self.add_link(link_type='Lead', link_name=lead)
self.add_link(link_type="Lead", link_name=lead)
# Add Employee Name
if self.is_incoming_call():
# Taking the last 10 digits of the number
employee_number = strip_number(self.get('to'))
employee_number = strip_number(self.get("to"))
employee = get_employees_with_number(self.get("to"))
self.call_received_by = employee[0].get("name")
@@ -48,29 +47,29 @@ class CallLog(Document):
def on_update(self):
def _is_call_missed(doc_before_save, doc_after_save):
# FIXME: This works for Exotel but not for all telepony providers
return doc_before_save.to != doc_after_save.to and doc_after_save.status not in END_CALL_STATUSES
return (
doc_before_save.to != doc_after_save.to and doc_after_save.status not in END_CALL_STATUSES
)
def _is_call_ended(doc_before_save, doc_after_save):
return doc_before_save.status not in END_CALL_STATUSES and self.status in END_CALL_STATUSES
doc_before_save = self.get_doc_before_save()
if not doc_before_save: return
if not doc_before_save:
return
if _is_call_missed(doc_before_save, self):
frappe.publish_realtime('call_{id}_missed'.format(id=self.id), self)
frappe.publish_realtime("call_{id}_missed".format(id=self.id), self)
self.trigger_call_popup()
if _is_call_ended(doc_before_save, self):
frappe.publish_realtime('call_{id}_ended'.format(id=self.id), self)
frappe.publish_realtime("call_{id}_ended".format(id=self.id), self)
def is_incoming_call(self):
return self.type == 'Incoming'
return self.type == "Incoming"
def add_link(self, link_type, link_name):
self.append('links', {
'link_doctype': link_type,
'link_name': link_name
})
self.append("links", {"link_doctype": link_type, "link_name": link_name})
def trigger_call_popup(self):
if self.is_incoming_call():
@@ -82,61 +81,72 @@ class CallLog(Document):
emails = set(scheduled_employees).intersection(employee_emails)
if frappe.conf.developer_mode:
self.add_comment(text=f"""
self.add_comment(
text=f"""
Scheduled Employees: {scheduled_employees}
Matching Employee: {employee_emails}
Show Popup To: {emails}
""")
"""
)
if employee_emails and not emails:
self.add_comment(text=_("No employee was scheduled for call popup"))
for email in emails:
frappe.publish_realtime('show_call_popup', self, user=email)
frappe.publish_realtime("show_call_popup", self, user=email)
def get_employee_name(emp):
employee_name = ''
for name in ['first_name', 'middle_name', 'last_name']:
employee_name = ""
for name in ["first_name", "middle_name", "last_name"]:
if emp.get(name):
employee_name += (' ' if employee_name else '') + emp.get(name)
employee_name += (" " if employee_name else "") + emp.get(name)
return employee_name
@frappe.whitelist()
def add_call_summary_and_call_type(call_log, summary, call_type):
doc = frappe.get_doc('Call Log', call_log)
doc = frappe.get_doc("Call Log", call_log)
doc.type_of_call = call_type
doc.save()
doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
doc.add_comment("Comment", frappe.bold(_("Call Summary")) + "<br><br>" + summary)
def get_employees_with_number(number):
number = strip_number(number)
if not number: return []
if not number:
return []
employee_doc_name_and_emails = frappe.cache().hget('employees_with_number', number)
if employee_doc_name_and_emails: return employee_doc_name_and_emails
employee_doc_name_and_emails = frappe.cache().hget("employees_with_number", number)
if employee_doc_name_and_emails:
return employee_doc_name_and_emails
employee_doc_name_and_emails = frappe.get_all('Employee', filters={
'cell_number': ['like', '%{}%'.format(number)],
'user_id': ['!=', '']
}, fields=['name', 'user_id'])
employee_doc_name_and_emails = frappe.get_all(
"Employee",
filters={"cell_number": ["like", "%{}%".format(number)], "user_id": ["!=", ""]},
fields=["name", "user_id"],
)
frappe.cache().hset('employees_with_number', number, employee_doc_name_and_emails)
frappe.cache().hset("employees_with_number", number, employee_doc_name_and_emails)
return employee_doc_name_and_emails
def link_existing_conversations(doc, state):
"""
Called from hooks on creation of Contact or Lead to link all the existing conversations.
"""
if doc.doctype != 'Contact': return
if doc.doctype != "Contact":
return
try:
numbers = [d.phone for d in doc.phone_nos]
for number in numbers:
number = strip_number(number)
if not number: continue
logs = frappe.db.sql_list("""
if not number:
continue
logs = frappe.db.sql_list(
"""
SELECT cl.name FROM `tabCall Log` cl
LEFT JOIN `tabDynamic Link` dl
ON cl.name = dl.parent
@@ -149,44 +159,42 @@ def link_existing_conversations(doc, state):
ELSE 0
END
)=0
""", dict(
phone_number='%{}'.format(number),
docname=doc.name,
doctype = doc.doctype
)
""",
dict(phone_number="%{}".format(number), docname=doc.name, doctype=doc.doctype),
)
for log in logs:
call_log = frappe.get_doc('Call Log', log)
call_log = frappe.get_doc("Call Log", log)
call_log.add_link(link_type=doc.doctype, link_name=doc.name)
call_log.save(ignore_permissions=True)
frappe.db.commit()
except Exception:
frappe.log_error(title=_('Error during caller information update'))
frappe.log_error(title=_("Error during caller information update"))
def get_linked_call_logs(doctype, docname):
# content will be shown in timeline
logs = frappe.get_all('Dynamic Link', fields=['parent'], filters={
'parenttype': 'Call Log',
'link_doctype': doctype,
'link_name': docname
})
logs = frappe.get_all(
"Dynamic Link",
fields=["parent"],
filters={"parenttype": "Call Log", "link_doctype": doctype, "link_name": docname},
)
logs = set([log.parent for log in logs])
logs = frappe.get_all('Call Log', fields=['*'], filters={
'name': ['in', logs]
})
logs = frappe.get_all("Call Log", fields=["*"], filters={"name": ["in", logs]})
timeline_contents = []
for log in logs:
log.show_call_button = 0
timeline_contents.append({
'icon': 'call',
'is_card': True,
'creation': log.creation,
'template': 'call_link',
'template_data': log
})
timeline_contents.append(
{
"icon": "call",
"is_card": True,
"creation": log.creation,
"template": "call_link",
"template_data": log,
}
)
return timeline_contents

View File

@@ -20,35 +20,38 @@ class IncomingCallSettings(Document):
self.validate_call_schedule_overlaps(self.call_handling_schedule)
def validate_call_schedule_timeslot(self, schedule: list):
""" Make sure that to time slot is ahead of from time slot.
"""
"""Make sure that to time slot is ahead of from time slot."""
errors = []
for record in schedule:
from_time = self.time_to_seconds(record.from_time)
to_time = self.time_to_seconds(record.to_time)
if from_time >= to_time:
errors.append(
_('Call Schedule Row {0}: To time slot should always be ahead of From time slot.').format(record.idx)
_("Call Schedule Row {0}: To time slot should always be ahead of From time slot.").format(
record.idx
)
)
if errors:
frappe.throw('<br/>'.join(errors))
frappe.throw("<br/>".join(errors))
def validate_call_schedule_overlaps(self, schedule: list):
"""Check if any time slots are overlapped in a day schedule.
"""
"""Check if any time slots are overlapped in a day schedule."""
week_days = set([each.day_of_week for each in schedule])
for day in week_days:
timeslots = [(record.from_time, record.to_time) for record in schedule if record.day_of_week==day]
timeslots = [
(record.from_time, record.to_time) for record in schedule if record.day_of_week == day
]
# convert time in timeslot into an integer represents number of seconds
timeslots = sorted(map(lambda seq: tuple(map(self.time_to_seconds, seq)), timeslots))
if len(timeslots) < 2: continue
if len(timeslots) < 2:
continue
for i in range(1, len(timeslots)):
if self.check_timeslots_overlap(timeslots[i-1], timeslots[i]):
frappe.throw(_('Please fix overlapping time slots for {0}.').format(day))
if self.check_timeslots_overlap(timeslots[i - 1], timeslots[i]):
frappe.throw(_("Please fix overlapping time slots for {0}.").format(day))
@staticmethod
def check_timeslots_overlap(ts1: Tuple[int, int], ts2: Tuple[int, int]) -> bool:
@@ -58,7 +61,6 @@ class IncomingCallSettings(Document):
@staticmethod
def time_to_seconds(time: str) -> int:
"""Convert time string of format HH:MM:SS into seconds
"""
"""Convert time string of format HH:MM:SS into seconds"""
date_time = datetime.strptime(time, "%H:%M:%S")
return date_time - datetime(1900, 1, 1)