hrms emp dashboard changes

This commit is contained in:
pranaysaidurga 2026-07-09 22:21:11 +05:30
parent a707df36cf
commit e93480648a
4 changed files with 188 additions and 40 deletions

View File

@ -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),

View File

@ -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;
}

View File

@ -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 },
});
}

View File

@ -172,11 +172,32 @@
<strong t-esc="leave.name"/>
<div>
<span><b t-esc="leave.remaining"/> Balance</span>
<span><b t-esc="leave.taken"/> Taken</span>
<span><b t-esc="leave.planned"/> Confirmed</span>
</div>
</div>
</div>
</div>
<div class="hrms-leave-graph">
<div t-foreach="state.data.leave_balances" t-as="leave" t-key="leave.id + '-graph'">
<div t-if="leave.requires_allocation === 'yes'" class="hrms-leave-graph-row">
<div class="hrms-leave-graph-label">
<strong t-esc="leave.name"/>
<span><t t-esc="leave.allocated"/> allocated</span>
</div>
<div class="hrms-leave-bar" role="img" t-att-aria-label="leave.name + ': ' + leave.remaining + ' balance, ' + leave.taken + ' taken, ' + leave.planned + ' confirmed'">
<span class="hrms-leave-bar-remaining" t-att-style="'width: ' + leave.remaining_percent + '%'"/>
<span class="hrms-leave-bar-taken" t-att-style="'width: ' + leave.taken_percent + '%'"/>
<span class="hrms-leave-bar-planned" t-att-style="'width: ' + leave.planned_percent + '%'"/>
</div>
<div class="hrms-leave-legend">
<span><i class="remaining"/>Balance <b t-esc="leave.remaining"/></span>
<span><i class="taken"/>Taken <b t-esc="leave.taken"/></span>
<span><i class="planned"/>Confirmed <b t-esc="leave.planned"/></span>
</div>
</div>
</div>
</div>
<div id="hrmsLeaveChart"/>
</section>
<section class="hrms-panel">