feat(fusion_plating_shopfloor): /fp/tablet/tiles + /fp/tablet/ping endpoints (P6.1.4-P6.1.5)
Tiles returns the lock-screen grid: operator-group users, sorted clocked-in-first then alphabetical, with avatar URL + has_pin flag. Honours station.x_fc_authorised_user_ids when non-empty (Phase 6.1.6 adds that field). Ping is a lightweight ack used by FpTabletLock as a heartbeat — logs current_tech_id at DEBUG for forensic visibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -147,3 +147,65 @@ class FpTabletController(http.Controller):
|
||||
'error': _('Incorrect PIN.'),
|
||||
'attempts_remaining': threshold - new_count,
|
||||
}
|
||||
|
||||
# ======================================================================
|
||||
# /fp/tablet/tiles — lock-screen tile grid
|
||||
# ======================================================================
|
||||
@http.route('/fp/tablet/tiles', type='jsonrpc', auth='user')
|
||||
def tiles(self, station_id=None):
|
||||
env = request.env
|
||||
op_group = env.ref(
|
||||
'fusion_plating.group_fusion_plating_operator',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not op_group:
|
||||
return {'ok': False, 'error': 'operator group missing'}
|
||||
|
||||
# Determine candidate users — station roster wins if non-empty
|
||||
users = op_group.user_ids
|
||||
if station_id:
|
||||
Station = env['fusion.plating.shopfloor.station']
|
||||
station = Station.browse(int(station_id))
|
||||
if (station.exists()
|
||||
and 'x_fc_authorised_user_ids' in station._fields
|
||||
and station.x_fc_authorised_user_ids):
|
||||
users = station.x_fc_authorised_user_ids
|
||||
|
||||
# has_pin needs sudo-read on the hash field
|
||||
clocked_ids = set()
|
||||
if 'hr.employee' in env and hasattr(
|
||||
env['hr.employee'], '_fp_clocked_in_user_ids',
|
||||
):
|
||||
clocked_ids = env['hr.employee']._fp_clocked_in_user_ids() or set()
|
||||
|
||||
users_sorted = users.sorted('name')
|
||||
users_sudo = users_sorted.sudo()
|
||||
tiles = []
|
||||
for u, u_sudo in zip(users_sorted, users_sudo):
|
||||
tiles.append({
|
||||
'user_id': u.id,
|
||||
'name': u.name,
|
||||
'avatar_url': f'/web/image/res.users/{u.id}/avatar_128',
|
||||
'is_clocked_in': u.id in clocked_ids,
|
||||
'has_pin': bool(u_sudo.x_fc_tablet_pin_hash),
|
||||
})
|
||||
# Clocked-in first, then alphabetical within bucket
|
||||
tiles.sort(key=lambda t: (not t['is_clocked_in'], t['name']))
|
||||
return {'ok': True, 'tiles': tiles}
|
||||
|
||||
# ======================================================================
|
||||
# /fp/tablet/ping — heartbeat used by the OWL component on every action
|
||||
# ======================================================================
|
||||
@http.route('/fp/tablet/ping', type='jsonrpc', auth='user')
|
||||
def ping(self, current_tech_id=None):
|
||||
"""Lightweight heartbeat. Used by the OWL component to confirm
|
||||
the server-side session is alive AND to log the tech-of-record
|
||||
every few minutes so the server has forensic visibility into
|
||||
which tech was 'driving' the tablet at any moment.
|
||||
"""
|
||||
if current_tech_id:
|
||||
_logger.debug(
|
||||
"Tablet ping: session uid %s carrying tablet_tech_id=%s",
|
||||
request.env.uid, current_tech_id,
|
||||
)
|
||||
return {'ok': True, 'server_time': fields.Datetime.now().isoformat()}
|
||||
|
||||
Reference in New Issue
Block a user