docs(fusion_clock): PIN kiosk design spec (photo-tile + PIN, NFC-matching style)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# 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 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).
|
||||
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 configured** → `no_location_configured` message.
|
||||
- **Photo master OFF** → skip the selfie step entirely; clock directly.
|
||||
- **Unscheduled day** → `unscheduled_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.
|
||||
Reference in New Issue
Block a user