diff --git a/fusion_clock/models/res_config_settings.py b/fusion_clock/models/res_config_settings.py
index c897c2f6..3518f0da 100644
--- a/fusion_clock/models/res_config_settings.py
+++ b/fusion_clock/models/res_config_settings.py
@@ -253,11 +253,13 @@ class ResConfigSettings(models.TransientModel):
"Leave empty to fall back to manager-group membership only.",
)
fclk_nfc_kiosk_debug = fields.Boolean(
- string='Enable Mock-Tap Debug',
+ string='Debug Mode (overlay + mock-tap)',
config_parameter='fusion_clock.nfc_kiosk_debug',
default=False,
- help="Enables a Ctrl+Shift+T keyboard shortcut on the kiosk page for "
- "simulating a tap with a configurable UID. Off in production.",
+ help="Enables two dev/troubleshooting features on the NFC kiosk page: "
+ "(1) a green-text debug overlay at the top of the screen logging every NFC and tap event in real time, "
+ "and (2) a Ctrl+Shift+T keyboard shortcut that simulates a tap with a configurable UID. "
+ "Turn OFF in production — the overlay is intrusive for end users.",
)
fclk_nfc_kiosk_location_id = fields.Many2one(
related='company_id.x_fclk_nfc_kiosk_location_id',
diff --git a/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js b/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js
index 4a482a56..2b7cda12 100644
--- a/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js
+++ b/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js
@@ -16,6 +16,29 @@
const debugEnabled = root.dataset.debugEnabled === "1";
const locationConfigured = root.dataset.locationConfigured === "1";
+ // ──────────────────────────────────────────────────────────────
+ // Debug overlay (visible only when fusion_clock.nfc_kiosk_debug = True)
+ // ──────────────────────────────────────────────────────────────
+ let _debugOverlayEl = null;
+ function debugLog(msg) {
+ try { console.log("[nfc-kiosk-debug]", msg); } catch (e) {}
+ if (!debugEnabled) return;
+ if (!_debugOverlayEl) {
+ _debugOverlayEl = document.createElement("div");
+ _debugOverlayEl.style.cssText = "position:fixed;top:0;left:0;right:0;background:rgba(0,0,0,0.9);color:#0f0;font-family:monospace;font-size:11px;padding:0.5rem;max-height:35vh;overflow-y:auto;z-index:9999;line-height:1.3;border-bottom:1px solid #0f0;";
+ document.body.appendChild(_debugOverlayEl);
+ }
+ const line = document.createElement("div");
+ const ts = new Date().toLocaleTimeString();
+ line.textContent = "[" + ts + "] " + msg;
+ _debugOverlayEl.appendChild(line);
+ while (_debugOverlayEl.childNodes.length > 40) {
+ _debugOverlayEl.removeChild(_debugOverlayEl.firstChild);
+ }
+ _debugOverlayEl.scrollTop = _debugOverlayEl.scrollHeight;
+ }
+ debugLog("page loaded; debugEnabled=" + debugEnabled + " photoRequired=" + photoRequired + " NDEFReader=" + ("NDEFReader" in window));
+
// ──────────────────────────────────────────────────────────────
// State machine
// ──────────────────────────────────────────────────────────────
@@ -281,49 +304,68 @@
let nfcReady = false;
async function startNfcReader() {
+ debugLog("startNfcReader: NDEFReader in window = " + ("NDEFReader" in window));
if (!("NDEFReader" in window)) {
throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android.");
}
ndefReader = new NDEFReader();
+ debugLog("startNfcReader: ndefReader created, calling scan()...");
await ndefReader.scan();
+ debugLog("startNfcReader: scan() resolved ✓");
ndefReader.addEventListener("reading", onNfcReading);
- ndefReader.addEventListener("readingerror", () => {
+ ndefReader.addEventListener("readingerror", (ev) => {
+ debugLog("readingerror event fired");
console.warn("[nfc-kiosk] reading error; reader still active");
});
nfcReady = true;
+ debugLog("startNfcReader: listeners attached, nfcReady=true");
}
function onNfcReading(event) {
// event.serialNumber is the card UID — works for raw MIFARE access cards
- const uid = (event.serialNumber || "").toUpperCase();
- if (!uid) return;
+ const rawSerial = event.serialNumber || "";
+ const uid = rawSerial.toUpperCase();
+ const recCount = (event.message && event.message.records) ? event.message.records.length : 0;
+ debugLog("reading event: serialNumber=" + JSON.stringify(rawSerial) + " (len=" + rawSerial.length + ") records=" + recCount + " state=" + currentState);
+ if (!uid) {
+ debugLog(" → IGNORED: empty serialNumber");
+ return;
+ }
if (currentState === STATE.ENROLL) {
- // Enroll Mode handles taps differently (wired up in Task 18)
+ debugLog(" → routing to _onEnrollTap");
window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid);
return;
}
- if (currentState !== STATE.IDLE) return; // ignore taps mid-result
+ if (currentState !== STATE.IDLE) {
+ debugLog(" → IGNORED: not in IDLE (state=" + currentState + ")");
+ return;
+ }
+ debugLog(" → calling handleTap(" + uid + ")");
handleTap(uid);
}
async function handleTap(uid) {
+ debugLog("handleTap: uid=" + uid);
setState(STATE.PROCESSING);
let photoB64 = "";
try {
photoB64 = await capturePhoto();
+ debugLog("handleTap: photo captured, size=" + photoB64.length);
} catch (e) {
+ debugLog("handleTap: photo capture failed: " + e.message);
console.warn("[nfc-kiosk] camera capture failed", e);
- // Server enforces photo_required if needed
}
try {
+ debugLog("handleTap: POST /fusion_clock/kiosk/nfc/tap...");
const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 });
+ debugLog("handleTap: response = " + JSON.stringify(result).slice(0, 200));
if (result.error === "debounce") {
- // silent — back to IDLE
setState(STATE.IDLE);
return;
}
setState(STATE.RESULT, result);
} catch (e) {
+ debugLog("handleTap: POST failed: " + e.message);
setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." });
}
}
@@ -374,11 +416,15 @@
const setupBtn = document.getElementById("nfc_setup_start");
if (setupBtn) {
setupBtn.addEventListener("click", async () => {
+ debugLog("setup button clicked");
try {
await startNfcReader();
+ debugLog("setup: NFC ready, starting camera...");
try {
await startCamera();
+ debugLog("setup: camera ready ✓");
} catch (camErr) {
+ debugLog("setup: camera failed: " + camErr.message);
if (photoRequired) throw camErr;
console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr);
}
diff --git a/fusion_clock/views/res_config_settings_views.xml b/fusion_clock/views/res_config_settings_views.xml
index 1b10c5ed..122a587c 100644
--- a/fusion_clock/views/res_config_settings_views.xml
+++ b/fusion_clock/views/res_config_settings_views.xml
@@ -263,7 +263,7 @@