hrms emp dashboard changes
This commit is contained in:
parent
a707df36cf
commit
e93480648a
|
|
@ -124,23 +124,33 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
|
|
||||||
def _leave_balances(self, employee):
|
def _leave_balances(self, employee):
|
||||||
LeaveType = request.env["hr.leave.type"].sudo()
|
LeaveType = request.env["hr.leave.type"].sudo()
|
||||||
Allocation = request.env["hr.leave.allocation"].sudo()
|
|
||||||
Leave = request.env["hr.leave"].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 = []
|
balances = []
|
||||||
for leave_type in LeaveType.search([("active", "=", True)], order="sequence, name"):
|
for leave_type in leave_types:
|
||||||
allocations = Allocation.search([
|
if leave_type.requires_allocation == "yes":
|
||||||
("employee_id", "=", employee.id),
|
data = allocation_data_by_type.get(leave_type.id, {})
|
||||||
("holiday_status_id", "=", leave_type.id),
|
allocated = data.get("max_leaves", 0)
|
||||||
("state", "=", "validate"),
|
taken = data.get("leaves_taken", 0)
|
||||||
])
|
planned = data.get("leaves_requested", 0)
|
||||||
leaves = Leave.search([
|
remaining = data.get("virtual_remaining_leaves", 0)
|
||||||
("employee_id", "=", employee.id),
|
else:
|
||||||
("holiday_status_id", "=", leave_type.id),
|
leaves = Leave.search([
|
||||||
("state", "in", ["confirm", "validate1", "validate"]),
|
("employee_id", "=", employee.id),
|
||||||
])
|
("holiday_status_id", "=", leave_type.id),
|
||||||
allocated = sum(allocations.mapped("number_of_days"))
|
("state", "in", ["confirm", "validate1", "validate"]),
|
||||||
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"))
|
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":
|
if allocated or taken or planned or leave_type.requires_allocation == "no":
|
||||||
balances.append({
|
balances.append({
|
||||||
"id": leave_type.id,
|
"id": leave_type.id,
|
||||||
|
|
@ -148,7 +158,10 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
"allocated": round(allocated, 2),
|
"allocated": round(allocated, 2),
|
||||||
"taken": round(taken, 2),
|
"taken": round(taken, 2),
|
||||||
"planned": round(planned, 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,
|
"requires_allocation": leave_type.requires_allocation,
|
||||||
})
|
})
|
||||||
return balances
|
return balances
|
||||||
|
|
@ -173,16 +186,19 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
def _get_public_holidays(self, employee, month_start, month_end):
|
def _get_public_holidays(self, employee, month_start, month_end):
|
||||||
start_dt = datetime.combine(month_start, time.min)
|
start_dt = datetime.combine(month_start, time.min)
|
||||||
end_dt = datetime.combine(month_end, time.max)
|
end_dt = datetime.combine(month_end, time.max)
|
||||||
|
|
||||||
calendar = employee.resource_calendar_id or employee.company_id.resource_calendar_id
|
calendar = employee.resource_calendar_id or employee.company_id.resource_calendar_id
|
||||||
holidays = request.env["resource.calendar.leaves"].sudo().search([
|
holidays = request.env["resource.calendar.leaves"].sudo().search([
|
||||||
("date_from", "<=", end_dt),
|
("date_from", "<=", end_dt),
|
||||||
("date_to", ">=", start_dt),
|
("date_to", ">=", start_dt),
|
||||||
("company_id", "in", [False, employee.company_id.id]),
|
("company_id", "in", [False, employee.company_id.id]),
|
||||||
|
("resource_id", "=", False), # Only company/public holidays
|
||||||
], order="date_from asc")
|
], order="date_from asc")
|
||||||
return holidays.filtered(lambda holiday: (
|
if calendar:
|
||||||
(not holiday.resource_id or holiday.resource_id == employee.resource_id)
|
holidays = holidays.filtered(
|
||||||
and (not holiday.calendar_id or holiday.calendar_id == calendar)
|
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"):
|
def _attendance_calendar(self, month_start, month_end, attendances, leaves, public_holidays, calendar_view="monthly"):
|
||||||
if calendar_view == "yearly":
|
if calendar_view == "yearly":
|
||||||
|
|
@ -229,14 +245,14 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
if outside_period:
|
if outside_period:
|
||||||
status = "empty"
|
status = "empty"
|
||||||
show_metrics = False
|
show_metrics = False
|
||||||
elif is_leave:
|
|
||||||
status = "leave"
|
|
||||||
label = _("Time Off")
|
|
||||||
show_metrics = is_half_day_leave
|
|
||||||
elif is_holiday:
|
elif is_holiday:
|
||||||
status = "holiday"
|
status = "holiday"
|
||||||
label = holiday_by_day[cursor]
|
label = holiday_by_day[cursor]
|
||||||
show_metrics = has_worked_hours
|
show_metrics = has_worked_hours
|
||||||
|
elif is_leave:
|
||||||
|
status = "leave"
|
||||||
|
label = _("Time Off")
|
||||||
|
show_metrics = is_half_day_leave
|
||||||
elif has_worked_hours:
|
elif has_worked_hours:
|
||||||
status = "present"
|
status = "present"
|
||||||
elif is_weekend:
|
elif is_weekend:
|
||||||
|
|
@ -388,26 +404,48 @@ class HrmsEmployeeDashboard(http.Controller):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _attendance_summary(self, month_start, month_end, attendances, leaves, public_holidays):
|
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)
|
counts = defaultdict(int)
|
||||||
|
|
||||||
|
expected_hours = 0.0
|
||||||
|
worked_hours = 0.0
|
||||||
|
break_hours = 0.0
|
||||||
|
leave_hours = 0.0
|
||||||
|
|
||||||
for day in days:
|
for day in days:
|
||||||
|
if day["outside_period"]:
|
||||||
|
continue
|
||||||
|
|
||||||
counts[day["status"]] += 1
|
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"))
|
expected_hours += day["expected_hours"]
|
||||||
break_hours = sum(day["break_hours"] for day in days if not day["outside_period"])
|
worked_hours += day["worked_hours"]
|
||||||
leave_hours = sum(day["leave_hours"] for day in days if not day["outside_period"])
|
break_hours += day["break_hours"]
|
||||||
|
leave_hours += day["leave_hours"]
|
||||||
|
|
||||||
remaining_hours = expected_hours - worked_hours - leave_hours
|
remaining_hours = expected_hours - worked_hours - leave_hours
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"present": counts["present"],
|
"present": counts["present"],
|
||||||
"absent": counts["absent"],
|
"absent": counts["absent"],
|
||||||
"leave": counts["leave"],
|
"leave": counts["leave"],
|
||||||
"holiday": counts["holiday"],
|
"holiday": counts["holiday"],
|
||||||
|
|
||||||
"hours": round(worked_hours, 2),
|
"hours": round(worked_hours, 2),
|
||||||
|
|
||||||
"worked_hours": round(worked_hours, 2),
|
"worked_hours": round(worked_hours, 2),
|
||||||
"break_hours": round(break_hours, 2),
|
"break_hours": round(break_hours, 2),
|
||||||
"expected_hours": round(expected_hours, 2),
|
"expected_hours": round(expected_hours, 2),
|
||||||
"leave_hours": round(leave_hours, 2),
|
"leave_hours": round(leave_hours, 2),
|
||||||
"remaining_hours": round(remaining_hours, 2),
|
"remaining_hours": round(remaining_hours, 2),
|
||||||
|
|
||||||
"worked_display": self._format_hours(worked_hours),
|
"worked_display": self._format_hours(worked_hours),
|
||||||
"break_display": self._format_hours(break_hours),
|
"break_display": self._format_hours(break_hours),
|
||||||
"expected_display": self._format_hours(expected_hours),
|
"expected_display": self._format_hours(expected_hours),
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.hrms-leave-tile span {
|
.hrms-leave-tile span {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 64px;
|
||||||
|
margin-right: 10px;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
@ -482,6 +485,91 @@
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
font-size: 18px;
|
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 {
|
.hrms-two-charts {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.3fr 0.7fr;
|
grid-template-columns: 1.3fr 0.7fr;
|
||||||
|
|
@ -553,10 +641,15 @@
|
||||||
|
|
||||||
.hrms-employee-grid,
|
.hrms-employee-grid,
|
||||||
.hrms-grid,
|
.hrms-grid,
|
||||||
|
.hrms-leave-graph-row,
|
||||||
.hrms-two-charts {
|
.hrms-two-charts {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hrms-leave-legend {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.hrms-panel.wide {
|
.hrms-panel.wide {
|
||||||
grid-column: span 1;
|
grid-column: span 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ class HrmsEmployeeDashboard extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.state.data = response;
|
this.state.data = response;
|
||||||
// setTimeout(() => this.initCharts(), 100);
|
|
||||||
const expenses = response.expenses || {};
|
const expenses = response.expenses || {};
|
||||||
const total =
|
const total =
|
||||||
(expenses.series || []).reduce(
|
(expenses.series || []).reduce(
|
||||||
|
|
@ -76,11 +75,7 @@ class HrmsEmployeeDashboard extends Component {
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
this.state.expenseCount = total;
|
this.state.expenseCount = total;
|
||||||
if (this.state.expenseCount > 0) {
|
setTimeout(() => this.initCharts(), 100);
|
||||||
setTimeout(() => this.initCharts(), 100);
|
|
||||||
} else {
|
|
||||||
this.destroyCharts();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.state.error = "Unable to load employee dashboard.";
|
this.state.error = "Unable to load employee dashboard.";
|
||||||
|
|
@ -134,17 +129,18 @@ class HrmsEmployeeDashboard extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLeaveChart(leaves) {
|
renderLeaveChart(leaves) {
|
||||||
|
const allocatedLeaves = leaves.filter((leave) => leave.requires_allocation === "yes");
|
||||||
this.chartBase("#hrmsLeaveChart", {
|
this.chartBase("#hrmsLeaveChart", {
|
||||||
chart: { type: "bar" },
|
chart: { type: "bar" },
|
||||||
series: [
|
series: [
|
||||||
{ name: "Remaining", data: leaves.map((leave) => leave.remaining) },
|
{ name: "Remaining", data: allocatedLeaves.map((leave) => Number(leave.remaining || 0)) },
|
||||||
{ name: "Taken", data: leaves.map((leave) => leave.taken) },
|
{ name: "Taken", data: allocatedLeaves.map((leave) => Number(leave.taken || 0)) },
|
||||||
{ name: "Planned", data: leaves.map((leave) => leave.planned) },
|
{ name: "Planned", data: allocatedLeaves.map((leave) => Number(leave.planned || 0)) },
|
||||||
],
|
],
|
||||||
colors: ["#16a34a", "#2563eb", "#f59e0b"],
|
colors: ["#16a34a", "#2563eb", "#f59e0b"],
|
||||||
plotOptions: { bar: { borderRadius: 4, columnWidth: "50%" } },
|
plotOptions: { bar: { borderRadius: 4, columnWidth: "50%" } },
|
||||||
dataLabels: { enabled: false },
|
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 },
|
yaxis: { min: 0, forceNiceScale: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,11 +172,32 @@
|
||||||
<strong t-esc="leave.name"/>
|
<strong t-esc="leave.name"/>
|
||||||
<div>
|
<div>
|
||||||
<span><b t-esc="leave.remaining"/> Balance</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="hrmsLeaveChart"/>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="hrms-panel">
|
<section class="hrms-panel">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue