Staff Hub — HR & Attendance Module (Design Spec)
Date: 2026-06-14 Status: Approved (brainstorm) → ready for implementation plan Owner: ssh Module key:staff_hr · Label: “Staff Hub” · Tier: Pro+ (bundled, auto-enabled by plan sync)
1. Summary
A self-contained HR module for clinics, sitting on top of the existingemployees/payroll
chain. It delivers three capabilities in one v1 release:
- A. Attendance / time-clock — staff clock in/out at a shared clinic kiosk, with on-premises enforcement by IP allowlist (no biometric hardware, no GPS games in v1). Punches roll up to daily/period summaries that feed payroll.
- B. Leave management — BreatheHR-style leave types, balances, accrual, carry-over and encashment, seeded with Pakistan statutory defaults. Admin records and approves leave.
- C. Pakistan hiring/termination document pack — fillable, printable HR letters (offer, appointment, probation, warning, show-cause, termination, dismissal, relieving, experience/service certificate, salary certificate, full & final settlement) rendered on clinic letterhead, auto-filled from the employee record, and filed against the employee.
Decisions locked during brainstorm
- Biometric is deferred. v1 presence enforcement = IP allowlist (clock-in must originate from the clinic’s registered network/public IP). A personal PIN identifies the staffer at a shared kiosk. The punch-capture path is built behind a pluggable “punch source” interface so selfie/biometric/fingerprint-device can be added later with zero rework.
- Access = clinic admin only for all management screens. The clock-in kiosk is a separate locked station used by all staff but exposes no dashboards. No employee self-service portal in v1.
- Packaging = bundled into Pro+ (same tier as Expenses/Analytics), auto-enabled via plan sync.
- Legal framing = templates, not legal advice. Every document carries a footer recommending the clinic’s own legal counsel review before use.
Non-goals (v1)
Employee self-service portal · Network Hub multi-branch HR roll-up (org_admin) · AI face-match on selfies · physical fingerprint-device bridge · automated EOBI/SESSI/PESSI filing · shift-roster planning/scheduling · overtime auto-calculation rules engine.2. Legal grounding (Pakistan)
Templates and leave defaults are grounded in the Industrial & Commercial Employment (Standing Orders) Ordinance, 1968 and the provincial Shops & Establishments statutes. These inform defaults and field requirements only; they are not legal advice and every clinic can edit them.| Statutory item | Default encoded | Source |
|---|---|---|
| Casual leave | 10 days/yr paid, ≤3 consecutive, no carry-over | Shops & Establishments |
| Sick leave | 8 days/yr paid, carry-forward, cap 16 | Shops & Establishments |
| Annual/earned leave | 14 days/yr after 12 mo service, accrue to 30, encashable | Shops & Establishments |
| Festival holidays | 10 days/yr paid | Shops & Establishments |
| Maternity | provincial (e.g. Sindh 16 weeks) — configurable | provincial Maternity Benefit laws |
| Probation | ≤3 months (non-managerial), no notice on termination during probation | Standing Orders SO 1 |
| Notice (permanent) | 1 month notice or pay in lieu; reasons stated in writing | Standing Orders |
| Misconduct dismissal | written charge + opportunity to explain (show-cause/inquiry) | Standing Orders SO 15 |
| Service certificate | statutory right at end of service | Standing Orders |
3. Roles, access & packaging
- Module gating: add
{ key: 'staff_hr', label: 'Staff Hub', category: 'admin', minPlan: 'Pro+' }toAVAILABLE_MODULES(server/src/constants/modules.ts). Gated bymodule-guardmiddleware +clinic_modules. Auto-enabled by plan sync at Pro+. - Permission keys (new nodes in
PERMISSION_TREE, server + UI parity enforced by the CI tripwire):staff.hr.view/staff.hr.manage(umbrella; admin-default-on)staff.attendance.view/staff.attendance.managestaff.leave.view/staff.leave.managestaff.documents.view/staff.documents.manage- Defaults grant all to the clinic admin role only (per “admin only” decision); reception/doctor get none in v1.
- Routes follow the established pattern:
requireClinicContext+requirePermissionByMethod(...). - Kiosk endpoints are an exception — they are not behind the admin permission (staff aren’t admins). They authenticate via a kiosk device token (see §4.2), scoped to one clinic, and are the only un-admin-gated surface in the module.
4. Part A — Attendance / time-clock
4.1 Integrity model (software-only, IP-enforced)
Clock-in is accepted only if all pass:- Kiosk device token present and valid (admin-registered device; reuses the
device_trust_tokenshashing pattern → newattendance_kioskstable). - Request IP ∈ clinic IP allowlist (
clinic.hrSettings.allowedIps). The server reads the real client IP from the CloudflareCF-Connecting-IPheader (not a spoofable body field). - Personal PIN matches the staffer’s hashed
attendance_pin(bcrypt/scrypt via existinglib/password). PIN identifies which employee is punching. - (deferred-ready) optional punch-source attestation — interface allows a future selfie blob
or fingerprint-device payload; v1 ships the
pinsource only.
4.2 Data model
timestamptz; all day-boundary/lateness math done with explicit
AT TIME ZONE 'Asia/Karachi' (canonical via lib/pkt). Never compare a timestamptz instant to a
naive wall-clock recast. Clinic work-start time lives in hrSettings.workHours for lateness calc.
4.3 Clinic hrSettings (new jsonb column on clinics)
4.4 Endpoints
Admin (clinic-context +staff.attendance.*):
GET /hr/kiosks·POST /hr/kiosks(register → returns one-time token) ·DELETE /hr/kiosks/:idGET /hr/attendance?from&to&employeeId(summaries + punch detail)POST /hr/attendance/adjustments(correction with reason)POST /hr/employees/:id/pin(set/reset a staffer’s clock-in PIN)
GET /hr/kiosk/roster(clinic staff list w/ photos for the tap screen)POST /hr/kiosk/punch{ employeeId, pin, type }→ validates token+IP+PIN, appends a punch, recomputes that day’s summary.
5. Part B — Leave management
5.1 Data model
5.2 Behaviour
- Seeding: on module enable (and on
leavePolicyVersionbump), seedleave_typesfrom the §2 PK defaults via an idempotent upsert keyed on(clinic_id, key). Admin can edit quotas/flags. - Balances: created lazily per employee per leave-year; accrual respects
accrualmode (after_12mofor annual). A recorded leave decrementstakenand writesattendance_day_summary.status = 'leave'for covered work-days. - Encashment (annual): admin action that moves balance →
encashedand emits a payroll line item reference for the next run / full & final settlement. - Payroll link: unpaid (LWOP) days produce a deduction reference consumed by the payroll run (does not auto-edit payroll; surfaces a suggested deduction the admin confirms).
5.3 Endpoints (admin, staff.leave.*)
GET/POST/PATCH /hr/leave-typesGET /hr/leave-balances?employeeId&yearGET/POST /hr/leave-records·POST /hr/leave-records/:id/cancelPOST /hr/leave-records/encash(annual encashment)
6. Part C — Pakistan HR document pack
6.1 Approach
Server-side template definitions (code, versioned) + clinic letterhead. Reuses the existing letterhead/react-pdf export path (document-letterhead route + R2 + lib/r2). Each template
declares merge fields auto-filled from the employee record, plus editable free-text blocks the
admin fills in a form before issuing. Issuing renders a PDF, stores it in R2, and files an
hr_documents row against the employee. Documents print directly (browser print) or download.
6.2 Document set (v1)
- Offer letter
- Appointment letter / employment contract — category (permanent/probationer/temporary/contract), probation (≤3 mo), salary breakdown, leave entitlement, notice terms, working hours
- Probation confirmation letter
- Warning letter (1st/2nd)
- Show-cause notice (misconduct: written charge + deadline to explain — SO 15)
- Termination letter (simpliciter) — 1 month notice or pay-in-lieu, reasons stated
- Dismissal letter (misconduct) — post-inquiry, grounds stated
- Resignation acceptance / relieving letter
- Experience / service certificate (statutory end-of-service right)
- Salary certificate
- Full & final settlement — leave encashment, EOBI/gratuity references, dues
6.3 Data model
6.4 Disclaimer
Every generated PDF carries a small footer:This document is a template provided for convenience and reflects common Pakistani employment practice and the cited statutes. It is not legal advice. Please have it reviewed by your legal advisor before use.
6.5 Endpoints (admin, staff.documents.*)
GET /hr/documents/templates(list + required fields per type)POST /hr/documents{ employeeId, docType, fields }→ render + store + file rowGET /hr/documents?employeeId·GET /hr/documents/:id(download/print) ·DELETE /hr/documents/:id
7. UI surfaces (admin-only, gated by module + permission)
New “Staff Hub” section in the sidebar (admin role; respects [[project_sidebar_ordering]]):- People — employee roster (extends the payroll employees view): profile, HR fields, set PIN, leave balances, document history.
- Attendance — calendar/grid of daily summaries, late/absent flags, punch detail, corrections, kiosk registration.
- Leave — leave-type config, per-employee balances, record/approve leave, encashment.
- Documents — issue a letter (pick type → fill form → preview → print/PDF), document history.
- Kiosk — a separate full-screen, no-chrome route (
/kiosk) the clinic opens on the reception device: roster tap → PIN → in/out. Visual bar per [[feedback_ui_visual_bar]] — get a real visual pass, not just tsc.
?action=/?tab= URL garbage — use useUrlState/useDeepNav (per
[[project_setsearchparams_clobber_2026_06_10]] and [[feedback_clean_urls_no_trigger_params]]).
8. Cross-cutting concerns
- Migrations: new tables + additive
employees/clinicscolumns via drizzle migration files, idempotent (CREATE TABLE IF NOT EXISTS,ADD COLUMN IF NOT EXISTS) — isolated/enterprise DBs drift behind code (per [[project_dentocorrect_enterprise_2026_06_12]]); introspect before any SQL on a live tenant and never run against prod without per-action confirmation ([[feedback_no_live_tenant_execution]]). - Security: kiosk token hashed at rest; PIN hashed; kiosk endpoints rate-limited (reuse
middleware/rate-limit); IP read fromCF-Connecting-IPonly; punches append-only. - Privacy: no patient PHI involved (staff data only), but HR data is sensitive — scope strictly to clinic context; future selfie blobs get a retention/purge policy when that source lands.
- Money icons: use
Banknote, never DollarSign ([[feedback_no_dollar_icons]]). Currency = PKR. - Superadmin parity ([[feedback_superadmin_ui_parity]]): add a read-only inspect view of a clinic’s HR module usage in the superadmin tenant panel (list employees/attendance counts) — can be a thin Phase-1.5 follow-up, but tracked, not forgotten.
- API docs: update
docs/api-reference.mdwith the new/hr/*endpoints ([[feedback_api_documentation_discipline]]).
9. Testing
- Unit: attendance summary computation (TZ correctness w/
Asia/Karachi), leave balance math (accrual/carry-over/encashment), document merge-field fill. - Integration: kiosk punch rejected when IP not allow-listed / token invalid / PIN wrong; admin permission gating; module-guard off when not Pro+.
- Visual: kiosk screen + each admin tab + at least one rendered PDF eyeballed (per [[feedback_ui_visual_bar]] and [[project_urdu_pdf_rendering]] — render a real PDF, don’t trust tsc).
10. Rollout
- Schema + module key + permissions (with CI parity tripwire green).
- Attendance (kiosk + IP + PIN + summaries) → leave engine → document pack.
- Enable on Pro+ via plan sync; smoke-test on the canonical test tenant ssh & Associates ([[feedback_test_tenant_ssh_associates]]) before any other clinic.
- Commit + deploy via
odontox-commit-deploy; force-promote CF canonical; verify fresh dist hash ([[project_ui_build_stale_dist_2026_06_09]]).
11. Open questions (resolve during planning, sensible defaults chosen)
- Leave-year basis: calendar year (default) vs employee anniversary — default calendar, configurable later.
- Half-day leave granularity: support 0.5-day records in v1 (cheap, expected).
- Kiosk auth lifetime: kiosk token long-lived but revocable; re-auth only if revoked.

