Files
Odoo-Modules/fusion_clock/docs/superpowers/specs/2026-05-31-pin-kiosk-design.md
2026-05-31 20:56:21 -04:00

9.2 KiB

Fusion Clock — PIN Kiosk Design

Date: 2026-05-31 Module: fusion_clock Status: Approved (brainstorming) — ready for implementation plan


1. Problem / goal

The module has a premium NFC kiosk but only a bare-Bootstrap classic PIN kiosk (/fusion_clock/kiosk) with no logo, gradient, or polish, reachable only by direct URL. We want a properly designed, opt-in PIN kiosk as an additional feature for clients who want simple PIN entry instead of NFC — matching the NFC kiosk's look and quality. This makes the existing enable_kiosk setting meaningful (it becomes this feature's on/off).

2. Decisions (from brainstorming)

  • Flow: photo-tile grid (with search) → tap your tile → enter PIN → clock. (Not PIN-as-identifier; not name-search.)
  • PIN always required. Drop the kiosk_pin_required toggle.
  • Set PIN on first use: an employee with no PIN is walked through creating + confirming one on first tap.
  • Selfie respects the global Photo Verification master toggle (enable_photo_verification): ON → guided selfie after PIN; OFF → none.
  • Style: always-dark glass + brand-hue mesh gradient, logo pill, live clock/date — matching the NFC kiosk.

3. Design

3.1 Screens / flow

  1. Grid (#pin_kiosk_root): company logo pill (top-centre), live clock + date, a search box, and a responsive grid of employee photo tiles (avatar + name) over the animated brand-tinted mesh gradient. Bottom chrome: location pill (left), operator ⚙ + lock (right).
  2. Tap a tilePIN pad: glass panel with the person's avatar + name, masked PIN dots, a big touch numpad (⌫ 0 ✓), Cancel.
    • If the employee has no PINfirst-use setup: "Create a PIN" → enter → "Re-enter to confirm" → saved, then proceed to clock.
    • Wrong PIN → shake + clear + retry (max 3 attempts, then back to grid).
  3. Selfie (only if enable_photo_verification master is ON) → guided capture with the oval face-guide + countdown (reuse the NFC kiosk's capture).
  4. Result: green-glow success card (avatar, name, "Clocked In/Out", time · location), auto-returns to the grid after ~3 s. Error → red shake card.

3.2 Style

New static/src/scss/pin_kiosk.scss, scoped to :has(#pin_kiosk_root) (never leaks to other pages — same discipline as nfc_kiosk.scss). Mirrors the NFC kiosk's tokens/patterns: dark page, animated mesh ::before, vignette ::after, frosted logo pill, clock/date, %glass panels, numpad, result card (success/error), photo panel + oval guide, reduced-motion fallback. Brand hue in its own CSS var --pk-h (don't collide with --nfc-h); deliberately a parallel file, not shared, to avoid coupling the two kiosks.

3.3 Backend — rework controllers/clock_kiosk.py

All routes auth='user', gated by _is_kiosk_operator (Clock Manager or Kiosk Operator group — unchanged). Page additionally gated by enable_kiosk.

  • GET /fusion_clock/kiosk — render the new template. Context: company_name, company_logo_url (for display + hue extraction), location_name, sounds_enabled, photo_required (= enable_photo_verification master). Redirect to /my if enable_kiosk off or not an operator (as today).
  • POST /fusion_clock/kiosk/search (extend the existing — keep the name; the NFC kiosk's employee_search delegates to it) — add avatar_url (via hr.employee.public, ?unique=write_date cache-buster) and has_pin (bool) to each row, alongside the current id/name/department/is_checked_in/card_uid.
  • POST /fusion_clock/kiosk/verify_pin (rework) — if not employee.x_fclk_kiosk_pin{'needs_setup': True}; else compare and return {'success': True, ...} or {'error': 'invalid_pin'}.
  • POST /fusion_clock/kiosk/set_pin (NEW) — first-use: validate a 4-digit numeric PIN, reject if the employee already has one (already_set), else write x_fclk_kiosk_pin (sudo) and return success.
  • POST /fusion_clock/kiosk/clock (rework) — accept photo_b64. Use the configured kiosk location (company.x_fclk_nfc_kiosk_location_id) — a fixed wall device, so NO per-clock GPS geofence (matches the NFC kiosk); return no_location_configured if unset. Clock via _attendance_action_change; write source 'kiosk', location, logs, penalties (as today). If enable_photo_verification master ON and photo_b64 present → store on x_fclk_check_in_photo (in) / x_fclk_check_out_photo (out), stripping the data-URL prefix. Unscheduled-day → unscheduled_shift log (as today). Module-level tap debounce (~5 s, like NFC).

3.4 Frontend — rewrite static/src/js/fusion_clock_kiosk.js

Rebuild as a proper Odoo-19 Interaction (@web/public/interaction, registered in registry.category("public.interactions")) — not the old IIFE. State machine: grid → pin | setup → (photo) → result → grid. Reuse the NFC kiosk's dominant-hue extraction (set --pk-h from the logo) and guided photo capture (camera + oval guide + countdown) — replicate those helpers cleanly in this file (parallel, not imported, to keep the two kiosks decoupled). Search filters the tile grid client-side; tile tap loads the PIN/setup panel; numpad drives the dots; success plays a sound if sounds_enabled.

3.5 Template — rebuild views/kiosk_templates.xml

Root <div id="pin_kiosk_root" class="pin-kiosk"> inside web.frontend_layout (no header/footer), carrying data-* for company_logo_url, location_name, sounds_enabled, photo_required. Contains the logo pill, clock/date, search, a grid container, and a JS-driven #pin_state_container (mirroring the NFC #nfc_state_container).

3.6 Access, settings, menu

  • Operator account: runs as the shared Kiosk Operator (group_fusion_clock_kiosk_app) or a manager — same as NFC. Screen lock + operator ⚙ for parity.
  • App icon: add a "Fusion Clock PIN Kiosk" ir.actions.act_url/fusion_clock/kiosk + a menuitem gated to group_fusion_clock_kiosk_app (parallel to the NFC app icon). Clients enable enable_kiosk and point the tablet at it.
  • Settings: enable_kiosk is the on/off (kept). Remove kiosk_pin_required (field + view row + seed + _FCLK_BOOL_PARAMS entry) — PIN is always required. The kiosk location reuses res.company.x_fclk_nfc_kiosk_location_id; relabel its field string to "Kiosk Location" (it now serves NFC + PIN) and note in help it applies to both.
  • PIN storage: existing hr.employee.x_fclk_kiosk_pin (Char, 4-digit, manager-editable, server-verified). Kept plaintext to match the existing field and keep it simple; hashing noted as optional future hardening (it's a low-stakes attribution PIN, manager-only field, never sent to other clients).

4. Reuse / dependencies

  • Keep FusionClockKiosk.kiosk_search working — the NFC kiosk's employee_search delegates to it (clock_nfc_kiosk.py:409).
  • Reuse (replicate) the NFC kiosk's hue extraction (extractDominantHue/applyBrandHue) and photo capture.
  • Reuse hr.employee.public for avatars (kiosk operator can't read hr.employee images — established for the NFC kiosk).
  • Reuse the master photo gate added in 19.0.3.16.1.

5. Edge cases

  • No PIN → first-use setup (enter + confirm; set_pin).
  • Wrong PIN → shake + retry, max 3 → back to grid.
  • Employee not clock-enabled → not listed; defensive re-check in clock.
  • No kiosk location configuredno_location_configured message.
  • Photo master OFF → skip the selfie step entirely; clock directly.
  • Unscheduled dayunscheduled_shift activity log (parity with NFC).
  • Double-tap → debounced.

6. Testing (tests/test_clock_kiosk.py, @tagged('-at_install','post_install','fusion_clock'))

  • search returns avatar_url + has_pin; only clock-enabled employees.
  • verify_pin: correct PIN → success; wrong → invalid_pin; no PIN → needs_setup.
  • set_pin: sets a 4-digit PIN; rejects non-numeric / wrong length; rejects when one already exists (already_set).
  • clock: clock-in then clock-out (source 'kiosk', kiosk location); photo stored only when master ON and photo_b64 present, not when OFF; no_location_configured when unset.
  • Page redirects when enable_kiosk off / non-operator.

7. Out of scope (YAGNI)

Offline mode; switching locations on one device; PIN hashing (noted as future); removing or changing the NFC kiosk; multi-company kiosk selection.

8. Files touched

  • Modify: controllers/clock_kiosk.py, views/kiosk_templates.xml, static/src/js/fusion_clock_kiosk.js, models/res_config_settings.py (drop kiosk_pin_required), views/res_config_settings_views.xml (drop row, relabel location), data/ir_config_parameter_data.xml (drop seed), models/res_company.py (relabel field string), views/clock_menus.xml (PIN kiosk app icon), __manifest__.py (register pin_kiosk.scss + version bump), fusion_clock/CLAUDE.md (kiosk section + settings keys).
  • Create: static/src/scss/pin_kiosk.scss, tests/test_clock_kiosk.py.

9. Deployment

Local test on the dev container, then the standard entech path: bump version, git commit --only explicit paths, push origin + gitea, upgrade entech, verify web 200 + version + (read-only) the page renders for the operator. Bump version so the new SCSS/JS bundle rebuilds; hard-refresh the tablet.