diff --git a/offboarding/decorators.py b/offboarding/decorators.py
index 9743dd464..a1811c079 100644
--- a/offboarding/decorators.py
+++ b/offboarding/decorators.py
@@ -21,8 +21,13 @@ from offboarding.models import (
def any_manager_can_enter(function, perm, offboarding_employee_can_enter=False):
def _function(request, *args, **kwargs):
employee = request.user.employee_get
+ permissions = perm
+ has_permission = False
+ if not isinstance(permissions, (list, tuple, set)):
+ permissions = [permissions]
+ has_permission = any(request.user.has_perm(perm) for perm in permissions)
if (
- request.user.has_perm(perm)
+ has_permission
or offboarding_employee_can_enter
or (
Offboarding.objects.filter(managers=employee).exists()
@@ -32,6 +37,7 @@ def any_manager_can_enter(function, perm, offboarding_employee_can_enter=False):
):
return function(request, *args, **kwargs)
else:
+ messages.info(request, "You don't have permission.")
previous_url = request.META.get("HTTP_REFERER", "/")
script = f''
key = "HTTP_HX_REQUEST"
diff --git a/offboarding/sidebar.py b/offboarding/sidebar.py
index 7d68c83eb..20bc6c181 100644
--- a/offboarding/sidebar.py
+++ b/offboarding/sidebar.py
@@ -17,6 +17,11 @@ ACCESSIBILITY = "offboarding.sidebar.offboarding_accessibility"
SUBMENUS = [
+ {
+ "menu": _("Dashboard"),
+ "redirect": reverse("offboarding-dashboard"),
+ "accessibility": "offboarding.sidebar.dashboard_accessibility",
+ },
{
"menu": _("Exit Process"),
"redirect": reverse("offboarding-pipeline"),
@@ -33,7 +38,7 @@ def offboarding_accessibility(request, menu, user_perms, *args, **kwargs):
accessible = False
try:
accessible = (
- request.user.has_perm("offboarding.view_offboarding")
+ request.user.has_module_perms("offboarding")
or any_manager(request.user.employee_get)
or is_offboarding_employee(request.user.employee_get)
)
@@ -45,3 +50,13 @@ def resignation_letter_accessibility(request, menu, user_perms, *args, **kwargs)
return resignation_request_enabled(request)[
"enabled_resignation_request"
] and request.user.has_perm("offboarding.view_resignationletter")
+
+
+def dashboard_accessibility(request, *args):
+ """
+ Check if the user has permission to view the dashboard.
+ """
+
+ return request.user.has_module_perms("offboarding") or any_manager(
+ request.user.employee_get
+ )
diff --git a/offboarding/static/offboarding/dashboard.js b/offboarding/static/offboarding/dashboard.js
new file mode 100644
index 000000000..8806f77a7
--- /dev/null
+++ b/offboarding/static/offboarding/dashboard.js
@@ -0,0 +1,98 @@
+$(document).ready(function () {
+ var departmentChart;
+ var joinChart;
+
+ var department_chart = () => {
+ $.ajax({
+ url: "/offboarding/dashboard-department-chart",
+ type: "GET",
+ success: function (data) {
+ var ctx = $("#departmentChart");
+ if (departmentChart) {
+ departmentChart.destroy();
+ }
+ departmentChart = new Chart(ctx, {
+ type: "bar",
+ data: {
+ labels: data.labels,
+ datasets: data.datasets,
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ stacked: true,
+ },
+ y: {
+ stacked: true,
+ },
+ },
+ },
+ plugins: [
+ {
+ afterRender: (chart) => emptyChart(chart),
+ },
+ ],
+ });
+ },
+ error: function (xhr, status, error) {
+ console.error("Error fetching department chart data:", error);
+ },
+ });
+ };
+
+ var join_chart = (type) => {
+ $.ajax({
+ url: "/offboarding/dashboard-join-chart",
+ type: "GET",
+ success: function (data) {
+ var ctx = $("#joinChart");
+ if (joinChart) {
+ joinChart.destroy();
+ }
+ joinChart = new Chart(ctx, {
+ type: type,
+ data: {
+ labels: data.labels,
+ datasets: [
+ {
+ label: "Employees",
+ data: data.items,
+ },
+ ],
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ },
+ plugins: [
+ {
+ afterRender: (chart) => emptyChart(chart),
+ },
+ ],
+ });
+ },
+ error: function (xhr, status, error) {
+ console.error("Error fetching department chart data:", error);
+ },
+ });
+ };
+
+ $("#joinChartChange").click(function (e) {
+ var chartType = joinChart.config.type;
+ if (chartType === "line") {
+ chartType = "bar";
+ } else if (chartType === "bar") {
+ chartType = "doughnut";
+ } else if (chartType === "doughnut") {
+ chartType = "pie";
+ } else if (chartType === "pie") {
+ chartType = "line";
+ }
+ join_chart(chartType);
+ });
+
+ department_chart("pie");
+ join_chart("bar");
+});
diff --git a/offboarding/templates/offboarding/dashboard/asset_returned_table.html b/offboarding/templates/offboarding/dashboard/asset_returned_table.html
new file mode 100644
index 000000000..e99708cf8
--- /dev/null
+++ b/offboarding/templates/offboarding/dashboard/asset_returned_table.html
@@ -0,0 +1,87 @@
+{% load static i18n offboarding_filter %}
+
+
+
+ {% if assets %}
+
+
+
+
{% trans "Employee" %}
+
{% trans "Asset" %}
+
+ {% trans "Reminder" %}
+
+
+
+
+ {% for asset in assets %}
+
+
+
+
+

+
+
{{ asset.assigned_to_employee_id.get_full_name}}
+
+
+
+
+ {{asset.asset_id.asset_name}} -
+ {{asset.asset_id.asset_category_id}}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+

+
+ {% trans "No Assets Due for Return from Offboarding Employees." %}
+
+
+ {% endif %}
+
+
diff --git a/offboarding/templates/offboarding/dashboard/dashboard.html b/offboarding/templates/offboarding/dashboard/dashboard.html
new file mode 100644
index 000000000..92ec2a5ef
--- /dev/null
+++ b/offboarding/templates/offboarding/dashboard/dashboard.html
@@ -0,0 +1,208 @@
+{% extends 'index.html' %} {% block content %} {% load static i18n horillafilters %}
+
+
+
+
+
+
+
+
+
+
+ {{resigning_employees.count}} :
+ {{onboarding_employees}}
+
+
+
+
+
+
+
+ {% if perms.offboarding.view_offboardingtask %}
+
+ {% endif %}
+ {% if "asset"|app_installed %}
+ {% if perms.offboarding.view_offboarding %}
+
+ {% endif %}
+ {% endif %}
+ {% if perms.offboarding.view_offboarding %}
+
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% endblock content %}
diff --git a/offboarding/templates/offboarding/dashboard/employee_feedback_table.html b/offboarding/templates/offboarding/dashboard/employee_feedback_table.html
new file mode 100644
index 000000000..c4a7af1c5
--- /dev/null
+++ b/offboarding/templates/offboarding/dashboard/employee_feedback_table.html
@@ -0,0 +1,75 @@
+{% load static i18n offboarding_filter %}
+
+
+
+ {% if feedbacks %}
+
+
+
+
{% trans "Employee" %}
+
{% trans "Feedback" %}
+
{% trans "Status" %}
+
+
+
+ {% for feedback in feedbacks %}
+
+
+
+ {{feedback.review_cycle}}
+
+
+ {{feedback.status}}
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+

+
+ {% trans "No feedbacks for Offboarding Employees." %}
+
+
+ {% endif %}
+
+
diff --git a/offboarding/templates/offboarding/dashboard/employee_task_table.html b/offboarding/templates/offboarding/dashboard/employee_task_table.html
new file mode 100644
index 000000000..ee2c508d6
--- /dev/null
+++ b/offboarding/templates/offboarding/dashboard/employee_task_table.html
@@ -0,0 +1,70 @@
+{% load static i18n offboarding_filter %}
+
+
+
+ {% if employees %}
+
+
+
+
{% trans "Employee" %}
+
{% trans "Stage" %}
+
+ {% trans "Task Status" %}
+
+
+
+
+ {% for employee in employees %}
+
+
+
+ {{employee.stage_id}}
+
+
+
+ {{ employee.employeetask_set|completed_tasks }} /
+ {{ employee.employeetask_set.all|length }}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+

+
+ {% trans "No Pending Tasks for Offboarding Employees." %}
+
+
+ {% endif %}
+
diff --git a/offboarding/templates/offboarding/task/table_body.html b/offboarding/templates/offboarding/task/table_body.html
index c0fbe61a3..3f4adb39c 100644
--- a/offboarding/templates/offboarding/task/table_body.html
+++ b/offboarding/templates/offboarding/task/table_body.html
@@ -112,7 +112,7 @@
>
{% endif %}
- {% if employee.employee_id.get_archive_condition %}
+ {% if employee.employee_id and employee.employee_id.get_archive_condition %}
0 else 0
+ )
+
+ context = {
+ "exit_ratio": round(exit_ratio, 4),
+ "employees": employees,
+ "archived_employees": archived_employees,
+ "resigning_employees": resigning_employees,
+ "onboarding_employees": len(onboarding_employees),
+ }
+ return render(request, "offboarding/dashboard/dashboard.html", context)
+
+
+@login_required
+@any_manager_can_enter(
+ ["offboarding.view_offboarding", "offboarding.view_offboardingtask"]
+)
+def dashboard_task_table(request):
+ """
+ This method is used to render the employee task table page in the dashboard.
+ """
+
+ employees = OffboardingEmployee.objects.entire()
+ return render(
+ request,
+ "offboarding/dashboard/employee_task_table.html",
+ {
+ "employees": employees,
+ },
+ )
+
+
+if apps.is_installed("asset"):
+
+ @login_required
+ @any_manager_can_enter(["offboarding.view_offboarding"])
+ def dashboard_asset_table(request):
+ """
+ This method is used to render the employee assets table page in the dashboard.
+ """
+ AssetAssignment = get_horilla_model_class(
+ app_label="asset", model="assetassignment"
+ )
+
+ offboarding_employees = OffboardingEmployee.objects.entire().values_list(
+ "employee_id__id", flat=True
+ )
+ assets = AssetAssignment.objects.entire().filter(
+ return_status__isnull=True,
+ assigned_to_employee_id__in=offboarding_employees,
+ )
+ return render(
+ request,
+ "offboarding/dashboard/asset_returned_table.html",
+ {"assets": assets},
+ )
+
+
+if apps.is_installed("pms"):
+
+ @login_required
+ @any_manager_can_enter("offboarding.view_offboarding")
+ def dashboard_feedback_table(request):
+ """
+ This method is used to render the employee assets table page in the dashboard.
+ """
+
+ Feedback = get_horilla_model_class(app_label="pms", model="feedback")
+
+ offboarding_employees = OffboardingEmployee.objects.entire().values_list(
+ "employee_id__id", "notice_period_starts"
+ )
+
+ if offboarding_employees:
+ id_list, date_list = map(list, zip(*offboarding_employees))
+ else:
+ id_list, date_list = [], []
+
+ feedbacks = (
+ Feedback.objects.entire()
+ .filter(employee_id__in=id_list)
+ .exclude(status="Closed")
+ )
+ return render(
+ request,
+ "offboarding/dashboard/employee_feedback_table.html",
+ {"feedbacks": feedbacks},
+ )
+
+
+@login_required
+@any_manager_can_enter("offboarding.view_offboarding")
+def dashboard_join_chart(request):
+ """
+ This method is used to render the joining - offboarding chart.
+ """
+
+ employees = Employee.objects.entire()
+ offboarding_employees = OffboardingEmployee.objects.entire()
+ archived_employees = offboarding_employees.filter(stage_id__type="archived")
+ resigning_employees = employees.filter(resignationletter__isnull=False).exclude(
+ offboardingemployee__stage_id__type="archived"
+ )
+
+ labels = ["resigning", "archived"]
+ items = [
+ resigning_employees.count(),
+ archived_employees.count(),
+ ]
+ if apps.is_installed("recruitment"):
+ Candidate = get_horilla_model_class(app_label="recruitment", model="candidate")
+ onboarding_employees = Candidate.objects.filter(
+ onboarding_stage__isnull=False, converted_employee_id__isnull=True
+ )
+ labels.append("New")
+ items.append(onboarding_employees.count())
+
+ response = {
+ "labels": labels,
+ "items": items,
+ }
+ return JsonResponse(response)
+
+
+@login_required
+@any_manager_can_enter("offboarding.view_offboarding")
+def department_job_postion_chart(request):
+ """
+ This method is used to render the department - job position chart.
+ """
+
+ departments = Department.objects.all()
+ offboarding_employees = OffboardingEmployee.objects.entire()
+
+ selected_departments = [
+ dept
+ for dept in departments
+ if offboarding_employees.filter(
+ employee_id__employee_work_info__department_id=dept.id
+ ).exists()
+ ]
+
+ job_positions = JobPosition.objects.filter(
+ id__in=offboarding_employees.values(
+ "employee_id__employee_work_info__job_position_id"
+ ).distinct()
+ )
+
+ labels = [dept.department for dept in selected_departments]
+
+ datasets = []
+ for job in job_positions:
+ job_dept = job.department_id
+ if job_dept not in selected_departments:
+ continue
+
+ data = [0] * len(selected_departments)
+ dept_index = labels.index(job_dept.department)
+
+ count = offboarding_employees.filter(
+ employee_id__employee_work_info__job_position_id=job.id
+ ).count()
+ data[dept_index] = count
+
+ datasets.append(
+ {
+ "label": f"{job.job_position} ({job_dept.department})",
+ "data": data,
+ "backgroundColor": f"hsl({hash(job.job_position) % 360}, 70%, 50%, 0.6)",
+ }
+ )
+
+ return JsonResponse({"labels": labels, "datasets": datasets})