Merge branch 'develop' of github.com:frappe/erpnext into call-summary-dialog
diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py
new file mode 100644
index 0000000..3bd9246
--- /dev/null
+++ b/erpnext/crm/doctype/utils.py
@@ -0,0 +1,20 @@
+import frappe
+
+def get_document_with_phone_number(number):
+ # finds contacts and leads
+ number = number[-10:]
+ number_filter = {
+ 'phone': ['like', '%{}'.format(number)],
+ 'mobile_no': ['like', '%{}'.format(number)]
+ }
+ contacts = frappe.get_all('Contact', or_filters=number_filter,
+ fields=['name'], limit=1)
+
+ if contacts:
+ return frappe.get_doc('Contact', contacts[0].name)
+
+ leads = frappe.get_all('Leads', or_filters=number_filter,
+ fields=['name'], limit=1)
+
+ if leads:
+ return frappe.get_doc('Lead', leads[0].name)
\ No newline at end of file
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.js b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.js
new file mode 100644
index 0000000..bfed491
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.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('Exotel Settings', {
+ // refresh: function(frm) {
+
+ // }
+});
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/doctype/exotel_settings/test_exotel_settings.py b/erpnext/erpnext_integrations/doctype/exotel_settings/test_exotel_settings.py
new file mode 100644
index 0000000..5d85615
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/test_exotel_settings.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 TestExotelSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py
new file mode 100644
index 0000000..3a922f7
--- /dev/null
+++ b/erpnext/erpnext_integrations/exotel_integration.py
@@ -0,0 +1,80 @@
+import frappe
+from erpnext.crm.doctype.utils import get_document_with_phone_number
+import requests
+
+# api/method/erpnext.erpnext_integrations.exotel_integration.handle_incoming_call
+
+@frappe.whitelist(allow_guest=True)
+def handle_incoming_call(*args, **kwargs):
+ # incoming_phone_number = kwargs.get('CallFrom')
+
+ # contact = get_document_with_phone_number(incoming_phone_number)
+ # last_communication = get_last_communication(incoming_phone_number, contact)
+ call_log = create_call_log(kwargs)
+ data = frappe._dict({
+ 'call_from': kwargs.get('CallFrom'),
+ 'agent_email': kwargs.get('AgentEmail'),
+ 'call_type': kwargs.get('Direction'),
+ 'call_log': call_log
+ })
+
+ frappe.publish_realtime('show_call_popup', data, user=data.agent_email)
+
+
+def get_last_communication(phone_number, contact):
+ # frappe.get_all('Communication', filter={})
+ return {}
+
+def create_call_log(call_payload):
+ communication = frappe.get_all('Communication', {
+ 'communication_medium': 'Phone',
+ 'call_id': call_payload.get('CallSid'),
+ }, limit=1)
+
+ if communication:
+ log = frappe.get_doc('Communication', communication[0].name)
+ log.call_status = 'Connected'
+ log.save(ignore_permissions=True)
+ return log
+
+ communication = frappe.new_doc('Communication')
+ communication.subject = frappe._('Call from {}').format(call_payload.get("CallFrom"))
+ communication.communication_medium = 'Phone'
+ communication.send_email = 0
+ communication.phone_no = call_payload.get("CallFrom")
+ communication.comment_type = 'Info'
+ communication.communication_type = 'Communication'
+ communication.status = 'Open'
+ communication.sent_or_received = 'Received'
+ communication.content = 'call_payload'
+ communication.call_status = 'Incoming'
+ communication.communication_date = call_payload.get('StartTime')
+ communication.call_id = call_payload.get('CallSid')
+ communication.save(ignore_permissions=True)
+ return communication
+
+def get_call_status(call_id):
+ settings = get_exotel_settings()
+ response = requests.get('https://{api_key}:{api_token}@api.exotel.com/v1/Accounts/erpnext/{sid}/{call_id}.json'.format(
+ api_key=settings.api_key,
+ api_token=settings.api_token,
+ call_id=call_id
+ ))
+ return response.json()
+
+@frappe.whitelist(allow_guest=True)
+def make_a_call(from_number, to_number, caller_id):
+ settings = get_exotel_settings()
+ response = requests.post('https://{api_key}:{api_token}@api.exotel.com/v1/Accounts/{sid}/Calls/connect.json'.format(
+ api_key=settings.api_key,
+ api_token=settings.api_token,
+ ), data={
+ 'From': from_number,
+ 'To': to_number,
+ 'CallerId': caller_id
+ })
+
+ return response.json()
+
+def get_exotel_settings():
+ return frappe.get_single('Exotel Settings')
\ No newline at end of file
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 45de6eb..818f336 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"
@@ -48,7 +49,8 @@
"public/js/utils/customer_quick_entry.js",
"public/js/education/student_button.html",
"public/js/education/assessment_result_tool.html",
- "public/js/hub/hub_factory.js"
+ "public/js/hub/hub_factory.js",
+ "public/js/call_popup/call_popup.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
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..2d95c5d
--- /dev/null
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -0,0 +1,97 @@
+class CallPopup {
+ constructor({ call_from, call_log }) {
+ this.number = call_from;
+ this.call_log = call_log;
+ this.make();
+ }
+
+ make() {
+ this.dialog = new frappe.ui.Dialog({
+ 'static': true,
+ 'minimizable': true,
+ 'fields': [{
+ 'fieldname': 'customer_info',
+ 'fieldtype': 'HTML'
+ }, {
+ 'fieldtype': 'Section Break'
+ }, {
+ 'fieldtype': 'Small Text',
+ 'label': "Last Communication",
+ 'fieldname': 'last_communication',
+ 'read_only': true
+ }, {
+ 'fieldtype': 'Column Break'
+ }, {
+ 'fieldtype': 'Small Text',
+ 'label': 'Call Summary',
+ 'fieldname': 'call_communication',
+ }, {
+ 'fieldtype': 'Button',
+ 'label': 'Submit',
+ 'click': () => {
+ this.dialog.get_value();
+ }
+ }]
+ });
+ this.set_call_status(this.call_log.call_status);
+ this.make_customer_contact();
+ this.dialog.show();
+ this.dialog.get_close_btn().show();
+ this.dialog.header.find('.indicator').removeClass('hidden').addClass('blue');
+ }
+
+ make_customer_contact() {
+ const wrapper = this.dialog.fields_dict["customer_info"].$wrapper;
+ const contact = this.contact;
+ const customer = this.contact.links ? this.contact.links[0] : null;
+ const customer_link = customer ? frappe.utils.get_form_link(customer.link_doctype, customer.link_name, true): '';
+ if (!contact) {
+ wrapper.append('<b>Unknown Contact</b>');
+ } else {
+ wrapper.append(`
+ <div class="customer-info flex">
+ <img src="${contact.image}">
+ <div class='flex-column'>
+ <span>${contact.first_name} ${contact.last_name}</span>
+ <span>${contact.mobile_no}</span>
+ ${customer_link}
+ </div>
+ </div>
+ `);
+ }
+ }
+
+ make_summary_section() {
+ //
+ }
+
+ set_call_status() {
+ let title = '';
+ if (this.call_log.call_status === 'Incoming') {
+ if (this.contact) {
+ title = __('Incoming call from {0}', [this.contact.name]);
+ } else {
+ title = __('Incoming call from unknown number');
+ }
+ } else {
+ title = __('Call Connected');
+ }
+ this.dialog.set_title(title);
+ }
+
+ update(data) {
+ this.call_log = data.call_log;
+ this.set_call_status();
+ }
+}
+
+$(document).on('app_ready', function () {
+ frappe.realtime.on('show_call_popup', data => {
+ if (!erpnext.call_popup) {
+ erpnext.call_popup = new CallPopup(data);
+ } else {
+ erpnext.call_popup.update(data);
+ 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..0ec2066
--- /dev/null
+++ b/erpnext/public/less/call_popup.less
@@ -0,0 +1,7 @@
+.customer-info {
+ img {
+ width: auto;
+ height: 100px;
+ margin-right: 15px;
+ }
+}
\ No newline at end of file