refactor: Remove exotel

Move it to separate app
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 e69de29..0000000
--- a/erpnext/erpnext_integrations/doctype/exotel_settings/__init__.py
+++ /dev/null
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 72f47b5..0000000
--- 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 4879cb5..0000000
--- 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 522de9e..0000000
--- 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 3ad2575..0000000
--- 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 76bbb3e..0000000
--- 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