From 53e4fee4db69b5a1d7ee90bd37d807737fc53607 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 8 May 2022 16:04:14 +0530 Subject: [PATCH] refactor: Remove exotel Move it to separate app --- .../doctype/exotel_settings/__init__.py | 0 .../exotel_settings/exotel_settings.json | 61 -------- .../exotel_settings/exotel_settings.py | 22 --- .../exotel_integration.py | 133 ------------------ erpnext/tests/exotel_test_data.py | 122 ---------------- erpnext/tests/test_exotel.py | 69 --------- 6 files changed, 407 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json delete mode 100644 erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py delete mode 100644 erpnext/erpnext_integrations/exotel_integration.py delete mode 100644 erpnext/tests/exotel_test_data.py delete mode 100644 erpnext/tests/test_exotel.py diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py b/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json deleted file mode 100644 index 72f47b53ec..0000000000 --- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "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 deleted file mode 100644 index 4879cb5623..0000000000 --- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -import requests -from frappe import _ -from frappe.model.document import Document - - -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")) diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py deleted file mode 100644 index 522de9ead8..0000000000 --- a/erpnext/erpnext_integrations/exotel_integration.py +++ /dev/null @@ -1,133 +0,0 @@ -import frappe -import requests -from frappe import _ - -# 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): - try: - 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) - else: - update_call_log(call_payload, call_log=call_log) - except Exception as e: - frappe.db.rollback() - frappe.log_error(title=_("Error in Exotel incoming call")) - frappe.db.commit() - - -@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): - status = "" - call_type = kwargs.get("CallType") - dial_call_status = kwargs.get("DialCallStatus") - - if call_type == "incomplete" and dial_call_status == "no-answer": - status = "No Answer" - elif call_type == "client-hangup" and dial_call_status == "canceled": - status = "Canceled" - elif call_type == "incomplete" and dial_call_status == "failed": - status = "Failed" - - update_call_log(kwargs, status) - - -def update_call_log(call_payload, status="Ringing", call_log=None): - call_log = call_log or get_call_log(call_payload) - - # for a new sid, call_log and get_call_log will be empty so create a new log - if not call_log: - call_log = create_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) - frappe.db.commit() - return call_log - - -def get_call_log(call_payload): - call_log_id = call_payload.get("CallSid") - if frappe.db.exists("Call Log", call_log_id): - return frappe.get_doc("Call Log", call_log_id) - - -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("DialWhomNumber") - 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 - ) diff --git a/erpnext/tests/exotel_test_data.py b/erpnext/tests/exotel_test_data.py deleted file mode 100644 index 3ad2575c23..0000000000 --- a/erpnext/tests/exotel_test_data.py +++ /dev/null @@ -1,122 +0,0 @@ -import frappe - -call_initiation_data = frappe._dict( - { - "CallSid": "23c162077629863c1a2d7f29263a162m", - "CallFrom": "09999999991", - "CallTo": "09999999980", - "Direction": "incoming", - "Created": "Wed, 23 Feb 2022 12:31:59", - "From": "09999999991", - "To": "09999999988", - "CurrentTime": "2022-02-23 12:32:02", - "DialWhomNumber": "09999999999", - "Status": "busy", - "EventType": "Dial", - "AgentEmail": "test_employee_exotel@company.com", - } -) - -call_end_data = frappe._dict( - { - "CallSid": "23c162077629863c1a2d7f29263a162m", - "CallFrom": "09999999991", - "CallTo": "09999999980", - "Direction": "incoming", - "ForwardedFrom": "null", - "Created": "Wed, 23 Feb 2022 12:31:59", - "DialCallDuration": "17", - "RecordingUrl": "https://s3-ap-southeast-1.amazonaws.com/random.mp3", - "StartTime": "2022-02-23 12:31:58", - "EndTime": "1970-01-01 05:30:00", - "DialCallStatus": "completed", - "CallType": "completed", - "DialWhomNumber": "09999999999", - "ProcessStatus": "null", - "flow_id": "228040", - "tenant_id": "67291", - "From": "09999999991", - "To": "09999999988", - "RecordingAvailableBy": "Wed, 23 Feb 2022 12:37:25", - "CurrentTime": "2022-02-23 12:32:25", - "OutgoingPhoneNumber": "09999999988", - "Legs": [ - { - "Number": "09999999999", - "Type": "single", - "OnCallDuration": "10", - "CallerId": "09999999980", - "CauseCode": "NORMAL_CLEARING", - "Cause": "16", - } - ], - } -) - -call_disconnected_data = frappe._dict( - { - "CallSid": "d96421addce69e24bdc7ce5880d1162l", - "CallFrom": "09999999991", - "CallTo": "09999999980", - "Direction": "incoming", - "ForwardedFrom": "null", - "Created": "Mon, 21 Feb 2022 15:58:12", - "DialCallDuration": "0", - "StartTime": "2022-02-21 15:58:12", - "EndTime": "1970-01-01 05:30:00", - "DialCallStatus": "canceled", - "CallType": "client-hangup", - "DialWhomNumber": "09999999999", - "ProcessStatus": "null", - "flow_id": "228040", - "tenant_id": "67291", - "From": "09999999991", - "To": "09999999988", - "CurrentTime": "2022-02-21 15:58:47", - "OutgoingPhoneNumber": "09999999988", - "Legs": [ - { - "Number": "09999999999", - "Type": "single", - "OnCallDuration": "0", - "CallerId": "09999999980", - "CauseCode": "RING_TIMEOUT", - "Cause": "1003", - } - ], - } -) - -call_not_answered_data = frappe._dict( - { - "CallSid": "fdb67a2b4b2d057b610a52ef43f81622", - "CallFrom": "09999999991", - "CallTo": "09999999980", - "Direction": "incoming", - "ForwardedFrom": "null", - "Created": "Mon, 21 Feb 2022 15:47:02", - "DialCallDuration": "0", - "StartTime": "2022-02-21 15:47:02", - "EndTime": "1970-01-01 05:30:00", - "DialCallStatus": "no-answer", - "CallType": "incomplete", - "DialWhomNumber": "09999999999", - "ProcessStatus": "null", - "flow_id": "228040", - "tenant_id": "67291", - "From": "09999999991", - "To": "09999999988", - "CurrentTime": "2022-02-21 15:47:40", - "OutgoingPhoneNumber": "09999999988", - "Legs": [ - { - "Number": "09999999999", - "Type": "single", - "OnCallDuration": "0", - "CallerId": "09999999980", - "CauseCode": "RING_TIMEOUT", - "Cause": "1003", - } - ], - } -) diff --git a/erpnext/tests/test_exotel.py b/erpnext/tests/test_exotel.py deleted file mode 100644 index 76bbb3e05a..0000000000 --- a/erpnext/tests/test_exotel.py +++ /dev/null @@ -1,69 +0,0 @@ -import frappe -from frappe.contacts.doctype.contact.test_contact import create_contact -from frappe.tests.test_api import FrappeAPITestCase - -from erpnext.hr.doctype.employee.test_employee import make_employee - - -class TestExotel(FrappeAPITestCase): - @classmethod - def setUpClass(cls): - cls.CURRENT_DB_CONNECTION = frappe.db - cls.test_employee_name = make_employee( - user="test_employee_exotel@company.com", cell_number="9999999999" - ) - frappe.db.set_value("Exotel Settings", "Exotel Settings", "enabled", 1) - phones = [{"phone": "+91 9999999991", "is_primary_phone": 0, "is_primary_mobile_no": 1}] - create_contact(name="Test Contact", salutation="Mr", phones=phones) - frappe.db.commit() - - def test_for_successful_call(self): - from .exotel_test_data import call_end_data, call_initiation_data - - api_method = "handle_incoming_call" - end_call_api_method = "handle_end_call" - - self.emulate_api_call_from_exotel(api_method, call_initiation_data) - self.emulate_api_call_from_exotel(end_call_api_method, call_end_data) - call_log = frappe.get_doc("Call Log", call_initiation_data.CallSid) - - self.assertEqual(call_log.get("from"), call_initiation_data.CallFrom) - self.assertEqual(call_log.get("to"), call_initiation_data.DialWhomNumber) - self.assertEqual(call_log.get("call_received_by"), self.test_employee_name) - self.assertEqual(call_log.get("status"), "Completed") - - def test_for_disconnected_call(self): - from .exotel_test_data import call_disconnected_data - - api_method = "handle_missed_call" - self.emulate_api_call_from_exotel(api_method, call_disconnected_data) - call_log = frappe.get_doc("Call Log", call_disconnected_data.CallSid) - self.assertEqual(call_log.get("from"), call_disconnected_data.CallFrom) - self.assertEqual(call_log.get("to"), call_disconnected_data.DialWhomNumber) - self.assertEqual(call_log.get("call_received_by"), self.test_employee_name) - self.assertEqual(call_log.get("status"), "Canceled") - - def test_for_call_not_answered(self): - from .exotel_test_data import call_not_answered_data - - api_method = "handle_missed_call" - self.emulate_api_call_from_exotel(api_method, call_not_answered_data) - call_log = frappe.get_doc("Call Log", call_not_answered_data.CallSid) - self.assertEqual(call_log.get("from"), call_not_answered_data.CallFrom) - self.assertEqual(call_log.get("to"), call_not_answered_data.DialWhomNumber) - self.assertEqual(call_log.get("call_received_by"), self.test_employee_name) - self.assertEqual(call_log.get("status"), "No Answer") - - def emulate_api_call_from_exotel(self, api_method, data): - self.post( - f"/api/method/erpnext.erpnext_integrations.exotel_integration.{api_method}", - data=frappe.as_json(data), - content_type="application/json", - as_tuple=True, - ) - # restart db connection to get latest data - frappe.connect() - - @classmethod - def tearDownClass(cls): - frappe.db = cls.CURRENT_DB_CONNECTION