Today Board, Roster & HR Reports
Business view
Two audiences, two rhythms:
The daily operational view — a manager wants to know, right now, who's in. The Today board (HR → Overview → Today) refreshes every 60 seconds and shows each employee as Present / Late / Absent / On leave / Off / Not yet due, with expected hours and actual check-in. The Roster shows the expected 7-day week, and each employee's profile has a monthly timesheet.
The management reporting view — HR → Reports stacks three:
- Leave liability — what unused paid leave is worth in money (remaining
days × daily rate), so finance can see the accrued obligation.
- Punctuality by branch — check-ins, late count, late %, and average minutes
late, per branch, per month.
- Payroll cost trend — the last 12 months of base, commission, overtime,
allowances, deductions, loan repayments, GOSI (employee & employer), net, and total employer cost.
Plus workforce analytics on the Overview → Trends view: headcount, doctors, average tenure, headcount by department, and hires-vs-exits over time.
Reports are period-scoped — pick a year/month; a period with no processed data honestly shows "No data" while the columns still document what the report tracks.
Technical view
Live board / roster / timesheet — resolvers/queries/actions/hr/hrBoard.js
activeStaff (:14) = non-disabled users with an active salary profile, joined to their employee record.
hrToday(:32) — resolves each staffer's scheduled day, joins today's
attendance (day start in company timezone), and assigns a status with precedence (:56): attendance present → LATE/PRESENT; else ON_LEAVE; else not-working → OFF; else before expected start → NOT_YET_DUE; else ABSENT. Returns counts + rows sorted attention-first (ABSENT → LATE → …). UI: HrTodayBoard.js:25 (polls 60s, pauses when the tab is hidden).
teamRoster(:92) — expected 7-day week per employee×day (working/on
leave/holiday/expected times) — schedule only, no attendance.
timesheet(:123) — one employee's month; target viaresolveTargetUserId
(:126, self always allowed, others need a manager perm) then re-verified in-company. Per-day status: HOLIDAY / LEAVE / PRESENT / LATE / SCHEDULED / ABSENT / REST_DAY_WORK, plus totals.
orgChart(:196) — flat active-staff list; the client builds the tree.
Reports — resolvers/queries/actions/hr/hrReports.js
leaveLiabilityReport(:15) — for every active profile, sums remaining
paid day-based leave (via computeLeaveBalancesBatch) valued at baseSalary / 30 (:31, KSA daily-rate convention), skips zero-balance and disabled users, sorts by liability. Returns {year, totalDays, totalLiability, rows}.
punctualityByBranch(:42) — one month, timezone-correct bounds; a
groupBy [branchId, isLate] over attendance yielding checkIns / lateCount / latePercent / avgLateMinutes per branch; drops branches with no check-ins.
payrollCostTrend(:87) — last N (≤36) PROCESSED runs aggregated in a
single grouped query over PAID lines (avoids a per-run loop). Emits per-period base/commission/overtime/allowances/deductions/loanRepayments/gosi(emp+empr)/net and employerCost = net + loan + gosiEmployee + gosiEmployer; reversed to oldest→newest for charting.
Workforce analytics — hrBoard.js:211
hrAnalytics — headcount, doctors, average tenure (from hire dates), headcount by department, and a hires/exits monthly trend (≤36 months) from employee records' hire/termination dates. UI: HrOverview.js:54 (Trends), HrReports.js:184.
Permissions
hrToday = VIEW_SALARY_HUB | MANAGE_HR | VIEW_ATTENDANCE; teamRoster = VIEW_SALARY_HUB | MANAGE_HR | APPROVE_LEAVE; leaveLiabilityReport, punctualityByBranch, payrollCostTrend, hrAnalytics = VIEW_SALARY_HUB (permissions.js:2414). timesheet/orgChart are isAuthenticated with in-resolver self-scoping.
Verified live: the Reports page renders all three report tables even when the selected period has no data — "No data" is the correct empty state, not a bug. The screenshot in the Feature Tour shows exactly this.