From e93480648a2b9db291f4a4f9ae63d4f8f4fdcf93 Mon Sep 17 00:00:00 2001 From: pranaysaidurga Date: Thu, 9 Jul 2026 22:21:11 +0530 Subject: [PATCH] hrms emp dashboard changes --- .../controllers/hrms_emp_dashboard.py | 96 +++++++++++++------ .../static/src/css/hrms_emp_dashboard.css | 93 ++++++++++++++++++ .../static/src/js/hrms_emp_dashboard.js | 16 ++-- .../static/src/xml/hrms_emp_dashboard.xml | 23 ++++- 4 files changed, 188 insertions(+), 40 deletions(-) diff --git a/addons_extensions/hrms_emp_dashboard/controllers/hrms_emp_dashboard.py b/addons_extensions/hrms_emp_dashboard/controllers/hrms_emp_dashboard.py index 85ec43d87..9ae0b4e09 100644 --- a/addons_extensions/hrms_emp_dashboard/controllers/hrms_emp_dashboard.py +++ b/addons_extensions/hrms_emp_dashboard/controllers/hrms_emp_dashboard.py @@ -124,23 +124,33 @@ class HrmsEmployeeDashboard(http.Controller): def _leave_balances(self, employee): LeaveType = request.env["hr.leave.type"].sudo() - Allocation = request.env["hr.leave.allocation"].sudo() Leave = request.env["hr.leave"].sudo() + employee = employee.sudo() + leave_types = LeaveType.search([("active", "=", True)], order="sequence, name") + allocation_data = leave_types.get_allocation_data(employee)[employee] + allocation_data_by_type = { + leave_type_id: data + for _name, data, _requires_allocation, leave_type_id in allocation_data + } balances = [] - for leave_type in LeaveType.search([("active", "=", True)], order="sequence, name"): - allocations = Allocation.search([ - ("employee_id", "=", employee.id), - ("holiday_status_id", "=", leave_type.id), - ("state", "=", "validate"), - ]) - leaves = Leave.search([ - ("employee_id", "=", employee.id), - ("holiday_status_id", "=", leave_type.id), - ("state", "in", ["confirm", "validate1", "validate"]), - ]) - allocated = sum(allocations.mapped("number_of_days")) - taken = sum(leaves.filtered(lambda leave: leave.state == "validate").mapped("number_of_days")) - planned = sum(leaves.filtered(lambda leave: leave.state in ("confirm", "validate1")).mapped("number_of_days")) + for leave_type in leave_types: + if leave_type.requires_allocation == "yes": + data = allocation_data_by_type.get(leave_type.id, {}) + allocated = data.get("max_leaves", 0) + taken = data.get("leaves_taken", 0) + planned = data.get("leaves_requested", 0) + remaining = data.get("virtual_remaining_leaves", 0) + else: + leaves = Leave.search([ + ("employee_id", "=", employee.id), + ("holiday_status_id", "=", leave_type.id), + ("state", "in", ["confirm", "validate1", "validate"]), + ]) + allocated = 0 + taken = sum(leaves.filtered(lambda leave: leave.state == "validate").mapped("number_of_days")) + planned = sum(leaves.filtered(lambda leave: leave.state in ("confirm", "validate1")).mapped("number_of_days")) + remaining = 0 + chart_total = max(allocated, taken + planned + max(remaining, 0), 1) if allocated or taken or planned or leave_type.requires_allocation == "no": balances.append({ "id": leave_type.id, @@ -148,7 +158,10 @@ class HrmsEmployeeDashboard(http.Controller): "allocated": round(allocated, 2), "taken": round(taken, 2), "planned": round(planned, 2), - "remaining": round(allocated - taken - planned, 2) if leave_type.requires_allocation != "no" else 0, + "remaining": round(remaining, 2), + "taken_percent": round((taken / chart_total) * 100, 2), + "planned_percent": round((planned / chart_total) * 100, 2), + "remaining_percent": round((max(remaining, 0) / chart_total) * 100, 2), "requires_allocation": leave_type.requires_allocation, }) return balances @@ -173,16 +186,19 @@ class HrmsEmployeeDashboard(http.Controller): def _get_public_holidays(self, employee, month_start, month_end): start_dt = datetime.combine(month_start, time.min) end_dt = datetime.combine(month_end, time.max) + calendar = employee.resource_calendar_id or employee.company_id.resource_calendar_id holidays = request.env["resource.calendar.leaves"].sudo().search([ ("date_from", "<=", end_dt), ("date_to", ">=", start_dt), ("company_id", "in", [False, employee.company_id.id]), + ("resource_id", "=", False), # Only company/public holidays ], order="date_from asc") - return holidays.filtered(lambda holiday: ( - (not holiday.resource_id or holiday.resource_id == employee.resource_id) - and (not holiday.calendar_id or holiday.calendar_id == calendar) - )) + if calendar: + holidays = holidays.filtered( + lambda h: not h.calendar_id or h.calendar_id == calendar + ) + return holidays def _attendance_calendar(self, month_start, month_end, attendances, leaves, public_holidays, calendar_view="monthly"): if calendar_view == "yearly": @@ -229,14 +245,14 @@ class HrmsEmployeeDashboard(http.Controller): if outside_period: status = "empty" show_metrics = False - elif is_leave: - status = "leave" - label = _("Time Off") - show_metrics = is_half_day_leave elif is_holiday: status = "holiday" label = holiday_by_day[cursor] show_metrics = has_worked_hours + elif is_leave: + status = "leave" + label = _("Time Off") + show_metrics = is_half_day_leave elif has_worked_hours: status = "present" elif is_weekend: @@ -388,26 +404,48 @@ class HrmsEmployeeDashboard(http.Controller): return result def _attendance_summary(self, month_start, month_end, attendances, leaves, public_holidays): - days = self._attendance_calendar(month_start, month_end, attendances, leaves, public_holidays) + days = self._attendance_calendar( + month_start, + month_end, + attendances, + leaves, + public_holidays, + ) + counts = defaultdict(int) + + expected_hours = 0.0 + worked_hours = 0.0 + break_hours = 0.0 + leave_hours = 0.0 + for day in days: + if day["outside_period"]: + continue + counts[day["status"]] += 1 - expected_hours = sum(day["expected_hours"] for day in days if not day["outside_period"]) - worked_hours = sum(attendances.mapped("worked_hours")) - break_hours = sum(day["break_hours"] for day in days if not day["outside_period"]) - leave_hours = sum(day["leave_hours"] for day in days if not day["outside_period"]) + + expected_hours += day["expected_hours"] + worked_hours += day["worked_hours"] + break_hours += day["break_hours"] + leave_hours += day["leave_hours"] + remaining_hours = expected_hours - worked_hours - leave_hours + return { "present": counts["present"], "absent": counts["absent"], "leave": counts["leave"], "holiday": counts["holiday"], + "hours": round(worked_hours, 2), + "worked_hours": round(worked_hours, 2), "break_hours": round(break_hours, 2), "expected_hours": round(expected_hours, 2), "leave_hours": round(leave_hours, 2), "remaining_hours": round(remaining_hours, 2), + "worked_display": self._format_hours(worked_hours), "break_display": self._format_hours(break_hours), "expected_display": self._format_hours(expected_hours), diff --git a/addons_extensions/hrms_emp_dashboard/static/src/css/hrms_emp_dashboard.css b/addons_extensions/hrms_emp_dashboard/static/src/css/hrms_emp_dashboard.css index 3687fefd1..b6e9b2d32 100644 --- a/addons_extensions/hrms_emp_dashboard/static/src/css/hrms_emp_dashboard.css +++ b/addons_extensions/hrms_emp_dashboard/static/src/css/hrms_emp_dashboard.css @@ -473,6 +473,9 @@ } .hrms-leave-tile span { + display: inline-block; + min-width: 64px; + margin-right: 10px; color: #64748b; font-size: 11px; } @@ -482,6 +485,91 @@ color: #0f172a; font-size: 18px; } + +.hrms-leave-graph { + display: grid; + gap: 12px; +} + +.hrms-leave-graph-row { + display: grid; + grid-template-columns: minmax(140px, 190px) minmax(180px, 1fr); + gap: 10px 14px; + align-items: center; +} + +.hrms-leave-graph-label strong { + display: block; + color: #0f172a; + font-size: 13px; +} + +.hrms-leave-graph-label span { + color: #64748b; + font-size: 11px; +} + +.hrms-leave-bar { + display: flex; + width: 100%; + height: 18px; + overflow: hidden; + border-radius: 8px; + background: #e5e7eb; +} + +.hrms-leave-bar span { + min-width: 0; +} + +.hrms-leave-bar-remaining { + background: #16a34a; +} + +.hrms-leave-bar-taken { + background: #2563eb; +} + +.hrms-leave-bar-planned { + background: #f59e0b; +} + +.hrms-leave-legend { + grid-column: 2; + display: flex; + flex-wrap: wrap; + gap: 8px 14px; + color: #64748b; + font-size: 11px; +} + +.hrms-leave-legend span { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.hrms-leave-legend i { + width: 9px; + height: 9px; + border-radius: 2px; +} + +.hrms-leave-legend i.remaining { + background: #16a34a; +} + +.hrms-leave-legend i.taken { + background: #2563eb; +} + +.hrms-leave-legend i.planned { + background: #f59e0b; +} + +.hrms-leave-legend b { + color: #0f172a; +} .hrms-two-charts { display: grid; grid-template-columns: 1.3fr 0.7fr; @@ -553,10 +641,15 @@ .hrms-employee-grid, .hrms-grid, + .hrms-leave-graph-row, .hrms-two-charts { grid-template-columns: 1fr; } + .hrms-leave-legend { + grid-column: 1; + } + .hrms-panel.wide { grid-column: span 1; } diff --git a/addons_extensions/hrms_emp_dashboard/static/src/js/hrms_emp_dashboard.js b/addons_extensions/hrms_emp_dashboard/static/src/js/hrms_emp_dashboard.js index e32a870d6..6cfeb97a6 100644 --- a/addons_extensions/hrms_emp_dashboard/static/src/js/hrms_emp_dashboard.js +++ b/addons_extensions/hrms_emp_dashboard/static/src/js/hrms_emp_dashboard.js @@ -68,7 +68,6 @@ class HrmsEmployeeDashboard extends Component { return; } this.state.data = response; - // setTimeout(() => this.initCharts(), 100); const expenses = response.expenses || {}; const total = (expenses.series || []).reduce( @@ -76,11 +75,7 @@ class HrmsEmployeeDashboard extends Component { 0 ); this.state.expenseCount = total; - if (this.state.expenseCount > 0) { - setTimeout(() => this.initCharts(), 100); - } else { - this.destroyCharts(); - } + setTimeout(() => this.initCharts(), 100); } catch (error) { console.error(error); this.state.error = "Unable to load employee dashboard."; @@ -134,17 +129,18 @@ class HrmsEmployeeDashboard extends Component { } renderLeaveChart(leaves) { + const allocatedLeaves = leaves.filter((leave) => leave.requires_allocation === "yes"); this.chartBase("#hrmsLeaveChart", { chart: { type: "bar" }, series: [ - { name: "Remaining", data: leaves.map((leave) => leave.remaining) }, - { name: "Taken", data: leaves.map((leave) => leave.taken) }, - { name: "Planned", data: leaves.map((leave) => leave.planned) }, + { name: "Remaining", data: allocatedLeaves.map((leave) => Number(leave.remaining || 0)) }, + { name: "Taken", data: allocatedLeaves.map((leave) => Number(leave.taken || 0)) }, + { name: "Planned", data: allocatedLeaves.map((leave) => Number(leave.planned || 0)) }, ], colors: ["#16a34a", "#2563eb", "#f59e0b"], plotOptions: { bar: { borderRadius: 4, columnWidth: "50%" } }, dataLabels: { enabled: false }, - xaxis: { categories: leaves.map((leave) => leave.name), labels: { rotate: -25, trim: true } }, + xaxis: { categories: allocatedLeaves.map((leave) => leave.name), labels: { rotate: -25, trim: true } }, yaxis: { min: 0, forceNiceScale: true }, }); } diff --git a/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml b/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml index 6631f230a..5c60d1564 100644 --- a/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml +++ b/addons_extensions/hrms_emp_dashboard/static/src/xml/hrms_emp_dashboard.xml @@ -172,11 +172,32 @@
Balance + Taken + Confirmed +
+ + + +
+
+
+
+ + allocated +
+ +
+ Balance + Taken + Confirmed
-