21 · HR, payroll & timeclock

Separate HR database per tenant, staff team management, timeclock/roster, payroll runs, PTO/benefits, and employee self-service portal.

dev-hr-{slug} isolation Feature: staff_hr requireHRAdminRole on sensitive APIs

Staff vs HR employee (don’t collapse them)

Team / StaffHR 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

PathPurpose
/admin/hrHR dashboard
/admin/hr/employees · …/:idEmployee CRUD / detail
/admin/hr/departments · benefitsOrg structure / benefits
/admin/hr/time-off · approvalsPTO self + manager approve
/admin/hr/run-payroll-wizardPayroll run
/admin/hr/business-tax-info · tax-ratesEmployer tax setup
/admin/hr/reportsHR 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.

RBAC Sensitive HR sub-routes use 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

APIPurpose
/api/hr clock-in/out, break start/endHR controller time paths
POST /api/tenant-time/punchToggle punch
GET /api/tenant-time/status · rosterStatus / team on clock
POST …/clock-out-staff/:staffIdManager force clock-out
Timesheet get/approveLegacy + team Timecards UI
Open integrity item clockIn/clockOut can accept a body 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

ConcernPath
HR routerbackend/routes/hr.routes.js + routes/hr/*
Servicesbackend/services/hr/*
Schemabackend/schemas/hr-schema.sql
TimetenantTime.routes.js · time.routes.js
Staffstaff.routes.js · team pages
PortalemployeePortal.routes.js · pages/employee-portal/*
Rolesmiddleware/requireRoles.js requireHRAdminRole
OAO11-HR-AND-PAYROLL.md