From d8456fb9a35af7a92ffe01f725f814ca4271995c Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 24 May 2026 13:17:53 -0400 Subject: [PATCH] feat(shopfloor): tablet_lock branches on tablet_session_mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ir.config_parameter[fp.shopfloor.tablet_session_mode]='session_swap', PIN submit calls /fp/tablet/unlock_session and reloads the page; the new session manager service kicks in on next mount. handOff() calls lockBack('manual') which destroys the tech session server-side and re-auths as kiosk. Legacy mode unchanged — same /fp/tablet/unlock + techStore flow. The feature flag, kiosk_uid, and current_uid arrive via the existing /fp/tablet/tiles bootstrap response (Task D0). Adds a tablet_lock-owned Hand-Off button visible only in session_swap mode (in legacy mode wrapper components own their own buttons that hit techStore.lock(); session_swap renders our own button so the manual hand-off goes through lockBack() + page reload). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../static/src/js/tablet_lock.js | 55 +++++++++++++++++++ .../static/src/xml/tablet_lock.xml | 14 +++++ 2 files changed, 69 insertions(+) diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js index ea6287bb..4909bc2a 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/tablet_lock.js @@ -34,6 +34,9 @@ export class FpTabletLock extends Component { this.techStore = useService("fp_shopfloor_tech_store"); this.activity = useService("fp_shopfloor_activity"); this.notification = useService("notification"); + // Phase D: idle + ceiling timer for session_swap mode. Started + // once tiles bootstrap shows we're already on a tech session. + this.tabletSessionManager = useService("fp_tablet_session_manager"); this.state = useState({ tiles: [], @@ -46,6 +49,10 @@ export class FpTabletLock extends Component { clockText: this._formatTime(new Date()), dateText: this._formatDate(new Date()), company: null, + // Phase D — feature flag + kiosk identity from bootstrap + sessionMode: "legacy", // 'legacy' or 'session_swap' + kioskUid: null, + currentUid: null, }); onMounted(async () => { @@ -65,16 +72,33 @@ export class FpTabletLock extends Component { this.state.clockText = this._formatTime(now); this.state.dateText = this._formatDate(now); }, 60000); + // Session-swap mode: if we're already on a TECH session (uid + // != kiosk), start the idle/ceiling timer immediately. This + // handles the case where the page was reloaded after + // unlock_session minted the tech's session. + if (this.state.sessionMode === "session_swap" + && this.state.currentUid + && this.state.currentUid !== this.state.kioskUid) { + this.tabletSessionManager.beginSession(); + } }); onWillUnmount(() => { if (this._tick) clearInterval(this._tick); if (this._ping) clearInterval(this._ping); if (this._clockInterval) clearInterval(this._clockInterval); + this.tabletSessionManager.endSession(); }); } get isLocked() { + // SESSION-SWAP MODE: the BROWSER session itself tells us whether + // a tech is unlocked — current_uid != kiosk_uid means unlocked. + // LEGACY MODE: defer to the techStore client-side flag. + if (this.state.sessionMode === "session_swap") { + return !this.state.currentUid + || this.state.currentUid === this.state.kioskUid; + } return this.techStore.isLocked; } @@ -85,6 +109,11 @@ export class FpTabletLock extends Component { const res = await rpc("/fp/tablet/tiles", { station_id: stationId }); if (res && res.ok) { this.state.company = res.company || null; + // Phase D — capture session_mode + kiosk/current uids so + // unlock() / isLocked / handOff can branch on mode. + this.state.sessionMode = res.tablet_session_mode || "legacy"; + this.state.kioskUid = res.kiosk_uid || null; + this.state.currentUid = res.current_uid || null; // Decorate each tile with an animation-delay (50ms staggered, // capped at 300ms so the screen doesn't take 3s to settle on // shops with 20+ operators). @@ -127,6 +156,25 @@ export class FpTabletLock extends Component { async unlock(pin) { try { + // SESSION-SWAP MODE: call the new endpoint, then reload the + // page so the browser re-bootstraps under the tech's session. + if (this.state.sessionMode === "session_swap") { + const res = await rpc("/fp/tablet/unlock_session", { + user_id: this.state.selectedTileUserId, + pin, + }); + if (res && res.ok) { + // Cookie has swapped. Reload so OWL/services re-init + // under the new (tech) session. The session manager + // (Task D1) picks up on the next page load. + window.location.reload(); + // Return a pending state so the caller doesn't try to + // navigate while we're tearing down. + return { ok: true, reloading: true }; + } + return { ok: false, error: (res && res.error) || "Unlock failed" }; + } + // LEGACY MODE: existing /fp/tablet/unlock path const res = await rpc("/fp/tablet/unlock", { user_id: this.state.selectedTileUserId, pin, @@ -148,6 +196,13 @@ export class FpTabletLock extends Component { } handOff() { + // SESSION-SWAP MODE: the server destroys the tech session, then + // we reload to re-bootstrap as the kiosk. + if (this.state.sessionMode === "session_swap") { + this.tabletSessionManager.lockBack("manual"); + return; + } + // LEGACY MODE: client-side state flip only. this.techStore.lock(); this.state.selectedTileUserId = null; this.state.idleSecondsRemaining = null; diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml index e9626953..12d3c549 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/tablet_lock.xml @@ -75,6 +75,20 @@ + +