Merge branch 'develop' of github.com:frappe/erpnext into call-summary-dialog
diff --git a/erpnext/communication/__init__.py b/erpnext/communication/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/communication/__init__.py
diff --git a/erpnext/communication/doctype/__init__.py b/erpnext/communication/doctype/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/communication/doctype/__init__.py
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/communication/doctype/call_log/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/communication/doctype/call_log/__init__.py
diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json
new file mode 100644
index 0000000..c3d6d07
--- /dev/null
+++ b/erpnext/communication/doctype/call_log/call_log.json
@@ -0,0 +1,105 @@
+{
+ "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
+  }
+ ],
+ "modified": "2019-06-17 09:02:48.150383",
+ "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
+}
\ 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
new file mode 100644
index 0000000..66f1064
--- /dev/null
+++ b/erpnext/communication/doctype/call_log/call_log.py
@@ -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)
diff --git a/erpnext/communication/doctype/communication_medium/__init__.py b/erpnext/communication/doctype/communication_medium/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium/__init__.py
diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.js b/erpnext/communication/doctype/communication_medium/communication_medium.js
new file mode 100644
index 0000000..e37cd5b
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium/communication_medium.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Communication Medium', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.json b/erpnext/communication/doctype/communication_medium/communication_medium.json
new file mode 100644
index 0000000..f009b38
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium/communication_medium.json
@@ -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
+}
\ No newline at end of file
diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.py b/erpnext/communication/doctype/communication_medium/communication_medium.py
new file mode 100644
index 0000000..f233da0
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium/communication_medium.py
@@ -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
diff --git a/erpnext/communication/doctype/communication_medium/test_communication_medium.py b/erpnext/communication/doctype/communication_medium/test_communication_medium.py
new file mode 100644
index 0000000..fc5754f
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium/test_communication_medium.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestCommunicationMedium(unittest.TestCase):
+	pass
diff --git a/erpnext/communication/doctype/communication_medium_timeslot/__init__.py b/erpnext/communication/doctype/communication_medium_timeslot/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium_timeslot/__init__.py
diff --git a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json
new file mode 100644
index 0000000..b278ca0
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json
@@ -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
+}
\ No newline at end of file
diff --git a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.py b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.py
new file mode 100644
index 0000000..d68d2d6
--- /dev/null
+++ b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.py
@@ -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
diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py
new file mode 100644
index 0000000..5f7a72e
--- /dev/null
+++ b/erpnext/crm/doctype/utils.py
@@ -0,0 +1,98 @@
+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.db.sql_list("""SELECT `employee_group`
+		FROM `tabCommunication Medium Timeslot`
+		WHERE `day_of_week` = %s
+		AND `parent` = %s
+		AND %s BETWEEN `from_time` AND `to_time`
+	""", (weekday, communication_medium, now_time))
+
+	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
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py b/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
new file mode 100644
index 0000000..72f47b5
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
@@ -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
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py
new file mode 100644
index 0000000..77de84c
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py
@@ -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"))
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py
new file mode 100644
index 0000000..a427186
--- /dev/null
+++ b/erpnext/erpnext_integrations/exotel_integration.py
@@ -0,0 +1,99 @@
+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(*args, **kwargs):
+	exotel_settings = get_exotel_settings()
+	if not exotel_settings.enabled: return
+
+	status = kwargs.get('Status')
+	if status == 'free':
+		return
+
+	create_call_log(kwargs)
+
+@frappe.whitelist(allow_guest=True)
+def handle_end_call(*args, **kwargs):
+	update_call_log(kwargs, 'Completed')
+
+@frappe.whitelist(allow_guest=True)
+def handle_missed_call(*args, **kwargs):
+	update_call_log(kwargs, 'Missed')
+
+def update_call_log(call_payload, status):
+	call_log = get_call_log(call_payload, False)
+	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, create_new_if_not_found=True):
+	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)
+	elif create_new_if_not_found:
+		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
+
+create_call_log = get_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
+	)
\ No newline at end of file
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 6ce75bb..756f6ba 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -169,6 +169,11 @@
 	{'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",
diff --git a/erpnext/hr/doctype/employee_group_table/employee_group_table.json b/erpnext/hr/doctype/employee_group_table/employee_group_table.json
index f2e7770..4e0045c 100644
--- a/erpnext/hr/doctype/employee_group_table/employee_group_table.json
+++ b/erpnext/hr/doctype/employee_group_table/employee_group_table.json
@@ -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
 }
\ No newline at end of file
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 9ef8937..316d6de 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -22,4 +22,5 @@
 Non Profit
 Hotels
 Hub Node
-Quality Management
\ No newline at end of file
+Quality Management
+Communication
\ No newline at end of file
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index be7189b..f6137d5 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -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": [
diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js
new file mode 100644
index 0000000..17bd741
--- /dev/null
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -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.fields_dict['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.fields_dict["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.fields_dict["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();
+		}
+	});
+});
diff --git a/erpnext/public/less/call_popup.less b/erpnext/public/less/call_popup.less
new file mode 100644
index 0000000..32e85ce
--- /dev/null
+++ b/erpnext/public/less/call_popup.less
@@ -0,0 +1,9 @@
+.call-popup {
+	a:hover {
+		text-decoration: underline;
+	}
+	.for-description {
+		max-height: 250px;
+		overflow: scroll;
+	}
+}
\ No newline at end of file
diff --git a/erpnext/public/sounds/call-disconnect.mp3 b/erpnext/public/sounds/call-disconnect.mp3
new file mode 100644
index 0000000..1202273
--- /dev/null
+++ b/erpnext/public/sounds/call-disconnect.mp3
Binary files differ
diff --git a/erpnext/public/sounds/incoming-call.mp3 b/erpnext/public/sounds/incoming-call.mp3
new file mode 100644
index 0000000..60431e3
--- /dev/null
+++ b/erpnext/public/sounds/incoming-call.mp3
Binary files differ