# NFC Kiosk — Enrollment UX, PIN fix, Speed, Clock-out Hours **Date:** 2026-05-30 **Module:** `fusion_clock` (NFC tap kiosk at `/fusion_clock/kiosk/nfc`) **Status:** Approved design, ready for implementation plan. ## Context The NFC kiosk (`static/src/js/fusion_clock_nfc_kiosk.js`, an IIFE state machine) handles tap-to-clock on a wall tablet at the entech client. Four issues to address, all driven by real shop-floor use (lines of 10–20 people). **Implementation approach:** extend the existing IIFE in place. A migration to an Odoo 19 `Interaction` (per repo CLAUDE.md guidance) is deliberately out of scope — the file is a large, working state machine on a live client device and the four changes here are surgical; a rewrite would be high-risk for no functional gain. Noted deviation. ## Requirements & Design ### 1. PIN entry: stop the per-digit full re-render **Problem:** in `renderEnroll(phase:"password")`, every numpad press calls `renderEnroll(...)` which rebuilds the whole panel via `stateContainer.innerHTML = ...` and replays the 400ms `nfc-state-in` entrance animation → the screen visibly "refreshes" on each digit (entry is preserved, but it flickers). **Design:** a reusable PIN-pad component that renders the panel **once**, then on digit/backspace mutates only the masked `.pin-display` text node + an in-memory buffer. No `innerHTML` rebuild, no re-animation. Used by both the ⚙ enroll PIN and the new Manager-PIN step (§2). OK/Cancel callbacks are parameters. ### 2. Program a tag from an unknown tap **Problem:** an unknown card tap returns `{error:"card_unknown"}` and shows a red error that auto-dismisses. Programming requires the separate ⚙ flow (enter password → search → **re-tap**). **Design:** the tapped UID is already captured, so program *that* card with no re-tap: 1. Unknown tap → **amber** "This card isn't programmed yet" panel with **"Program this card"** and **"Cancel"** buttons. Auto-cancel to idle after ~8s of inactivity. 2. **"Program this card"** → **Manager PIN** step (reuses §1 component; credential = `fusion_clock.nfc_enroll_password`, currently `1120`; labelled "Manager PIN" in UI). 3. **Employee step**: search-and-pick an existing employee **or** "+ New employee" → enter a name → create a minimal `hr.employee`. 4. **Assign**: bind the captured UID to that employee → success confirmation. - The ⚙ enroll mode stays as a proactive path, reusing the same fixed PIN component. **Backend:** - Reuse `POST /fusion_clock/kiosk/nfc/enroll` (`employee_id`, `card_uid`, `enroll_password`) for the bind. Already manager/Kiosk-Operator + password gated, sudo data ops. - **New endpoint** `POST /fusion_clock/kiosk/nfc/create_employee` (`name`, `enroll_password`): Kiosk-Operator-gated + password-gated; creates `hr.employee` via **sudo** with `name`, `x_fclk_enable_clock=True`, `company_id = request.env.company.id`; returns `{employee_id, employee_name}` (or `{error}`). JS then calls `enroll` with the captured UID. Minimal fields only — department/contract/etc. are completed later in HR. ### 3. Faster clock-in/out ("Fast") **Problem:** result card lingers 3s (errors 4s) and entrance animations are 0.4–0.7s → slow throughput for long lines. **Design (JS timers):** success result display **3000 → 1800 ms**; error **4000 → 3000 ms**. **Design (SCSS durations):** `nfc-state-in` 400→200ms; `nfc-success-burst` 700→350ms; `nfc-avatar-in` 600→300ms. Ambient idle wave/chip loop unchanged (does not gate throughput). `prefers-reduced-motion` fallback preserved. ### 4. Clock-out shows shift hours, clearly **Problem:** clock-out shows `${net_hours_today.toFixed(1)}h today` — mislabelled "today", small, and hidden when it rounds to 0. **Design:** on clock-out always show a prominent **"Worked Xh Ym this shift"** computed from `net_hours_today` (the just-closed attendance's net hours = worked − break). Render h+m; show even when 0 (e.g. "Worked 0h 4m this shift"). Backend already returns the value; this is a JS label/format + SCSS prominence change. Clock-in unchanged. ## Files - `static/src/js/fusion_clock_nfc_kiosk.js` — PIN component; unknown-tap → program flow; create-employee call; result timers; clock-out hours formatting. - `static/src/scss/nfc_kiosk.scss` — animation durations; amber "unknown card" panel + create-employee styles; prominent clock-out hours. - `controllers/clock_nfc_kiosk.py` — new `nfc_create_employee` endpoint. - `__manifest__.py` — version bump (assets changed). ## Out of scope / non-goals - No migration of the kiosk JS to an `Interaction`. - No new employee fields beyond name/clock-enabled/company at kiosk-create time. - Classic PIN kiosk (`/fusion_clock/kiosk`) untouched (disabled at entech). ## Test / verify - Local: `pyflakes` the controller; `xmllint`/manifest parse; review the JS by hand (no local Odoo container available this session). - entech: deploy, upgrade, then on the tablet — PIN entry no longer flickers; unknown tap → program (existing + new employee) binds without re-tap; clock-in/out visibly faster; clock-out shows "Worked Xh Ym this shift".