diff --git a/attendance/static/dashboard/attendanceChart.js b/attendance/static/dashboard/attendanceChart.js index af42f929e..286c11d5a 100644 --- a/attendance/static/dashboard/attendanceChart.js +++ b/attendance/static/dashboard/attendanceChart.js @@ -1,4 +1,33 @@ $(document).ready(function () { + + // initializing the department overtime chart. + var departmentChartData = { + labels: [], + datasets: [], + }; + window["departmentOvertimeChart"] = {}; + const departmentOvertimeChart = document.getElementById("departmentOvertimeChart"); + var departmentAttendanceChart = new Chart(departmentOvertimeChart, { + type: "pie", + data: departmentChartData, + options: { + responsive: true, + maintainAspectRatio: false, + }, + plugins: [ + { + afterRender: (departmentAttendanceChart) => emptyOvertimeChart(departmentAttendanceChart), + }, + ], + }); + + var today = new Date(); + month = ("0" + (today.getMonth() + 1)).slice(-2); + year = today.getFullYear(); + var day = ("0" + today.getDate()).slice(-2); + var formattedDate = year + "-" + month + "-" + day; + var currentWeek = getWeekNumber(today); + $("#attendance_month").val(formattedDate); $.ajax({ @@ -11,6 +40,148 @@ $(document).ready(function () { createAttendanceChart(response.dataSet, response.labels); }, }); + + // Function to update the department overtime chart according to the response fetched from backend. + + function departmentDataUpdate(response) { + departmentChartData.labels = response.labels; + departmentChartData.datasets=response.dataset; + departmentChartData.message=response.message; + departmentChartData.emptyImageSrc=response.emptyImageSrc; + departmentAttendanceChart.update(); + } + + // Function to update the department overtime chart according to the dates provided. + + function changeDepartmentMonth() { + let type = $("#department_date_type").val(); + let date = $("#department_month").val(); + let end_date = $("#department_month2").val(); + $.ajax({ + type: "GET", + url: "/attendance/department-overtime-chart", + dataType: "json", + data: { + date: date, + type: type, + end_date: end_date, + }, + success: function (response) { + departmentDataUpdate(response); + }, + error: (error) => {}, + }); + } + + // Function to update the input fields according to type select field. + + function changeDepartmentView(element) { + var dataType = $(element).val(); + if (dataType === "date_range") { + $("#department_month").prop("type", "date"); + $("#department_day_input").after( + '' + ); + $("#department_month").val(formattedDate); + $("#department_month2").val(formattedDate); + changeDepartmentMonth(); + } else { + $("#department_month2").remove(); + if (dataType === "weekly") { + $("#department_month").prop("type", "week"); + if (currentWeek <10){ + $("#department_month").val(`${year}-W0${currentWeek}`); + } + else { + $("#department_month").val(`${year}-W${currentWeek}`); + } + changeDepartmentMonth(); + } else if (dataType === "day") { + $("#department_month").prop("type", "date"); + $("#department_month").val(formattedDate); + changeDepartmentMonth(); + } else { + $("#department_month").prop("type", "month"); + $("#department_month").val(`${year}-${month}`); + changeDepartmentMonth(); + } + } + } + + // Function for empty message for department overtime chart. + + function emptyOvertimeChart(departmentAttendanceChart, args, options) { + flag = false; + for (let i = 0; i < departmentAttendanceChart.data.datasets.length; i++) { + flag = flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean); + } + if (!flag) { + const { ctx, canvas } = departmentAttendanceChart; + departmentAttendanceChart.clear(); + const parent = canvas.parentElement; + + // Set canvas width/height to match + canvas.width = parent.clientWidth; + canvas.height = parent.clientHeight; + // Calculate center position + const x = canvas.width / 2; + const y = (canvas.height - 70) / 2; + var noDataImage = new Image(); + noDataImage.src = departmentAttendanceChart.data.emptyImageSrc + ? departmentAttendanceChart.data.emptyImageSrc + : "/static/images/ui/joiningchart.png"; + + message = departmentAttendanceChart.data.message + ? departmentAttendanceChart.data.message + : emptyMessages[languageCode]; + + noDataImage.onload = () => { + // Draw image first at center + ctx.drawImage(noDataImage, x - 35, y, 70, 70); + + // Draw text below image + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = "hsl(0,0%,45%)"; + ctx.font = "16px Poppins"; + ctx.fillText(message, x, y + 70 + 30); + }; + } + } + + // Ajax request to create department overtime chart initially. + + $.ajax({ + url: "/attendance/department-overtime-chart", + type: "GET", + dataType: "json", + headers: { + "X-Requested-With": "XMLHttpRequest", + }, + success: (response) => { + + departmentDataUpdate(response); + + }, + error: (error) => { + console.log("Error", error); + }, + }); + + // Functions to update department overtime chart while changing the date input field and select input field. + + $("#departmentChartCard").on("change","#department_date_type", function (e) { + changeDepartmentView($(this)) + }) + + $("#departmentChartCard").on("change","#department_month", function (e) { + changeDepartmentMonth() + }) + + $("#departmentChartCard").on("change","#department_month2", function (e) { + changeDepartmentMonth() + }) + }); var data; @@ -149,7 +320,12 @@ function changeView(element) { $("#attendance_month2").remove(); if (dataType === "weekly") { $("#attendance_month").prop("type", "week"); - $("#attendance_month").val(`${year}-W${currentWeek}`); + if (currentWeek <10){ + $("#attendance_month").val(`${year}-W0${currentWeek}`); + } + else { + $("#attendance_month").val(`${year}-W${currentWeek}`); + } changeMonth(); } else if (dataType === "day") { $("#attendance_month").prop("type", "date"); @@ -163,7 +339,7 @@ function changeView(element) { } } var chart = new Chart( - document.getElementById("pendingHoursCanvas").getContext("2d"), + document.getElementById("pendingHoursCanvas"), {} ); window["pendingHoursCanvas"] = chart; @@ -174,12 +350,13 @@ function pendingHourChart(year, month) { data: { month: month, year: year }, success: function (response) { pendingHoursCanvas.destroy(); - var ctx = document.getElementById("pendingHoursCanvas").getContext("2d"); + var ctx = document.getElementById("pendingHoursCanvas"); pendingHoursCanvas = new Chart(ctx, { type: "bar", // Bar chart type data: response.data, options: { responsive: true, + aspectRatio: false, indexAxis: "x", scales: { x: { @@ -187,6 +364,7 @@ function pendingHourChart(year, month) { }, y: { beginAtZero: true, + stacked: true, }, }, onClick: (e, activeEls) => { diff --git a/attendance/templates/attendance/dashboard/dashboard.html b/attendance/templates/attendance/dashboard/dashboard.html index 3f10f46da..6d5cd0ab3 100644 --- a/attendance/templates/attendance/dashboard/dashboard.html +++ b/attendance/templates/attendance/dashboard/dashboard.html @@ -1,181 +1,333 @@ -{% extends 'index.html' %} -{% block content %} -{% load static %} +{% extends 'index.html' %} {% load i18n %} {% block content %} {% load static %} +
-
-
-
-
-
-
- On Time -
-
-
- - {% if on_time < late_come %} - - {% else %} - - {% endif %} - - {{on_time}} +
+
+
+
+
+
+ {% trans "Today's Attendances" %} +
+
+
+ {{marked_attendances_ratio}}% +
+ {{marked_attendances}}/{{expected_attendances}} +
+
- {{on_time_ratio}}% -
-
-
-
-
-
- Today's Attendances -
-
-
- {% comment %} 100% {% endcomment %} - {{marked_attendances_ratio}}% +
+
+
+ {% trans "On Time" %} +
+
+
+ + {% if on_time < late_come %} + + {% else %} + + {% endif %} + + {{on_time}} +
+ {{on_time_ratio}}% +
+
- {{marked_attendances}}/{{expected_attendances}} -
-
-
-
-
-
- Late Come -
-
-
- - {% if late_come < on_time %} - - {% else %} - - {% endif %} - - - {{late_come}} +
+
+
+ {% trans "Late Come" %} +
+
+
+ + {% if late_come < on_time %} + + {% else %} + + {% endif %} + + {{late_come}} +
+ {{late_come_ratio}}% +
+
- {{late_come_ratio}}% +
+
+
+
-
-
-
-
- -
-
-
-
- Day Analytic -
-
- -
-
-
-
-
-
- Overall Conversions -
-
- -
-
-
-
-
-
-
-
-
-
- -
-
- Birthday - Katie Melua - 29/03/2023 -
-
- -
-
- -
- -
- Birthday - Katie Melua - 29/03/2023 -
-
- -
-
- -
-
- Birthday - Katie Melua - 29/03/2023 -
-
-
-
    -
  • -
  • -
  • -
-
- -
-
- On Break -
- -
-
    - {% for emp in on_break %} -
  • -
    -
    - Amy Winehouse -
    - {{emp.employee_id}} +
    +
    +
    + {% trans "Attendance Analytic" %} +
    +
    + + + + +
    +
    + +
    +
    +
    -
  • - {% endfor %} - - -
-
+
+ {% include "dashboard/not_in_yet.html" %} +
+ +
+
+
+
+
+ {% trans "Hours Chart" %} +
+
+ +
+
+
+ +
+
+
+
+
-
+ +
+
+
+
+ {% trans "On Break" %} +
+ +
+ {% if on_break %} +
    + {% for emp in on_break %} +
  • +
    +
    + Amy Winehouse +
    + {{emp.employee_id}} +
    +
  • + {% endfor %} +
+ {% else %} +
+
+ +

{% trans "No employees on Break...." %}

+
+
+ {% endif %} +
+
+
+
+
+
+ {% trans "Overtime to validate" %} +
+
+ {% include "attendance/dashboard/overtime_table.html" %} +
+
+
+
+
+ +
+ {% include "attendance/dashboard/to_validate_table.html" %} +
+
+
+
+
+ {% trans "Department overtime Chart" %} +
+
+ + + + +
+
+ +
+
+
+
+
-
- +
+ - + - {% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/attendance/templates/attendance/dashboard/overtime_table.html b/attendance/templates/attendance/dashboard/overtime_table.html new file mode 100644 index 000000000..1bd5df56f --- /dev/null +++ b/attendance/templates/attendance/dashboard/overtime_table.html @@ -0,0 +1,92 @@ +{% load i18n %} +{% if overtime_attendances %} +
+
+
+
+
+ {% trans "Employee" %} +
+
{% trans "Check-In" %}
+
+ {% trans "In Date" %} +
+
{% trans "Check-Out" %}
+
+ {% trans "Out Date" %} +
+
+ {% trans "Overtime" %} +
+
+
+
+ {% for attendance in overtime_attendances %} +
+
+
+
+
+ +
+ {{attendance.employee_id}} +
+
+
+ {{attendance.attendance_clock_in}} +
+
+ {{attendance.attendance_clock_in_date}} +
+
+ {{attendance.attendance_clock_out}} +
+
+ {{attendance.attendance_clock_out_date}} +
+
+ {{attendance.attendance_overtime}} +
+ +
+
+ {% endfor %} +
+
+{% else %} +
+
+ +

{% trans "No Overtime to Validate...." %}

+
+
+{% endif %} \ No newline at end of file diff --git a/attendance/templates/attendance/dashboard/to_validate_table.html b/attendance/templates/attendance/dashboard/to_validate_table.html new file mode 100644 index 000000000..f02471f2f --- /dev/null +++ b/attendance/templates/attendance/dashboard/to_validate_table.html @@ -0,0 +1,142 @@ +{% load i18n %} +
+
+ {% if validate_attendances.has_previous %} + + + + {% endif %} + + {% trans 'Attendance to validate' %} + + {% if validate_attendances.has_next %} + + + + {% endif %} +
+
+ {% if validate_attendances %} +
+
+
+
+
+ {% trans "Employee" %} +
+
+ {% trans "Date" %} +
+
{% trans "Check-In" %}
+
+ {% trans "In Date" %} +
+
{% trans "Check-Out" %}
+
+ {% trans "Out Date" %} +
+
{% trans "Shift" %}
+
{% trans "Work Type" %}
+
{% trans "Min Hour" %}
+
+ {% trans "At Work" %} +
+
+ {% trans "Pending Hour" %} +
+
+
+
+ {% for attendance in validate_attendances %} +
+
+ +
+
+
+ Mary Magdalene +
+ {{attendance.employee_id}} +
+
+
{{attendance.attendance_date}}
+
+ {{attendance.attendance_clock_in}} +
+
+ {{attendance.attendance_clock_in_date}} +
+
+ {{attendance.attendance_clock_out}} +
+
+ {{attendance.attendance_clock_out_date}} +
+
{{attendance.shift_id}}
+
{{attendance.work_type_id}}
+
{{attendance.minimum_hour}}
+
+ {{attendance.attendance_worked_hour}} +
+
{{attendance.hours_pending}}
+
+ {% if perms.attendance.change_attendance %} + + {% trans "Validate" %} + + {% endif %} +
+ +
+
+ {% endfor %} +
+
+ + {% else %} +
+
+ +

{% trans "All Attendance Validated." %}

+
+
+ {% endif %} +
+
\ No newline at end of file diff --git a/attendance/views/dashboard.py b/attendance/views/dashboard.py index 588b7af50..ef6386c19 100644 --- a/attendance/views/dashboard.py +++ b/attendance/views/dashboard.py @@ -15,11 +15,14 @@ from attendance.filters import ( LateComeEarlyOutFilter, AttendanceOverTimeFilter, ) -from attendance.models import Attendance, AttendanceLateComeEarlyOut, AttendanceOverTime +from attendance.models import Attendance, AttendanceLateComeEarlyOut, AttendanceOverTime, AttendanceValidationCondition +from attendance.views.views import strtime_seconds from base.methods import filtersubordinates from base.models import Department, EmployeeShiftSchedule from employee.models import Employee +from employee.not_in_out_dashboard import paginator_qry from horilla.decorators import login_required +from leave.models import LeaveRequest def find_on_time(request, today, week_day, department=None): @@ -29,22 +32,12 @@ def find_on_time(request, today, week_day, department=None): on_time = 0 attendances = Attendance.objects.filter(attendance_date=today) - attendances = filtersubordinates(request, attendances, "attendance.view_attendance") if department is not None: attendances = attendances.filter( employee_id__employee_work_info__department_id=department ) - excepted_attendances = 0 - for attendance in attendances: - shift = attendance.shift_id - schedules_today = shift.employeeshiftschedule_set.filter(day__day=week_day) - if schedules_today.first() is not None: - excepted_attendances = excepted_attendances + 1 - late_come_obj = attendance.late_come_early_out.filter( - type="late_come" - ).first() - if late_come_obj is None: - on_time = on_time + 1 + late_come = AttendanceLateComeEarlyOut.objects.filter(attendance_id__attendance_date=today,type="late_come") + on_time = len(attendances) - len(late_come) return on_time @@ -52,13 +45,9 @@ def find_expected_attendances(week_day): """ This method is used to find count of expected attendances for the week day """ - schedules_today = EmployeeShiftSchedule.objects.filter(day__day=week_day) - expected_attendances = 0 - for schedule in schedules_today: - shift = schedule.shift_id - expected_attendances = expected_attendances + len( - shift.employeeworkinformation_set.all() - ) + employees = Employee.objects.filter(is_active=True) + on_leave = LeaveRequest.objects.filter(status = "Approved") + expected_attendances = len(employees) - len(on_leave) return expected_attendances @@ -67,6 +56,8 @@ def dashboard(request): """ This method is used to render individual dashboard for attendance module """ + page_number = request.GET.get("page") + previous_data = request.GET.urlencode() employees = Employee.objects.filter( is_active=True, ).filter(~Q(employee_work_info__shift_id=None)) @@ -76,7 +67,8 @@ def dashboard(request): week_day = today.strftime("%A").lower() on_time = find_on_time(request, today=today, week_day=week_day) - late_come_obj = find_late_come(start_date=today) + late_come = find_late_come(start_date=today) + late_come_obj = len(late_come) marked_attendances = late_come_obj + on_time @@ -85,15 +77,30 @@ def dashboard(request): late_come_ratio = 0 marked_attendances_ratio = 0 if expected_attendances != 0: - on_time_ratio = f"{(on_time / expected_attendances) * 100:.1f}" - late_come_ratio = f"{(late_come_obj / expected_attendances) * 100:.1f}" + on_time_ratio = f"{(on_time / expected_attendances) * 100:.2f}" + late_come_ratio = f"{(late_come_obj / expected_attendances) * 100:.2f}" marked_attendances_ratio = ( - f"{(marked_attendances / expected_attendances) * 100:.1f}" + f"{(marked_attendances / expected_attendances) * 100:.2f}" ) early_outs = AttendanceLateComeEarlyOut.objects.filter( type="early_out", attendance_id__attendance_date=today ) + condition = AttendanceValidationCondition.objects.first() + min_ot = strtime_seconds("00:00") + if condition is not None and condition.minimum_overtime_to_approve is not None: + min_ot = strtime_seconds(condition.minimum_overtime_to_approve) + ot_attendances = Attendance.objects.filter( + overtime_second__gte=min_ot, + attendance_validated=True, + employee_id__is_active=True, + attendance_overtime_approve =False, + ) + + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id__is_active=True + ) + return render( request, "attendance/dashboard/dashboard.html", @@ -107,9 +114,30 @@ def dashboard(request): "marked_attendances": marked_attendances, "marked_attendances_ratio": marked_attendances_ratio, "on_break": early_outs, + "overtime_attendances": ot_attendances, + "validate_attendances": paginator_qry(validate_attendances, page_number), + "pd": previous_data, }, ) +@login_required +def validated_attendances_table(request): + page_number = request.GET.get("page") + previous_data = request.GET.urlencode() + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id__is_active=True + ) + context = { + "validate_attendances": paginator_qry(validate_attendances, page_number), + "pd": previous_data, + } + + return render( + request, + "attendance/dashboard/to_validate_table.html", + context + ) + def total_attendance(start_date, department, end_date=None): """ @@ -138,6 +166,14 @@ def find_late_come(start_date, department=None, end_date=None): "attendance_date__lte": end_date, } ).qs + else: + late_come_obj = LateComeEarlyOutFilter( + { + "type": "late_come", + "attendance_date__gte": start_date, + "attendance_date__lte": end_date, + } + ).qs return late_come_obj @@ -322,3 +358,79 @@ def pending_hours(request): } return JsonResponse({"data": data}) + + +@login_required +def department_overtime_chart(request): + start_date = request.GET.get("date") if request.GET.get("date") else date.today() + chart_type = request.GET.get("type") if request.GET.get("type") else "day" + end_date = request.GET.get("end_date") if request.GET.get("end_date") else start_date + + if chart_type == "day": + start_date = start_date + end_date = start_date + if chart_type == "weekly": + start_date, end_date = get_week_start_end_dates(start_date) + if chart_type == "monthly": + start_date, end_date = get_month_start_end_dates(start_date) + if chart_type == "date_range": + start_date = start_date + end_date = end_date + + attendance = total_attendance( + start_date=start_date, department=None, end_date=end_date + ) + + + condition = AttendanceValidationCondition.objects.first() + min_ot = strtime_seconds("00:00") + if condition is not None and condition.minimum_overtime_to_approve is not None: + min_ot = strtime_seconds(condition.minimum_overtime_to_approve) + attendances = attendance.filter( + overtime_second__gte=min_ot, + attendance_validated=True, + employee_id__is_active=True, + attendance_overtime_approve =True, + ) + departments = [] + department_total= [] + + for attendance in attendances: + departments.append( + attendance.employee_id.employee_work_info.department_id.department + ) + departments = list(set(departments)) + + for depart in departments: + department_total.append({"department": depart, "ot_hours": 0}) + + + for attendance in attendances: + if attendance.employee_id.employee_work_info.department_id: + department = attendance.employee_id.employee_work_info.department_id.department + ot = attendance.approved_overtime_second + ot_hrs = ot / 3600 + for depart in department_total: + if depart["department"] == department: + depart["ot_hours"] += ot_hrs + + dataset = [ + { + "label": "", + "data": [], + } + ] + + for depart_total, depart in zip(department_total, departments): + if depart == depart_total["department"]: + dataset[0]["data"].append(depart_total["ot_hours"]) + + response = { + "dataset": dataset, + "labels": departments, + "department_total": department_total, + "message": _("No validated Overtimes were found"), + "emptyImageSrc":"/static/images/ui/overtime-icon.png", + } + + return JsonResponse(response) \ No newline at end of file diff --git a/employee/not_in_out_dashboard.py b/employee/not_in_out_dashboard.py index 7150ee52b..064c6a546 100644 --- a/employee/not_in_out_dashboard.py +++ b/employee/not_in_out_dashboard.py @@ -15,6 +15,17 @@ from horilla.decorators import manager_can_enter, login_required from horilla import settings from employee.filters import EmployeeFilter from recruitment.models import RecruitmentMailTemplate +from django.core.paginator import Paginator + + + +def paginator_qry(qryset, page_number): + """ + This method is used to paginate query set + """ + paginator = Paginator(qryset, 20) + qryset = paginator.get_page(page_number) + return qryset @login_required @@ -24,12 +35,15 @@ def not_in_yet(request): This context processor wil return the employees, if they not marked the attendance for the day """ + page_number = request.GET.get("page") + previous_data = request.GET.urlencode() emps = ( EmployeeFilter({"not_in_yet": date.today()}) .qs.exclude(employee_work_info__isnull=True) .filter(is_active=True) ) - return render(request, "dashboard/not_in_yet.html", {"employees": emps}) + + return render(request, "dashboard/not_in_yet.html", {"employees": paginator_qry(emps, page_number),"pd": previous_data,}) @login_required diff --git a/employee/templates/dashboard/not_in_yet.html b/employee/templates/dashboard/not_in_yet.html index 7a16654c1..267999482 100644 --- a/employee/templates/dashboard/not_in_yet.html +++ b/employee/templates/dashboard/not_in_yet.html @@ -1,10 +1,24 @@ {% load i18n %}
- {% trans 'Not In Yet' %} + {% if employees.has_previous %} + + + + + {% endif %} + {% trans 'Not In Yet' %} + {% if employees.has_next %} + + + + + {% endif %}
-
+
{% for emp in employees %} diff --git a/static/images/ui/attendance-validate.png b/static/images/ui/attendance-validate.png new file mode 100644 index 000000000..8090bced7 Binary files /dev/null and b/static/images/ui/attendance-validate.png differ diff --git a/static/images/ui/coffee-break.png b/static/images/ui/coffee-break.png new file mode 100644 index 000000000..aee150c2c Binary files /dev/null and b/static/images/ui/coffee-break.png differ diff --git a/static/images/ui/comment.png b/static/images/ui/comment.png new file mode 100644 index 000000000..54f3ad364 Binary files /dev/null and b/static/images/ui/comment.png differ diff --git a/static/images/ui/overtime-icon.png b/static/images/ui/overtime-icon.png new file mode 100644 index 000000000..30e15900a Binary files /dev/null and b/static/images/ui/overtime-icon.png differ diff --git a/templates/dashboard.html b/templates/dashboard.html index bd9e7a05b..3ca455dce 100755 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -51,7 +51,7 @@ >
-
+
{% if perms.employee.view_employee %} @@ -144,7 +144,7 @@ {% if perms.candidate.view_employee or request.user|is_reportingmanager %}
@@ -326,7 +326,7 @@ style="width: 100px; color: #5e5c5c" />
-
+
{% endif %} + + {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Shift Request Approve" %} +
+
+ {% include "request_and_approve/shift_request.html" %} +
+
+
+ {% endif %} + {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Work Type Request Approve" %} +
+
+ {% include "request_and_approve/work_type_request.html" %} +
+
+
+ {% endif %}
@@ -539,6 +590,32 @@
+ +
+
+
+

+ {% trans "Details" %} +

+ +
+
+
+
diff --git a/templates/sidebar.html b/templates/sidebar.html index 1ee3fc5de..f42c8a64e 100755 --- a/templates/sidebar.html +++ b/templates/sidebar.html @@ -399,6 +399,11 @@ > {% endif %} +
  • + {% trans "Encashments & Reimbursements" %} +
  • {% if perms.payroll.view_filingstatus %}
  • +
  • + +
    + + + + + + + + + + + + + +
    + {% trans "Configuration" %} +
    + +