Skip to main content

Staff

Anyone who signs into Helm to operate the shop — owner, manager, sales staff, mechanic, junior. Each staff member has identity, a role, and per-staff permission overrides.

Drafted from planning · v0.1

Table: staff

ColumnTypeNotes
idINTEGER PK
first_name, last_nameTEXTPII
emailTEXTPII
phoneTEXTPII
role_idINTEGER FK → rolesDetermines default permissions
pin_hashTEXTPBKDF2-SHA256, base64; see ADR-0013
pin_saltTEXTbase64
pin_set_atTEXTTimestamp of last PIN change
lockout_untilTEXTNULL when not locked; set on 5 wrong attempts
failed_attemptsINTEGERResets on success or expiry of lockout_until
last_sign_in_atTEXT
is_activeINTEGER (0/1)Soft-delete; inactive staff cannot sign in
wage_cents_per_hourINTEGERFor cost-of-service reporting; not payroll
created_at, updated_atTEXT
  • roles — Sys Admin, Owner, Sales, Mechanic, Junior, Service Lead, etc.
  • role_permissions(role_id, screen_id, can_see) defaults
  • staff_screen_permissions(staff_id, screen_id, can_see) per-staff overrides; wins over role
  • staff_sessions — active sign-in tokens; one row per active session

See ADR-0014: per-staff permission overrides.

Behaviors

Sign-in

POST /api/auth/login:

  • Body: { pin }
  • Hashes input PIN with each staff's salt, compares to pin_hash
  • On match: creates staff_sessions row, sets helm_session cookie
  • On miss: increments failed_attempts; if ≥ 5 in 60s, sets lockout_until = now+5min

The admin reset code (466687) is a special case — it signs in as the Sys Admin staff row directly (without per-staff PIN match). See security model.

PIN management

PUT /api/staff/{id}/pin:

  • Body: { new_pin } (5 digits)
  • Generates new salt, hashes, updates pin_hash + pin_salt + pin_set_at
  • Audit-logged (the new PIN value is never logged)

DELETE /api/staff/{id}/pin:

  • Clears pin_hash + pin_salt
  • Staff cannot sign in until PIN is set again
  • Audit-logged

Lockout reset

The admin reset code unlocks any locked staff (clears lockout_until and failed_attempts). Also accessible via the PIN-reset modal at the sign-in overlay.

Edit

PUT /api/staff/{id} for name, email, phone, role, wage, is_active. Editable in-situ on the staff card in Settings → Staff & Permissions.

Permission resolution

For a given staff + screen, the resolved permission is:

COALESCE(
(SELECT can_see FROM staff_screen_permissions WHERE staff_id=? AND screen_id=?),
(SELECT can_see FROM role_permissions WHERE role_id=staff.role_id AND screen_id=?)
)

The Staff & Permissions UI shows a checkbox per screen per staff; toggling writes to staff_screen_permissions (per-staff), not role_permissions.

A "Reset to role defaults" button clears all override rows for that staff.

Idle timeout

The operator app tracks pointer/key events. After 60 seconds idle (configurable in shop_config.idle_timeout_seconds), the sign-in overlay re-appears. The current page state is preserved; signing back in restores it.

In-situ editing surface

On the Staff & Permissions card in Settings:

  • Each staff row: click name to edit; role dropdown; per-screen visibility checkboxes; Select/Deselect All
  • PIN management: a "Set PIN" / "Clear PIN" small button
  • "Reset to role defaults" button per staff
    • Add staff opens the new-staff form

Migrated from AIM

For Swicked: 9 staff records from AIM's stf table:

  • stf.stf_pk → internal mapping for staff.id assignment
  • stf.fname/lnamestaff.first_name/last_name
  • AIM's role concept (string) → mapped to Helm role IDs
  • PINs are NOT migrated; each staff member sets a fresh PIN on first sign-in (a Sys Admin sets initial PINs via the staff card)

See also