Merge branch 'develop' of github.com:frappe/erpnext into refactor-call-popup
diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json
index 110030d..508ade9 100644
--- a/erpnext/communication/doctype/call_log/call_log.json
+++ b/erpnext/communication/doctype/call_log/call_log.json
@@ -8,12 +8,18 @@
"from",
"to",
"column_break_3",
+ "received_by",
"medium",
+ "caller_information",
+ "contact",
+ "contact_name",
+ "column_break_10",
+ "lead",
+ "lead_name",
"section_break_5",
"status",
"duration",
- "recording_url",
- "summary"
+ "recording_url"
],
"fields": [
{
@@ -61,12 +67,6 @@
"read_only": 1
},
{
- "fieldname": "summary",
- "fieldtype": "Data",
- "label": "Summary",
- "read_only": 1
- },
- {
"fieldname": "recording_url",
"fieldtype": "Data",
"label": "Recording URL",
@@ -77,10 +77,56 @@
"fieldtype": "Data",
"label": "Medium",
"read_only": 1
+ },
+ {
+ "fieldname": "received_by",
+ "fieldtype": "Link",
+ "label": "Received By",
+ "options": "Employee",
+ "read_only": 1
+ },
+ {
+ "fieldname": "caller_information",
+ "fieldtype": "Section Break",
+ "label": "Caller Information"
+ },
+ {
+ "fieldname": "contact",
+ "fieldtype": "Link",
+ "label": "Contact",
+ "options": "Contact",
+ "read_only": 1
+ },
+ {
+ "fieldname": "lead",
+ "fieldtype": "Link",
+ "label": "Lead ",
+ "options": "Lead",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "contact.name",
+ "fieldname": "contact_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "lead.lead_name",
+ "fieldname": "lead_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Lead Name",
+ "read_only": 1
}
],
"in_create": 1,
- "modified": "2019-07-01 09:09:48.516722",
+ "modified": "2019-07-15 19:18:31.267379",
"modified_by": "Administrator",
"module": "Communication",
"name": "Call Log",
@@ -97,10 +143,15 @@
"role": "System Manager",
"share": 1,
"write": 1
+ },
+ {
+ "read": 1,
+ "role": "Employee"
}
],
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "from",
- "track_changes": 1
+ "track_changes": 1,
+ "track_views": 1
}
\ No newline at end of file
diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py
index 66f1064..fae649c 100644
--- a/erpnext/communication/doctype/call_log/call_log.py
+++ b/erpnext/communication/doctype/call_log/call_log.py
@@ -4,16 +4,52 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.model.document import Document
from erpnext.crm.doctype.utils import get_employee_emails_for_popup
+from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number
+from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
class CallLog(Document):
+ def before_insert(self):
+ number = self.get('from').lstrip('0')
+ self.contact = get_contact_with_phone_number(number)
+ self.lead = get_lead_with_phone_number(number)
+
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)
+ self.trigger_call_popup()
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']:
+ if not doc_before_save: return
+ if doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self)
+ elif doc_before_save.to != self.to:
+ self.trigger_call_popup()
+
+ def trigger_call_popup(self):
+ employee_email = get_employee_email(self.to)
+ if employee_email:
+ frappe.publish_realtime('show_call_popup', self, user=employee_email)
+
+@frappe.whitelist()
+def add_call_summary(call_log, summary):
+ doc = frappe.get_doc('Call Log', call_log)
+ doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
+
+def get_employee_email(number):
+ if not number: return
+ number = number.lstrip('0')
+
+ employee = frappe.cache().hget('employee_with_number', number)
+ if employee: return employee
+
+ employees = frappe.get_all('Employee', or_filters={
+ 'phone': ['like', '%{}'.format(number)],
+ 'user_id': ['!=', '']
+ }, fields=['user_id'], limit=1)
+
+ employee = employees[0].user_id if employees else None
+ frappe.cache().hset('employee_with_number', number, employee)
+
+ return employee
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 4343db0..3ee6844 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -230,3 +230,15 @@
link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links)
return lead_name
+
+def get_lead_with_phone_number(number):
+ if not number: return
+
+ leads = frappe.get_all('Lead', or_filters={
+ 'phone': ['like', '%{}'.format(number)],
+ 'mobile_no': ['like', '%{}'.format(number)]
+ }, limit=1)
+
+ lead = leads[0].name if leads else None
+
+ return lead
\ No newline at end of file
diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py
index 9cfab15..7e9a8f4 100644
--- a/erpnext/crm/doctype/utils.py
+++ b/erpnext/crm/doctype/utils.py
@@ -3,80 +3,54 @@
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)
+def get_last_interaction(contact=None, lead=None):
- 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)
+ if not contact and not lead: return
last_communication = {}
last_issue = {}
- if reference_doc.doctype == 'Contact':
- customer_name = ''
+ if contact:
query_condition = ''
- for link in reference_doc.links:
- link = frappe._dict(link)
+ contact = frappe.get_doc('Contact', contact)
+ for link in contact.links:
if link.link_doctype == 'Customer':
- customer_name = link.link_name
+ last_issue = get_last_issue_from_customer(link.link_name)
query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name)
if query_condition:
+ # remove extra appended 'OR'
query_condition = query_condition[:-2]
last_communication = frappe.db.sql("""
SELECT `name`, `content`
FROM `tabCommunication`
- WHERE {}
+ WHERE
+ `sent_or_received`='Received' AND
+ ({})
ORDER BY `modified`
LIMIT 1
- """.format(query_condition)) # nosec
+ """.format(query_condition), as_dict=1) # nosec
- if customer_name:
- last_issue = frappe.get_all('Issue', {
- 'customer': customer_name
- }, ['name', 'subject', 'customer'], limit=1)
-
- elif reference_doc.doctype == 'Lead':
+ if lead:
last_communication = frappe.get_all('Communication', filters={
- 'reference_doctype': reference_doc.doctype,
- 'reference_name': reference_doc.name,
+ 'reference_doctype': 'Contact',
+ 'reference_name': contact,
'sent_or_received': 'Received'
}, fields=['name', 'content'], limit=1)
+ last_communication = last_communication[0] if last_communication else None
+
return {
- 'last_communication': last_communication[0] if last_communication else None,
- 'last_issue': last_issue[0] if last_issue else None
+ 'last_communication': last_communication,
+ 'last_issue': last_issue
}
-@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_last_issue_from_customer(customer_name):
+ issues = frappe.get_all('Issue', {
+ 'customer': customer_name
+ }, ['name', 'subject', 'customer'], limit=1)
+
+ return issues[0] if issues else None
+
def get_employee_emails_for_popup(communication_medium):
now_time = frappe.utils.nowtime()
diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py
index c04cedc..09c399e 100644
--- a/erpnext/erpnext_integrations/exotel_integration.py
+++ b/erpnext/erpnext_integrations/exotel_integration.py
@@ -18,6 +18,8 @@
call_log = get_call_log(call_payload)
if not call_log:
create_call_log(call_payload)
+ else:
+ update_call_log(call_payload, call_log=call_log)
@frappe.whitelist(allow_guest=True)
def handle_end_call(**kwargs):
@@ -27,10 +29,11 @@
def handle_missed_call(**kwargs):
update_call_log(kwargs, 'Missed')
-def update_call_log(call_payload, status):
- call_log = get_call_log(call_payload)
+def update_call_log(call_payload, status='Ringing', call_log=None):
+ call_log = call_log or get_call_log(call_payload)
if call_log:
call_log.status = status
+ call_log.to = call_payload.get('DialWhomNumber')
call_log.duration = call_payload.get('DialCallDuration') or 0
call_log.recording_url = call_payload.get('RecordingUrl')
call_log.save(ignore_permissions=True)
@@ -48,7 +51,7 @@
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.to = call_payload.get('DialWhomNumber')
call_log.medium = call_payload.get('To')
call_log.status = 'Ringing'
setattr(call_log, 'from', call_payload.get('CallFrom'))
diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js
index 89657a1..847b501 100644
--- a/erpnext/public/js/call_popup/call_popup.js
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -11,12 +11,44 @@
'static': true,
'minimizable': true,
'fields': [{
- 'fieldname': 'caller_info',
- 'fieldtype': 'HTML'
+ 'fieldname': 'name',
+ 'label': 'Name',
+ 'default': this.get_caller_name() || __('Unknown Caller'),
+ 'fieldtype': 'Data',
+ 'read_only': 1
+ }, {
+ 'fieldtype': 'Button',
+ 'label': __('Open Contact'),
+ 'click': () => frappe.set_route('Form', 'Contact', this.call_log.contact),
+ 'depends_on': () => this.call_log.contact
+ }, {
+ 'fieldtype': 'Button',
+ 'label': __('Open Lead'),
+ 'click': () => frappe.set_route('Form', 'Lead', this.call_log.lead),
+ 'depends_on': () => this.call_log.lead
+ }, {
+ 'fieldtype': 'Button',
+ 'label': __('Make New Contact'),
+ 'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }),
+ 'depends_on': () => !this.get_caller_name()
+ }, {
+ 'fieldtype': 'Button',
+ 'label': __('Make New Lead'),
+ 'click': () => frappe.new_doc('Lead', { 'mobile_no': this.caller_number }),
+ 'depends_on': () => !this.get_caller_name()
+ }, {
+ 'fieldtype': 'Column Break',
+ }, {
+ 'fieldname': 'number',
+ 'label': 'Phone Number',
+ 'fieldtype': 'Data',
+ 'default': this.caller_number,
+ 'read_only': 1
}, {
'fielname': 'last_interaction',
'fieldtype': 'Section Break',
'label': __('Activity'),
+ 'depends_on': () => this.get_caller_name()
}, {
'fieldtype': 'Small Text',
'label': __('Last Communication'),
@@ -30,7 +62,7 @@
'read_only': true,
'default': `<i class="text-muted">${__('No issue raised by the customer.')}<i>`
}, {
- 'fieldtype': 'Column Break',
+ 'fieldtype': 'Section Break',
}, {
'fieldtype': 'Small Text',
'label': __('Call Summary'),
@@ -41,13 +73,19 @@
'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,
+ frappe.xcall('erpnext.communication.doctype.call_log.call_log.add_call_summary', {
+ 'call_log': this.call_log.name,
'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>`,
+ 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'
});
});
@@ -55,71 +93,14 @@
}],
});
this.set_call_status();
- this.make_caller_info_section();
this.dialog.get_close_btn().show();
+ this.make_last_interaction_section();
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);
@@ -129,7 +110,7 @@
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()]);
+ title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]);
this.set_indicator('blue', true);
} else if (call_status === 'In Progress') {
title = __('Call Connected');
@@ -164,13 +145,13 @@
if (!this.dialog.get_value('call_summary')) {
this.close_modal();
}
- }, 10000);
+ }, 30000);
}
make_last_interaction_section() {
frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', {
- 'number': this.caller_number,
- 'reference_doc': this.contact
+ 'contact': this.call_log.contact,
+ 'lead': this.call_log.lead
}).then(data => {
const comm_field = this.dialog.get_field('last_communication');
if (data.last_communication) {
@@ -188,9 +169,12 @@
}
});
}
+
get_caller_name() {
- return this.contact ? this.contact.lead_name || this.contact.name || '' : this.caller_number;
+ let log = this.call_log;
+ return log.contact_name || log.lead_name;
}
+
setup_listener() {
frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => {
this.call_disconnected(call_log);