Dentolize · HR Module Walkthrough
On this pageBusiness viewTechnical view

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 via resolveTargetUserId

(: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.