fix(shopfloor): Phase C review findings — lock_session closes unlock event + cron test

Important 1: lock_session now closes the original unlock event's
session_ended_at via the same parameterized-SQL bypass pattern used
by the force-lock cron. Without this, every Hand-Off click became
a duplicate force_lock event 8 hours later (cron saw the unlock still
open and re-processed).

Important 2: test_unlock_lock_session_endpoints setUp now
unconditionally overrides the kiosk password (was gated on
'if not get_param(...)' which broke on entech where the post-migrate
hook already generated a random password — tests failed against the
real value). HttpCase rolls back per test so no persistence.

Minor 4: _cron_force_lock_stale_sessions now routes the force_lock
create through write_event helper for consistency (single audit-write
path; helper captures acting_uid/ip/ua uniformly).

Minor 5: Hoisted local imports inside method bodies to top-of-file
in tablet_controller.py (AccessDenied, _tablet_session_audit) and
fp_tablet_session_event.py (timedelta, write_event).

Minor 6: New test_force_lock_cron.py with 3 tests: stale session
emits force_lock + closes original; recent session unaffected;
already-closed session not re-processed. Would have caught
Important 1 if it had existed during Phase C review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 13:08:30 -04:00
parent 87e924d1d8
commit 8f6302b446
5 changed files with 130 additions and 18 deletions

View File

@@ -7,8 +7,11 @@ write/unlink/edit are forbidden to anyone except root via direct SQL.
Spec section 4: docs/superpowers/specs/2026-05-24-tablet-pin-session-redesign-design.md
"""
from datetime import timedelta
from odoo import _, api, fields, models
from odoo.exceptions import AccessError
from odoo.addons.fusion_plating_shopfloor.controllers._tablet_session_audit import write_event
class FpTabletSessionEvent(models.Model):
@@ -124,7 +127,6 @@ class FpTabletSessionEvent(models.Model):
Runs every 5 minutes per fp_tablet_cron.xml.
"""
from datetime import timedelta
ceiling_hours = int(self.env['ir.config_parameter'].sudo().get_param(
'fp.tablet.session_ceiling_hours', 8))
cutoff = fields.Datetime.now() - timedelta(hours=ceiling_hours)
@@ -136,15 +138,15 @@ class FpTabletSessionEvent(models.Model):
now = fields.Datetime.now()
for event in stale:
duration = int((now - event.session_started_at).total_seconds())
self.sudo().create({
'event_type': 'force_lock',
'user_id': event.user_id.id,
'session_id_hash': event.session_id_hash,
'session_started_at': event.session_started_at,
'session_ended_at': now,
'duration_seconds': duration,
'notes': 'Cron force-lock: session exceeded %d-hour ceiling' % ceiling_hours,
})
write_event(self.env,
event_type='force_lock',
user_id=event.user_id.id,
session_id_hash=event.session_id_hash,
session_started_at=event.session_started_at,
session_ended_at=now,
duration_seconds=duration,
notes='Cron force-lock: session exceeded %d-hour ceiling' % ceiling_hours,
)
# Mark the original unlock event closed so it's not reprocessed
# next tick. write() is blocked by the model override — use
# direct SQL bypass (this is the documented escape hatch for