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_requiredtoggle. - 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
- 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). - Tap a tile → PIN pad: glass panel with the person's avatar + name, masked PIN dots, a big touch numpad (
⌫ 0 ✓), Cancel.- If the employee has no PIN → first-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).
- Selfie (only if
enable_photo_verificationmaster is ON) → guided capture with the oval face-guide + countdown (reuse the NFC kiosk's capture). - 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_verificationmaster). Redirect to/myifenable_kioskoff or not an operator (as today).POST /fusion_clock/kiosk/search(extend the existing — keep the name; the NFC kiosk'semployee_searchdelegates to it) — addavatar_url(viahr.employee.public,?unique=write_datecache-buster) andhas_pin(bool) to each row, alongside the currentid/name/department/is_checked_in/card_uid.POST /fusion_clock/kiosk/verify_pin(rework) — ifnot 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 writex_fclk_kiosk_pin(sudo) and return success.POST /fusion_clock/kiosk/clock(rework) — acceptphoto_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); returnno_location_configuredif unset. Clock via_attendance_action_change; write source'kiosk', location, logs, penalties (as today). Ifenable_photo_verificationmaster ON andphoto_b64present → store onx_fclk_check_in_photo(in) /x_fclk_check_out_photo(out), stripping the data-URL prefix. Unscheduled-day →unscheduled_shiftlog (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+ amenuitemgated togroup_fusion_clock_kiosk_app(parallel to the NFC app icon). Clients enableenable_kioskand point the tablet at it. - Settings:
enable_kioskis the on/off (kept). Removekiosk_pin_required(field + view row + seed +_FCLK_BOOL_PARAMSentry) — PIN is always required. The kiosk location reusesres.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_searchworking — the NFC kiosk'semployee_searchdelegates to it (clock_nfc_kiosk.py:409). - Reuse (replicate) the NFC kiosk's hue extraction (
extractDominantHue/applyBrandHue) and photo capture. - Reuse
hr.employee.publicfor avatars (kiosk operator can't readhr.employeeimages — 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 configured →
no_location_configuredmessage. - Photo master OFF → skip the selfie step entirely; clock directly.
- Unscheduled day →
unscheduled_shiftactivity log (parity with NFC). - Double-tap → debounced.
6. Testing (tests/test_clock_kiosk.py, @tagged('-at_install','post_install','fusion_clock'))
searchreturnsavatar_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 andphoto_b64present, not when OFF;no_location_configuredwhen unset.- Page redirects when
enable_kioskoff / 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(dropkiosk_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(registerpin_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.