feat(fusion_clock): PIN kiosk — polished photo-tile + PIN clock (opt-in)

A proper shared-device PIN kiosk for clients who don't want NFC: photo-tile grid
(+search) -> tap -> PIN (or first-use create) -> optional master-gated selfie ->
clock, in the NFC kiosk's dark glass + brand-gradient style. Built as an Odoo 19
Interaction; new pin_kiosk.scss (scoped); reworked clock_kiosk.py
(search +avatar/has_pin, verify_pin needs_setup, set_pin, clock via kiosk location).
Drops the redundant kiosk_pin_required (PIN always required); relabels the company
kiosk location; adds a PIN-kiosk app icon. Opt-in via enable_kiosk (off by default).
HttpCase tests added. Bump 19.0.4.0.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-31 21:25:32 -04:00
parent b61e159e6f
commit a5ec79013a
13 changed files with 672 additions and 393 deletions

View File

@@ -24,6 +24,20 @@
sequence="46"
groups="group_fusion_clock_kiosk_app"/>
<!-- PIN kiosk app icon (opt-in alternative to NFC: tap photo + PIN). -->
<record id="action_fusion_clock_kiosk_pin" model="ir.actions.act_url">
<field name="name">Fusion Clock PIN Kiosk</field>
<field name="url">/fusion_clock/kiosk</field>
<field name="target">self</field>
</record>
<menuitem id="menu_fusion_clock_kiosk_pin_app_root"
name="Fusion Clock PIN Kiosk"
web_icon="fusion_clock,static/description/icon.png"
action="action_fusion_clock_kiosk_pin"
sequence="47"
groups="group_fusion_clock_kiosk_app"/>
<!-- Dashboard — layered & role-aware: personal band for everyone,
team band for leads (direct reports), org band for managers.
Gated to the base clock-user group (lead/manager imply it). -->

View File

@@ -1,61 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Kiosk Page Template -->
<template id="kiosk_page" name="Fusion Clock Kiosk">
<template id="kiosk_page" name="Fusion Clock PIN Kiosk">
<t t-call="web.frontend_layout">
<t t-set="no_header" t-value="True"/>
<t t-set="no_footer" t-value="True"/>
<div id="fclk-kiosk" class="container-fluid vh-100 d-flex flex-column align-items-center justify-content-center"
style="background: var(--o-main-bg-color, #f8f9fa);"
t-att-data-pin-required="'true' if pin_required else 'false'">
<t t-set="head">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<!-- Kiosk lockdown: hide Odoo's frontend->backend nav so a kiosk
user can't reach the backend. Page-scoped. -->
<style>.o_frontend_to_backend_nav { display: none !important; }</style>
</t>
<!-- Header -->
<div class="text-center mb-4">
<h1 style="font-size: 2.5rem;">Fusion Clock</h1>
<p class="text-muted" style="font-size: 1.2rem;">Kiosk Mode</p>
<div id="fclk-kiosk-time" style="font-size: 3rem; font-weight: 300;"></div>
</div>
<div id="pin_kiosk_root" class="pin-kiosk"
t-att-data-location="location_name"
t-att-data-sounds="'1' if sounds_enabled else '0'"
t-att-data-photo="'1' if photo_required else '0'">
<!-- Search / PIN Entry -->
<div class="card shadow-sm" style="width: 100%; max-width: 500px;">
<div class="card-body p-4">
<img t-if="company_logo_url"
id="pin_kiosk_logo" class="pin-kiosk__logo"
t-att-src="company_logo_url" crossorigin="anonymous" alt="Company logo"/>
<!-- Step 1: Search Employee -->
<div id="fclk-kiosk-search">
<label class="form-label fw-bold">Employee Name</label>
<input type="text" id="fclk-kiosk-query" class="form-control form-control-lg mb-3"
placeholder="Type your name..." autocomplete="off"/>
<div id="fclk-kiosk-results" class="list-group mb-3" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<div class="pin-kiosk__clock" id="pin_kiosk_clock">--:--</div>
<div class="pin-kiosk__date" id="pin_kiosk_date"></div>
<!-- Step 2: PIN (if required) -->
<div id="fclk-kiosk-pin" style="display: none;">
<h4 id="fclk-kiosk-emp-name" class="text-center mb-3"></h4>
<t t-if="pin_required">
<label class="form-label fw-bold">Enter PIN</label>
<input type="password" id="fclk-kiosk-pin-input" class="form-control form-control-lg text-center mb-3"
maxlength="6" placeholder="------"/>
</t>
<div class="d-grid gap-2">
<button id="fclk-kiosk-clock-btn" class="btn btn-lg btn-primary">
Clock In / Out
</button>
<button id="fclk-kiosk-back-btn" class="btn btn-outline-secondary">
Back
</button>
</div>
</div>
<input type="text" class="pin-kiosk__search" id="pin_kiosk_search"
placeholder="Search your name…" autocomplete="off"/>
<div class="pin-kiosk__grid" id="pin_kiosk_grid"></div>
<!-- Step 3: Result -->
<div id="fclk-kiosk-result" style="display: none;">
<div id="fclk-kiosk-result-msg" class="text-center py-4" style="font-size: 1.3rem;"></div>
</div>
<div class="pin-kiosk__location" t-esc="location_name"/>
<div class="pin-kiosk__settings" id="pin_kiosk_settings" title="Settings"></div>
<!-- Error message -->
<div id="fclk-kiosk-error" class="alert alert-danger mt-3" style="display: none;"></div>
</div>
</div>
<!-- JS swaps overlays (PIN pad / setup / photo / result) in here -->
<div id="pin_state_container"></div>
</div>
</t>
</template>

View File

@@ -163,12 +163,6 @@
<setting id="fclk_kiosk" string="Kiosk Mode"
help="Allow employees to clock in/out from a shared device (tablet or computer).">
<field name="fclk_enable_kiosk"/>
<div class="content-group" invisible="not fclk_enable_kiosk">
<div class="row mt16">
<field name="fclk_kiosk_pin_required"/>
<label for="fclk_kiosk_pin_required" class="ms-1"/>
</div>
</div>
</setting>
<setting id="fclk_corrections" string="Correction Requests"
help="Allow employees to request timesheet corrections from the portal.">