feat: Call Popup and Exotel Integration (#17763)

feat: Call Popup and Exotel Integration
This commit is contained in:
Faris Ansari 2019-07-01 16:36:32 +05:30 committed by GitHub
commit 8d2c996b50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 835 additions and 105 deletions

View File

View File

@ -0,0 +1,106 @@
{
"autoname": "field:id",
"creation": "2019-06-05 12:07:02.634534",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"id",
"from",
"to",
"column_break_3",
"medium",
"section_break_5",
"status",
"duration",
"recording_url",
"summary"
],
"fields": [
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fieldname": "id",
"fieldtype": "Data",
"label": "ID",
"read_only": 1,
"unique": 1
},
{
"fieldname": "from",
"fieldtype": "Data",
"in_list_view": 1,
"label": "From",
"read_only": 1
},
{
"fieldname": "to",
"fieldtype": "Data",
"label": "To",
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Ringing\nIn Progress\nCompleted\nMissed",
"read_only": 1
},
{
"description": "Call Duration in seconds",
"fieldname": "duration",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Duration",
"read_only": 1
},
{
"fieldname": "summary",
"fieldtype": "Data",
"label": "Summary",
"read_only": 1
},
{
"fieldname": "recording_url",
"fieldtype": "Data",
"label": "Recording URL",
"read_only": 1
},
{
"fieldname": "medium",
"fieldtype": "Data",
"label": "Medium",
"read_only": 1
}
],
"in_create": 1,
"modified": "2019-07-01 09:09:48.516722",
"modified_by": "Administrator",
"module": "Communication",
"name": "Call Log",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "from",
"track_changes": 1
}

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from erpnext.crm.doctype.utils import get_employee_emails_for_popup
class CallLog(Document):
def after_insert(self):
employee_emails = get_employee_emails_for_popup(self.medium)
for email in employee_emails:
frappe.publish_realtime('show_call_popup', self, user=email)
def on_update(self):
doc_before_save = self.get_doc_before_save()
if doc_before_save and doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self)

View File

@ -0,0 +1,81 @@
{
"autoname": "Prompt",
"creation": "2019-06-05 11:48:30.572795",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"communication_medium_type",
"catch_all",
"column_break_3",
"provider",
"disabled",
"timeslots_section",
"timeslots"
],
"fields": [
{
"fieldname": "communication_medium_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Communication Medium Type",
"options": "Voice\nEmail\nChat",
"reqd": 1
},
{
"description": "If there is no assigned timeslot, then communication will be handled by this group",
"fieldname": "catch_all",
"fieldtype": "Link",
"label": "Catch All",
"options": "Employee Group"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "provider",
"fieldtype": "Link",
"label": "Provider",
"options": "Supplier"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "timeslots_section",
"fieldtype": "Section Break",
"label": "Timeslots"
},
{
"fieldname": "timeslots",
"fieldtype": "Table",
"label": "Timeslots",
"options": "Communication Medium Timeslot"
}
],
"modified": "2019-06-05 11:49:30.769006",
"modified_by": "Administrator",
"module": "Communication",
"name": "Communication Medium",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class CommunicationMedium(Document):
pass

View File

@ -0,0 +1,56 @@
{
"creation": "2019-06-05 11:43:38.897272",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"day_of_week",
"from_time",
"to_time",
"employee_group"
],
"fields": [
{
"fieldname": "day_of_week",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Day of Week",
"options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
"reqd": 1
},
{
"columns": 2,
"fieldname": "from_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "From Time",
"reqd": 1
},
{
"columns": 2,
"fieldname": "to_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "To Time",
"reqd": 1
},
{
"fieldname": "employee_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee Group",
"options": "Employee Group",
"reqd": 1
}
],
"istable": 1,
"modified": "2019-06-05 12:19:59.994979",
"modified_by": "Administrator",
"module": "Communication",
"name": "Communication Medium Timeslot",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class CommunicationMediumTimeslot(Document):
pass

View File

@ -0,0 +1,100 @@
import frappe
from frappe import _
import json
@frappe.whitelist()
def get_document_with_phone_number(number):
# finds contacts and leads
if not number: return
number = number.lstrip('0')
number_filter = {
'phone': ['like', '%{}'.format(number)],
'mobile_no': ['like', '%{}'.format(number)]
}
contacts = frappe.get_all('Contact', or_filters=number_filter, limit=1)
if contacts:
return frappe.get_doc('Contact', contacts[0].name)
leads = frappe.get_all('Lead', or_filters=number_filter, limit=1)
if leads:
return frappe.get_doc('Lead', leads[0].name)
@frappe.whitelist()
def get_last_interaction(number, reference_doc):
reference_doc = json.loads(reference_doc) if reference_doc else get_document_with_phone_number(number)
if not reference_doc: return
reference_doc = frappe._dict(reference_doc)
last_communication = {}
last_issue = {}
if reference_doc.doctype == 'Contact':
customer_name = ''
query_condition = ''
for link in reference_doc.links:
link = frappe._dict(link)
if link.link_doctype == 'Customer':
customer_name = link.link_name
query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name)
if query_condition:
query_condition = query_condition[:-2]
last_communication = frappe.db.sql("""
SELECT `name`, `content`
FROM `tabCommunication`
WHERE {}
ORDER BY `modified`
LIMIT 1
""".format(query_condition)) # nosec
if customer_name:
last_issue = frappe.get_all('Issue', {
'customer': customer_name
}, ['name', 'subject', 'customer'], limit=1)
elif reference_doc.doctype == 'Lead':
last_communication = frappe.get_all('Communication', filters={
'reference_doctype': reference_doc.doctype,
'reference_name': reference_doc.name,
'sent_or_received': 'Received'
}, fields=['name', 'content'], limit=1)
return {
'last_communication': last_communication[0] if last_communication else None,
'last_issue': last_issue[0] if last_issue else None
}
@frappe.whitelist()
def add_call_summary(docname, summary):
call_log = frappe.get_doc('Call Log', docname)
summary = _('Call Summary by {0}: {1}').format(
frappe.utils.get_fullname(frappe.session.user), summary)
if not call_log.summary:
call_log.summary = summary
else:
call_log.summary += '<br>' + summary
call_log.save(ignore_permissions=True)
def get_employee_emails_for_popup(communication_medium):
now_time = frappe.utils.nowtime()
weekday = frappe.utils.get_weekday()
available_employee_groups = frappe.get_all("Communication Medium Timeslot", filters={
'day_of_week': weekday,
'parent': communication_medium,
'from_time': ['<=', now_time],
'to_time': ['>=', now_time],
}, fields=['employee_group'], debug=1)
available_employee_groups = tuple([emp.employee_group for emp in available_employee_groups])
employees = frappe.get_all('Employee Group Table', filters={
'parent': ['in', available_employee_groups]
}, fields=['user_id'])
employee_emails = set([employee.user_id for employee in employees])
return employee_emails

View File

@ -0,0 +1,61 @@
{
"creation": "2019-05-21 07:41:53.536536",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"enabled",
"section_break_2",
"account_sid",
"api_key",
"api_token"
],
"fields": [
{
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"depends_on": "enabled",
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "account_sid",
"fieldtype": "Data",
"label": "Account SID"
},
{
"fieldname": "api_token",
"fieldtype": "Data",
"label": "API Token"
},
{
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key"
}
],
"issingle": 1,
"modified": "2019-05-22 06:25:18.026997",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Exotel Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
import requests
import frappe
from frappe import _
class ExotelSettings(Document):
def validate(self):
self.verify_credentials()
def verify_credentials(self):
if self.enabled:
response = requests.get('https://api.exotel.com/v1/Accounts/{sid}'
.format(sid = self.account_sid), auth=(self.api_key, self.api_token))
if response.status_code != 200:
frappe.throw(_("Invalid credentials"))

View File

@ -0,0 +1,101 @@
import frappe
import requests
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_incoming_call
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_end_call
# api/method/erpnext.erpnext_integrations.exotel_integration.handle_missed_call
@frappe.whitelist(allow_guest=True)
def handle_incoming_call(**kwargs):
exotel_settings = get_exotel_settings()
if not exotel_settings.enabled: return
call_payload = kwargs
status = call_payload.get('Status')
if status == 'free':
return
call_log = get_call_log(call_payload)
if not call_log:
create_call_log(call_payload)
@frappe.whitelist(allow_guest=True)
def handle_end_call(**kwargs):
update_call_log(kwargs, 'Completed')
@frappe.whitelist(allow_guest=True)
def handle_missed_call(**kwargs):
update_call_log(kwargs, 'Missed')
def update_call_log(call_payload, status):
call_log = get_call_log(call_payload)
if call_log:
call_log.status = status
call_log.duration = call_payload.get('DialCallDuration') or 0
call_log.recording_url = call_payload.get('RecordingUrl')
call_log.save(ignore_permissions=True)
frappe.db.commit()
return call_log
def get_call_log(call_payload):
call_log = frappe.get_all('Call Log', {
'id': call_payload.get('CallSid'),
}, limit=1)
if call_log:
return frappe.get_doc('Call Log', call_log[0].name)
def create_call_log(call_payload):
call_log = frappe.new_doc('Call Log')
call_log.id = call_payload.get('CallSid')
call_log.to = call_payload.get('CallTo')
call_log.medium = call_payload.get('To')
call_log.status = 'Ringing'
setattr(call_log, 'from', call_payload.get('CallFrom'))
call_log.save(ignore_permissions=True)
frappe.db.commit()
return call_log
@frappe.whitelist()
def get_call_status(call_id):
endpoint = get_exotel_endpoint('Calls/{call_id}.json'.format(call_id=call_id))
response = requests.get(endpoint)
status = response.json().get('Call', {}).get('Status')
return status
@frappe.whitelist()
def make_a_call(from_number, to_number, caller_id):
endpoint = get_exotel_endpoint('Calls/connect.json?details=true')
response = requests.post(endpoint, data={
'From': from_number,
'To': to_number,
'CallerId': caller_id
})
return response.json()
def get_exotel_settings():
return frappe.get_single('Exotel Settings')
def whitelist_numbers(numbers, caller_id):
endpoint = get_exotel_endpoint('CustomerWhitelist')
response = requests.post(endpoint, data={
'VirtualNumber': caller_id,
'Number': numbers,
})
return response
def get_all_exophones():
endpoint = get_exotel_endpoint('IncomingPhoneNumbers')
response = requests.post(endpoint)
return response
def get_exotel_endpoint(action):
settings = get_exotel_settings()
return 'https://{api_key}:{api_token}@api.exotel.com/v1/Accounts/{sid}/{action}'.format(
api_key=settings.api_key,
api_token=settings.api_token,
sid=settings.account_sid,
action=action
)

View File

@ -169,6 +169,11 @@ default_roles = [
{'role': 'Student', 'doctype':'Student', 'email_field': 'student_email_id'},
]
sounds = [
{"name": "incoming-call", "src": "/assets/erpnext/sounds/incoming-call.mp3", "volume": 0.2},
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
]
has_website_permission = {
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",

View File

@ -1,109 +1,45 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-11-19 12:39:46.153061",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2018-11-19 12:39:46.153061",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"user_id"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.first_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fetch_from": "employee.first_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Employee Name"
},
{
"fetch_from": "employee.user_id",
"fieldname": "user_id",
"fieldtype": "Data",
"label": "ERPNext User ID",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-11-19 13:18:17.281656",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Group Table",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"istable": 1,
"modified": "2019-06-06 10:41:20.313756",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Group Table",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -22,4 +22,5 @@ ERPNext Integrations
Non Profit
Hotels
Hub Node
Quality Management
Quality Management
Communication

View File

@ -1,7 +1,8 @@
{
"css/erpnext.css": [
"public/less/erpnext.less",
"public/less/hub.less"
"public/less/hub.less",
"public/less/call_popup.less"
],
"css/marketplace.css": [
"public/less/hub.less"
@ -49,6 +50,7 @@
"public/js/education/student_button.html",
"public/js/education/assessment_result_tool.html",
"public/js/hub/hub_factory.js",
"public/js/call_popup/call_popup.js",
"public/js/utils/dimension_tree_filter.js"
],
"js/item-dashboard.min.js": [

View File

@ -0,0 +1,212 @@
class CallPopup {
constructor(call_log) {
this.caller_number = call_log.from;
this.call_log = call_log;
this.setup_listener();
this.make();
}
make() {
this.dialog = new frappe.ui.Dialog({
'static': true,
'minimizable': true,
'fields': [{
'fieldname': 'caller_info',
'fieldtype': 'HTML'
}, {
'fielname': 'last_interaction',
'fieldtype': 'Section Break',
'label': __('Activity'),
}, {
'fieldtype': 'Small Text',
'label': __('Last Communication'),
'fieldname': 'last_communication',
'read_only': true,
'default': `<i class="text-muted">${__('No communication found.')}<i>`
}, {
'fieldtype': 'Small Text',
'label': __('Last Issue'),
'fieldname': 'last_issue',
'read_only': true,
'default': `<i class="text-muted">${__('No issue raised by the customer.')}<i>`
}, {
'fieldtype': 'Column Break',
}, {
'fieldtype': 'Small Text',
'label': __('Call Summary'),
'fieldname': 'call_summary',
}, {
'fieldtype': 'Button',
'label': __('Save'),
'click': () => {
const call_summary = this.dialog.get_value('call_summary');
if (!call_summary) return;
frappe.xcall('erpnext.crm.doctype.utils.add_call_summary', {
'docname': this.call_log.id,
'summary': call_summary,
}).then(() => {
this.close_modal();
frappe.show_alert({
message: `${__('Call Summary Saved')}<br><a class="text-small text-muted" href="#Form/Call Log/${this.call_log.name}">${__('View call log')}</a>`,
indicator: 'green'
});
});
}
}],
});
this.set_call_status();
this.make_caller_info_section();
this.dialog.get_close_btn().show();
this.dialog.$body.addClass('call-popup');
this.dialog.set_secondary_action(this.close_modal.bind(this));
frappe.utils.play_sound('incoming-call');
this.dialog.show();
}
make_caller_info_section() {
const wrapper = this.dialog.get_field('caller_info').$wrapper;
wrapper.append(`<div class="text-muted"> ${__("Loading...")} </div>`);
frappe.xcall('erpnext.crm.doctype.utils.get_document_with_phone_number', {
'number': this.caller_number
}).then(contact_doc => {
wrapper.empty();
const contact = this.contact = contact_doc;
if (!contact) {
this.setup_unknown_caller(wrapper);
} else {
this.setup_known_caller(wrapper);
this.set_call_status();
this.make_last_interaction_section();
}
});
}
setup_unknown_caller(wrapper) {
wrapper.append(`
<div class="caller-info">
<b>${__('Unknown Number')}:</b> ${this.caller_number}
<button
class="margin-left btn btn-new btn-default btn-xs"
data-doctype="Contact"
title=${__("Make New Contact")}>
<i class="octicon octicon-plus text-medium"></i>
</button>
</div>
`).find('button').click(
() => frappe.set_route(`Form/Contact/New Contact?phone=${this.caller_number}`)
);
}
setup_known_caller(wrapper) {
const contact = this.contact;
const contact_name = frappe.utils.get_form_link(contact.doctype, contact.name, true, this.get_caller_name());
const links = contact.links ? contact.links : [];
let contact_links = '';
links.forEach(link => {
contact_links += `<div>${link.link_doctype}: ${frappe.utils.get_form_link(link.link_doctype, link.link_name, true)}</div>`;
});
wrapper.append(`
<div class="caller-info flex">
${frappe.avatar(null, 'avatar-xl', contact.name, contact.image)}
<div>
<h5>${contact_name}</h5>
<div>${contact.mobile_no || ''}</div>
<div>${contact.phone_no || ''}</div>
${contact_links}
</div>
</div>
`);
}
set_indicator(color, blink=false) {
let classes = `indicator ${color} ${blink ? 'blink': ''}`;
this.dialog.header.find('.indicator').attr('class', classes);
}
set_call_status(call_status) {
let title = '';
call_status = call_status || this.call_log.status;
if (['Ringing'].includes(call_status) || !call_status) {
title = __('Incoming call from {0}', [this.get_caller_name()]);
this.set_indicator('blue', true);
} else if (call_status === 'In Progress') {
title = __('Call Connected');
this.set_indicator('yellow');
} else if (call_status === 'Missed') {
this.set_indicator('red');
title = __('Call Missed');
} else if (['Completed', 'Disconnected'].includes(call_status)) {
this.set_indicator('red');
title = __('Call Disconnected');
} else {
this.set_indicator('blue');
title = call_status;
}
this.dialog.set_title(title);
}
update_call_log(call_log) {
this.call_log = call_log;
this.set_call_status();
}
close_modal() {
this.dialog.hide();
delete erpnext.call_popup;
}
call_disconnected(call_log) {
frappe.utils.play_sound('call-disconnect');
this.update_call_log(call_log);
setTimeout(() => {
if (!this.dialog.get_value('call_summary')) {
this.close_modal();
}
}, 10000);
}
make_last_interaction_section() {
frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', {
'number': this.caller_number,
'reference_doc': this.contact
}).then(data => {
const comm_field = this.dialog.get_field('last_communication');
if (data.last_communication) {
const comm = data.last_communication;
comm_field.set_value(comm.content);
}
if (data.last_issue) {
const issue = data.last_issue;
const issue_field = this.dialog.get_field("last_issue");
issue_field.set_value(issue.subject);
issue_field.$wrapper.append(`<a class="text-medium" href="#List/Issue?customer=${issue.customer}">
${__('View all issues from {0}', [issue.customer])}
</a>`);
}
});
}
get_caller_name() {
return this.contact ? this.contact.lead_name || this.contact.name || '' : this.caller_number;
}
setup_listener() {
frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => {
this.call_disconnected(call_log);
// Remove call disconnect listener after the call is disconnected
frappe.realtime.off(`call_${this.call_log.id}_disconnected`);
});
}
}
$(document).on('app_ready', function () {
frappe.realtime.on('show_call_popup', call_log => {
if (!erpnext.call_popup) {
erpnext.call_popup = new CallPopup(call_log);
} else {
erpnext.call_popup.update_call_log(call_log);
erpnext.call_popup.dialog.show();
}
});
});

View File

@ -0,0 +1,9 @@
.call-popup {
a:hover {
text-decoration: underline;
}
.for-description {
max-height: 250px;
overflow: scroll;
}
}

Binary file not shown.

Binary file not shown.