cleanup(shopfloor): session_swap is the only tablet flow

Frontend cleanup completing Phase G of the tablet PIN session
redesign:

- tablet_lock.js: removed sessionMode branching (no legacy path).
  unlock() always calls /fp/tablet/unlock_session + reloads.
  handOff() always calls tabletSessionManager.lockBack('manual').
  isLocked uses currentUid vs kioskUid exclusively. _checkIdle
  still drives the warning UI via activity_tracker; the actual
  lock RPC is owned by tablet_session_manager.

- fp_rpc.js: simplified to a thin async pass-through around @web/core
  network rpc. tech_store-based tablet_tech_id injection is gone
  (the session uid IS the tech).

- tech_store.js: DELETED (replaced by per-session backend attribution
  + tablet_session_manager for lock state). Removed from manifest.

- Wrapper components (shopfloor_landing, job_workspace,
  manager_dashboard, plant_kanban): swapped useService('fp_shopfloor_tech_store')
  for useService('fp_tablet_session_manager'); techStore.lock() ->
  tabletSessionManager.lockBack('manual'). plant_kanban's defensive
  try/catch on the tech_store lookup is no longer needed.

- tablet_lock.xml: Hand-Off button no longer gated on sessionMode;
  always rendered.

- Tests: removed legacy TestTabletUnlock class from test_tablet_pin.py
  (covered the deleted /fp/tablet/unlock route). Dropped session_mode
  assertion from test_tiles_bootstrap_fields.py (the return key is
  gone post-Phase-G). kiosk_uid + current_uid assertions retained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 14:36:12 -04:00
parent d9f2983ea7
commit 67fc22237b
11 changed files with 67 additions and 280 deletions

View File

@@ -126,63 +126,6 @@ class TestTabletResetPinFor(HttpCase):
self.assertFalse(self.target.sudo().x_fc_tablet_pin_hash)
@tagged('-at_install', 'post_install', 'fp_shopfloor', 'fp_tablet_pin')
class TestTabletUnlock(HttpCase):
"""P6.1.3 — /fp/tablet/unlock endpoint + lockout."""
def setUp(self):
super().setUp()
self.authenticate("admin", "admin")
self.target = self.env['res.users'].create({
'name': 'Unlock Target', 'login': 'unlock@example.com',
})
self.target.sudo().set_tablet_pin('1234')
def _unlock(self, pin):
return _rpc(self, '/fp/tablet/unlock',
user_id=self.target.id, pin=pin)
def test_unlock_correct_pin(self):
res = self._unlock('1234')
self.assertTrue(res['ok'])
self.assertEqual(res['current_tech_id'], self.target.id)
self.assertEqual(res['current_tech_name'], 'Unlock Target')
def test_unlock_correct_pin_resets_fail_counter(self):
self._unlock('0000') # fail once
self._unlock('1234') # succeed
self.target.invalidate_recordset(['x_fc_tablet_pin_failed_count'])
self.assertEqual(self.target.sudo().x_fc_tablet_pin_failed_count, 0)
def test_unlock_wrong_pin_increments_counter(self):
self._unlock('0000')
self.target.invalidate_recordset(['x_fc_tablet_pin_failed_count'])
self.assertEqual(self.target.sudo().x_fc_tablet_pin_failed_count, 1)
def test_lockout_after_5_fails(self):
for _ in range(5):
self._unlock('0000')
res = self._unlock('0000') # 6th
self.assertFalse(res['ok'])
self.assertIn('locked', res['error'].lower())
self.target.invalidate_recordset(['x_fc_tablet_locked_until'])
self.assertTrue(self.target.sudo().x_fc_tablet_locked_until)
def test_lockout_blocks_even_correct_pin(self):
for _ in range(5):
self._unlock('0000')
# Even the correct PIN now rejected
res = self._unlock('1234')
self.assertFalse(res['ok'])
self.assertIn('locked', res['error'].lower())
def test_unlock_no_pin_set(self):
self.target.clear_tablet_pin()
res = self._unlock('1234')
self.assertFalse(res['ok'])
self.assertTrue(res.get('needs_setup'))
@tagged('-at_install', 'post_install', 'fp_shopfloor', 'fp_tablet_pin')
class TestTabletTiles(HttpCase):
"""P6.1.4 — /fp/tablet/tiles endpoint."""

View File

@@ -3,10 +3,11 @@ from odoo.tests.common import HttpCase, tagged
@tagged('-at_install', 'post_install', 'fp_tablet')
class TestTilesBootstrapFields(HttpCase):
"""Phase D added 3 bootstrap fields to /fp/tablet/tiles:
tablet_session_mode, kiosk_uid, current_uid. The OWL lock screen
branches on these, so a contract regression would silently
break the session-swap flow."""
"""Post-Phase-G /fp/tablet/tiles returns kiosk_uid + current_uid so
the OWL lock screen can detect whether the current browser session
belongs to the kiosk (locked) or a tech (unlocked). A contract
regression on either field would silently break the session-swap
flow."""
def setUp(self):
super().setUp()
@@ -25,15 +26,6 @@ class TestTilesBootstrapFields(HttpCase):
headers={'Content-Type': 'application/json'},
).json()
def test_tiles_returns_session_mode(self):
self.authenticate('fp_tablet_kiosk@enplating.local', 'tiles_test_pwd')
resp = self._jsonrpc('/fp/tablet/tiles', {})
result = resp.get('result', {})
self.assertIn('tablet_session_mode', result,
'tiles bootstrap must include tablet_session_mode for OWL branching')
# Default value is 'legacy' until rollout flips the flag
self.assertIn(result['tablet_session_mode'], ('legacy', 'session_swap'))
def test_tiles_returns_kiosk_uid(self):
self.authenticate('fp_tablet_kiosk@enplating.local', 'tiles_test_pwd')
resp = self._jsonrpc('/fp/tablet/tiles', {})