diff --git a/fusion_plating/fusion_plating_shopfloor/controllers/tablet_controller.py b/fusion_plating/fusion_plating_shopfloor/controllers/tablet_controller.py index a7b446d1..f22bd824 100644 --- a/fusion_plating/fusion_plating_shopfloor/controllers/tablet_controller.py +++ b/fusion_plating/fusion_plating_shopfloor/controllers/tablet_controller.py @@ -321,6 +321,100 @@ class FpTabletController(http.Controller): 'tech_name': target.name, } + # ====================================================================== + # /fp/tablet/lock_session — destroy tech session + re-auth as kiosk + # ====================================================================== + @http.route('/fp/tablet/lock_session', type='jsonrpc', auth='user') + def lock_session(self, reason='manual'): + """Lock the tablet — destroy the tech's session and re-auth the + browser as fp_tablet_kiosk. Audit-log the event. + + `reason` is one of 'manual' / 'idle' / 'ceiling' — controls which + event_type gets written. The corresponding event_type names: + manual -> manual_lock + idle -> idle_lock + ceiling -> ceiling_lock + Anything else falls back to manual_lock. + """ + from ._tablet_session_audit import write_event, _sha256_session_sid + env = request.env + now = fields.Datetime.now() + sid = request.session.sid + tech_id = env.uid + + # Determine the audit event_type + event_type_map = { + 'manual': 'manual_lock', + 'idle': 'idle_lock', + 'ceiling': 'ceiling_lock', + } + event_type = event_type_map.get(reason, 'manual_lock') + + # Find the matching open session_event so we can compute duration. + # We look for the most recent unlock for this user without a + # session_ended_at — that's the open one. + SessionEvent = env['fp.tablet.session.event'].sudo() + open_event = SessionEvent.search([ + ('event_type', '=', 'unlock'), + ('user_id', '=', tech_id), + ('session_ended_at', '=', False), + ], order='create_date desc', limit=1) + + session_started_at = ( + open_event.session_started_at if open_event else False + ) + duration_seconds = None + if session_started_at: + duration_seconds = int((now - session_started_at).total_seconds()) + + # Write the lock event BEFORE destroying the session (we lose + # env.uid after logout). + write_event(env, + event_type=event_type, + user_id=tech_id, + session_id_hash=_sha256_session_sid(sid), + session_started_at=session_started_at, + session_ended_at=now, + duration_seconds=duration_seconds) + _logger.info( + 'Tablet locked (reason=%s) for uid %s (sid %s..) duration=%ss', + reason, tech_id, sid[:8] if sid else '', duration_seconds, + ) + + # Destroy the tech session + request.session.logout(keep_db=True) + + # Re-authenticate as the kiosk user. Credential comes from + # ir.config_parameter (auto-generated on first install in + # post-migrate.py). + kiosk_login = 'fp_tablet_kiosk@enplating.local' + kiosk_password = env['ir.config_parameter'].sudo().get_param( + 'fp.tablet.kiosk_password', '' + ) + if not kiosk_password: + _logger.error( + 'fp.tablet.kiosk_password missing from ir.config_parameter; ' + 'cannot re-auth tablet as kiosk. The browser will need ' + 'manual login.' + ) + return {'ok': True, 'locked_at': now.isoformat(), + 'needs_kiosk_relogin': True} + + try: + request.session.authenticate( + request.db, + {'type': 'password', + 'login': kiosk_login, + 'password': kiosk_password}, + ) + except Exception as e: + _logger.exception( + 'Failed to re-auth tablet as kiosk: %s', e) + return {'ok': True, 'locked_at': now.isoformat(), + 'needs_kiosk_relogin': True} + + return {'ok': True, 'locked_at': now.isoformat()} + # ====================================================================== # /fp/tablet/tiles — lock-screen tile grid # ======================================================================