21 · HR, payroll & timeclock
Separate HR database per tenant, staff team management, timeclock/roster, payroll runs, PTO/benefits, and employee self-service portal.
Staff vs HR employee (don’t collapse them)
| Team / Staff | HR employees | |
|---|---|---|
| Job | Who can log into POS/admin, PIN, permissions | Employment record, W4/I9, pay, PTO, benefits |
| UI | /admin/team/* |
/admin/hr/* |
| API | /api/staff, permissions, personal |
/api/hr/*, payroll, tax |
| DB | Often tenant main Staff tables | Tenant HR DB dev-hr-{slug} |
Same human may exist in both; linking quality varies. Timeclock can use staffId (shared terminal pattern).
Data plane
flowchart TB MAIN[(tenant main
Staff · timesheets ops)] HR[(dev-hr-slug
employees · payroll · PTO)] MASTER[(master
tenant_employees routing)] PORTAL[Employee portal login] MASTER --> PORTAL PORTAL --> HR ADMIN[HR admin UI] --> HR CLOCK[Timeclock / tenant-time] --> MAIN
HR schema examples: employees, employee_w4, compensation, departments, benefit_plans, pay_periods, payroll_runs, payroll_line_items, time_off_requests, pto_balances, business_tax_info — see backend/schemas/hr-schema.sql.
Admin UI & API
UI paths
| Path | Purpose |
|---|---|
/admin/hr | HR dashboard |
/admin/hr/employees · …/:id | Employee CRUD / detail |
/admin/hr/departments · benefits | Org structure / benefits |
/admin/hr/time-off · approvals | PTO self + manager approve |
/admin/hr/run-payroll-wizard | Payroll run |
/admin/hr/business-tax-info · tax-rates | Employer tax setup |
/admin/hr/reports | HR reports |
/admin/team/* | Team members, permission sets, timecards, payroll settings shortcuts |
API mount /api/hr
Session + tenant + checkFeatureAccess('staff_hr'). Sub-routers include employees, business-tax-info, tax-rates, payroll calc/runs, departments, benefits, time-off, reports, security.
requireHRAdminRole (admin/owner/manager/master_admin).
Time-off self-service stays open for the caller’s own requests; approve/reject gated.
Benefits were locked after IDOR findings (audit).
Also: /api/payroll, /api/time, /api/team-hours, /api/staff-personal.
Timeclock & roster
| API | Purpose |
|---|---|
/api/hr clock-in/out, break start/end | HR controller time paths |
POST /api/tenant-time/punch | Toggle punch |
GET /api/tenant-time/status · roster | Status / team on clock |
POST …/clock-out-staff/:staffId | Manager force clock-out |
| Timesheet get/approve | Legacy + team Timecards UI |
staffId without proving it matches the caller —
intentional for shared terminal, but enables “clock in as someone else” if unguarded.
Trace real UI/device flow before changing (audit residual).
Employee self-service portal
- URLs:
/employee-portal,/employee-portal/:tenantSlug, login variants - Routing: email → master
tenant_employees→ correct tenant HR DB - Service:
tenantEmployeeRouting.service.js·employeePortal.service.js
Distinct from staff POS login and from customer portal (/customer).
Gaps / notes
- Legacy inline HR routes still coexist with newer
hr/*sub-routers (parallel debt) - Full payroll compliance for real production runs needs careful re-verify per jurisdiction
- Team vs HR employee dual models can confuse onboarding
File anchors
| Concern | Path |
|---|---|
| HR router | backend/routes/hr.routes.js + routes/hr/* |
| Services | backend/services/hr/* |
| Schema | backend/schemas/hr-schema.sql |
| Time | tenantTime.routes.js · time.routes.js |
| Staff | staff.routes.js · team pages |
| Portal | employeePortal.routes.js · pages/employee-portal/* |
| Roles | middleware/requireRoles.js requireHRAdminRole |
| OAO | 11-HR-AND-PAYROLL.md |