feat: Employee Exits report
diff --git a/erpnext/hr/report/employee_exits/__init__.py b/erpnext/hr/report/employee_exits/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/__init__.py
diff --git a/erpnext/hr/report/employee_exits/employee_exits.js b/erpnext/hr/report/employee_exits/employee_exits.js
new file mode 100644
index 0000000..ac677d8
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.js
@@ -0,0 +1,77 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Employee Exits"] = {
+	filters: [
+		{
+			"fieldname": "from_date",
+			"label": __("From Date"),
+			"fieldtype": "Date",
+			"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12)
+		},
+		{
+			"fieldname": "to_date",
+			"label": __("To Date"),
+			"fieldtype": "Date",
+			"default": frappe.datetime.nowdate()
+		},
+		{
+			"fieldname": "company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company"
+		},
+		{
+			"fieldname": "department",
+			"label": __("Department"),
+			"fieldtype": "Link",
+			"options": "Department"
+		},
+		{
+			"fieldname": "designation",
+			"label": __("Designation"),
+			"fieldtype": "Link",
+			"options": "Designation"
+		},
+		{
+			"fieldname": "employee",
+			"label": __("Employee"),
+			"fieldtype": "Link",
+			"options": "Employee"
+		},
+		{
+			"fieldname": "reports_to",
+			"label": __("Reports To"),
+			"fieldtype": "Link",
+			"options": "Employee"
+		},
+		{
+			"fieldname": "interview_status",
+			"label": __("Interview Status"),
+			"fieldtype": "Select",
+			"options": ["", "Pending", "Scheduled", "Completed"]
+		},
+		{
+			"fieldname": "final_decision",
+			"label": __("Final Decision"),
+			"fieldtype": "Select",
+			"options": ["", "Employee Retained", "Exit Confirmed"]
+		},
+		{
+			"fieldname": "exit_interview_pending",
+			"label": __("Exit Interview Pending"),
+			"fieldtype": "Check"
+		},
+		{
+			"fieldname": "questionnaire_pending",
+			"label": __("Exit Questionnaire Pending"),
+			"fieldtype": "Check"
+		},
+		{
+			"fieldname": "fnf_pending",
+			"label": __("FnF Pending"),
+			"fieldtype": "Check"
+		}
+	]
+};
diff --git a/erpnext/hr/report/employee_exits/employee_exits.json b/erpnext/hr/report/employee_exits/employee_exits.json
new file mode 100644
index 0000000..4fe9a85
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-12-05 19:47:18.332319",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Test",
+ "modified": "2021-12-05 19:47:18.332319",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Exits",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Exit Interview",
+ "report_name": "Employee Exits",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "HR Manager"
+  },
+  {
+   "role": "HR User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py
new file mode 100644
index 0000000..fd49543
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.py
@@ -0,0 +1,231 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# License: MIT. See LICENSE
+
+import frappe
+from frappe import _
+from frappe.utils import getdate
+
+
+def execute(filters=None):
+	columns = get_columns()
+	data = get_data(filters)
+	chart = get_chart_data(data)
+	report_summary = get_report_summary(data)
+
+	return columns, data, None, chart, report_summary
+
+def get_columns():
+	return [
+		{
+			'label': _('Employee'),
+			'fieldname': 'employee',
+			'fieldtype': 'Link',
+			'options': 'Employee',
+			'width': 150
+		},
+		{
+			'label': _('Employee Name'),
+			'fieldname': 'employee_name',
+			'fieldtype': 'Data',
+			'width': 150
+		},
+		{
+			'label': _('Date of Joining'),
+			'fieldname': 'date_of_joining',
+			'fieldtype': 'Date',
+			'width': 100
+		},
+		{
+			'label': _('Relieving Date'),
+			'fieldname': 'relieving_date',
+			'fieldtype': 'Date',
+			'width': 100
+		},
+		{
+			'label': _('Department'),
+			'fieldname': 'department',
+			'fieldtype': 'Link',
+			'options': 'Department',
+			'width': 120
+		},
+		{
+			'label': _('Designation'),
+			'fieldname': 'designation',
+			'fieldtype': 'Link',
+			'options': 'Designation',
+			'width': 120
+		},
+		{
+			'label': _('Reports To'),
+			'fieldname': 'reports_to',
+			'fieldtype': 'Link',
+			'options': 'Employee',
+			'width': 120
+		},
+		{
+			'label': _('Exit Interview'),
+			'fieldname': 'exit_interview',
+			'fieldtype': 'Link',
+			'options': 'Exit Interview',
+			'width': 98
+		},
+		{
+			'label': _('Interview Status'),
+			'fieldname': 'interview_status',
+			'fieldtype': 'Data',
+			'width': 98
+		},
+		{
+			'label': _('Final Decision'),
+			'fieldname': 'employee_status',
+			'fieldtype': 'Data',
+			'width': 98
+		},
+		{
+			'label': _('Full and Final Statement'),
+			'fieldname': 'full_and_final_statement',
+			'fieldtype': 'Link',
+			'options': 'Full and Final Statement',
+			'width': 150
+		}
+	]
+
+def get_data(filters):
+	data = []
+
+	employee = frappe.qb.DocType('Employee')
+	interview = frappe.qb.DocType('Exit Interview')
+	fnf = frappe.qb.DocType('Full and Final Statement')
+
+	query = (
+		frappe.qb.from_(employee)
+			.left_join(interview).on(interview.employee == employee.name)
+			.left_join(fnf).on(fnf.employee == employee.name)
+			.select(
+				employee.name.as_('employee'), employee.employee_name.as_('employee_name'),
+				employee.date_of_joining.as_('date_of_joining'), employee.relieving_date.as_('relieving_date'),
+				employee.department.as_('department'), employee.designation.as_('designation'),
+				employee.reports_to.as_('reports_to'), interview.name.as_('exit_interview'),
+				interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
+				interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement')
+			).distinct()
+			.where(
+				((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
+				& ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
+				& ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
+			)
+	)
+
+	query = get_conditions(filters, query, employee, interview, fnf)
+	result = query.run(as_dict=True)
+
+	return result
+
+
+def get_conditions(filters, query, employee, interview, fnf):
+	if filters.get('from_date') and filters.get('to_date'):
+		query = query.where(employee.relieving_date[getdate(filters.get('from_date')):getdate(filters.get('to_date'))])
+
+	elif filters.get('from_date'):
+		query = query.where(employee.relieving_date >= filters.get('from_date'))
+
+	elif filters.get('to_date'):
+		query = query.where(employee.relieving_date <= filters.get('to_date'))
+
+	if filters.get('company'):
+		query = query.where(employee.company == filters.get('company'))
+
+	if filters.get('department'):
+		query = query.where(employee.department == filters.get('department'))
+
+	if filters.get('designation'):
+		query = query.where(employee.designation == filters.get('designation'))
+
+	if filters.get('employee'):
+		query = query.where(employee.name == filters.get('employee'))
+
+	if filters.get('reports_to'):
+		query = query.where(employee.reports_to == filters.get('reports_to'))
+
+	if filters.get('interview_status'):
+		query = query.where(interview.status == filters.get('interview_status'))
+
+	if filters.get('final_decision'):
+		query = query.where(interview.employee_status == filters.get('final_decision'))
+
+	if filters.get('exit_interview_pending'):
+		query = query.where((interview.name == '') | (interview.name.isnull()))
+
+	if filters.get('questionnaire_pending'):
+		query = query.where((interview.reference_document_name == '') | (interview.reference_document_name.isnull()))
+
+	if filters.get('fnf_pending'):
+		query = query.where((fnf.name == '') | (interview.name.isnull()))
+
+	return query
+
+
+def get_chart_data(data):
+	if not data:
+		return None
+
+	retained = 0
+	exit_confirmed = 0
+	pending = 0
+
+	for entry in data:
+		if entry.employee_status == 'Employee Retained':
+			retained += 1
+		elif entry.employee_status == 'Exit Confirmed':
+			exit_confirmed += 1
+		else:
+			pending += 1
+
+	chart = {
+		'data': {
+			'labels': [_('Retained'), _('Exit Confirmed'), _('Decision Pending')],
+			'datasets': [{'name': _('Employee Status'), 'values': [retained, exit_confirmed, pending]}]
+		},
+		'type': 'donut',
+		'colors': ['green', 'red', 'blue'],
+	}
+
+	return chart
+
+
+def get_report_summary(data):
+	if not data:
+		return None
+
+	total_resignations = len(data)
+	interviews_pending = len([entry.name for entry in data if not entry.exit_interview])
+	fnf_pending = len([entry.name for entry in data if not entry.full_and_final_statement])
+	questionnaires_pending = len([entry.name for entry in data if not entry.questionnaire])
+
+	return [
+		{
+			'value': total_resignations,
+			'label': _('Total Resignations'),
+			'indicator': 'Red' if total_resignations > 0 else 'Green',
+			'datatype': 'Int',
+		},
+		{
+			'value': interviews_pending,
+			'label': _('Pending Interviews'),
+			'indicator': 'Blue' if interviews_pending > 0 else 'Green',
+			'datatype': 'Int',
+		},
+		{
+			'value': fnf_pending,
+			'label': _('Pending FnF'),
+			'indicator': 'Blue' if fnf_pending > 0 else 'Green',
+			'datatype': 'Int',
+		},
+		{
+			'value': questionnaires_pending,
+			'label': _('Pending Questionnaires'),
+			'indicator': 'Blue' if questionnaires_pending > 0 else 'Green',
+			'datatype': 'Int'
+		},
+	]
+