USB kiosk: guest check-out/check-in without a login
This commit is contained in:
@@ -274,3 +274,181 @@ def list_checkouts(request, active_only):
|
||||
event['device_status'] = _status(checkout.device) if checkout.device else None
|
||||
data.append(event)
|
||||
return success_response(data)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kiosk (self-hosted mode). Same contract as the external path in routes.py:
|
||||
# move a device between checked-out and checked-in, attributed to a badge that
|
||||
# resolved to a real person, and nothing else.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def kiosk_device(device_id):
|
||||
device = _find(device_id)
|
||||
if not device:
|
||||
return error_response(ErrorCodes.NOT_FOUND,
|
||||
'No device matches that code', http_code=404)
|
||||
return success_response({
|
||||
'device_id': device.serialnumber,
|
||||
'device_desc': device.label or device.productname or '',
|
||||
'status': _status(device),
|
||||
'locker_location': device.storagelocation,
|
||||
# Name only. The kiosk needs it for the return-for-someone-else
|
||||
# confirm; it does not need their badge.
|
||||
'holdername': device.currentusername or '' if device.ischeckedout else '',
|
||||
})
|
||||
|
||||
|
||||
def kiosk_session(data):
|
||||
"""See routes.kiosk_session for why the list costs a valid badge."""
|
||||
badge = (data.get('badge') or '').strip()
|
||||
if not badge:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'Scan your badge')
|
||||
name = _resolve_name(badge)
|
||||
if not name:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'Badge not recognized - see IT', http_code=422)
|
||||
|
||||
yours, available, others = [], [], 0
|
||||
for device in USBDevice.query.filter_by(isactive=True).order_by(
|
||||
USBDevice.serialnumber).all():
|
||||
entry = {
|
||||
'device_id': device.serialnumber,
|
||||
'device_desc': device.label or device.productname or '',
|
||||
'locker_location': device.storagelocation,
|
||||
}
|
||||
if not device.ischeckedout:
|
||||
available.append(entry)
|
||||
elif (device.currentuserid or '').strip() == badge:
|
||||
yours.append(entry)
|
||||
else:
|
||||
others += 1
|
||||
|
||||
return success_response({'name': name, 'yours': yours,
|
||||
'available': available, 'otherscount': others})
|
||||
|
||||
|
||||
def kiosk_others(data):
|
||||
badge = (data.get('badge') or '').strip()
|
||||
if not badge:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'Scan your badge')
|
||||
if not _resolve_name(badge):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'Badge not recognized - see IT', http_code=422)
|
||||
|
||||
out = []
|
||||
for device in USBDevice.query.filter_by(isactive=True, ischeckedout=True
|
||||
).order_by(USBDevice.serialnumber).all():
|
||||
if (device.currentuserid or '').strip() == badge:
|
||||
continue
|
||||
out.append({
|
||||
'device_id': device.serialnumber,
|
||||
'device_desc': device.label or device.productname or '',
|
||||
'locker_location': device.storagelocation,
|
||||
'holdername': device.currentusername or '',
|
||||
})
|
||||
return success_response(out)
|
||||
|
||||
|
||||
def kiosk_checkout(data):
|
||||
device_id = (data.get('device_id') or '').strip()
|
||||
badge = (data.get('badge') or '').strip()
|
||||
if not badge:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'Scan your badge')
|
||||
|
||||
device = _find(device_id)
|
||||
if not device:
|
||||
return error_response(ErrorCodes.NOT_FOUND,
|
||||
'No device matches that code', http_code=404)
|
||||
if not device.isactive:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'That device is retired - see IT')
|
||||
if device.ischeckedout:
|
||||
return error_response(
|
||||
ErrorCodes.CONFLICT,
|
||||
'That device is already checked out{}'.format(
|
||||
' to {}'.format(device.currentusername)
|
||||
if device.currentusername else ''),
|
||||
http_code=409)
|
||||
|
||||
# Refused rather than recorded anonymously - see the note on the external
|
||||
# path. A checkout nobody can trace to a person defeats the tracking.
|
||||
name = _resolve_name(badge)
|
||||
if not name:
|
||||
return error_response(
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'Badge not recognized - see IT to check this device out',
|
||||
http_code=422)
|
||||
|
||||
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
db.session.add(USBCheckout(usbdeviceid=device.usbdeviceid, sso=badge,
|
||||
checkoutname=name, checkouttime=now))
|
||||
device.ischeckedout = True
|
||||
device.currentuserid = badge
|
||||
device.currentusername = name
|
||||
device.currentcheckoutdate = now
|
||||
db.session.commit()
|
||||
return success_response({'device_id': device.serialnumber,
|
||||
'holdername': name},
|
||||
message='{} checked out to {}'.format(
|
||||
device.serialnumber, name))
|
||||
|
||||
|
||||
def kiosk_checkin(data):
|
||||
device_id = (data.get('device_id') or '').strip()
|
||||
badge = (data.get('badge') or '').strip()
|
||||
if not badge:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'Scan your badge')
|
||||
forsomeoneelse = bool(data.get('forsomeoneelse'))
|
||||
|
||||
device = _find(device_id)
|
||||
if not device:
|
||||
return error_response(ErrorCodes.NOT_FOUND,
|
||||
'No device matches that code', http_code=404)
|
||||
if not device.ischeckedout:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'That device is not checked out')
|
||||
|
||||
name = _resolve_name(badge)
|
||||
if not name:
|
||||
return error_response(
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'Badge not recognized - see IT to return this device',
|
||||
http_code=422)
|
||||
|
||||
holderbadge = (device.currentuserid or '').strip()
|
||||
if holderbadge and holderbadge != badge:
|
||||
if not _kiosk_flag('usb_kiosk_allow_return_for_others', True):
|
||||
return error_response(
|
||||
ErrorCodes.FORBIDDEN,
|
||||
'That device is checked out to somebody else - they need to '
|
||||
'return it, or see IT',
|
||||
http_code=403)
|
||||
if not forsomeoneelse:
|
||||
return error_response(
|
||||
ErrorCodes.CONFLICT,
|
||||
'That device is checked out to {}. Confirm you are returning '
|
||||
'it for them.'.format(device.currentusername or 'somebody else'),
|
||||
details={'needsconfirm': True,
|
||||
'holdername': device.currentusername or ''},
|
||||
http_code=409)
|
||||
|
||||
open_checkout = (USBCheckout.query
|
||||
.filter_by(usbdeviceid=device.usbdeviceid, checkintime=None)
|
||||
.order_by(USBCheckout.checkouttime.desc()).first())
|
||||
if open_checkout:
|
||||
open_checkout.checkintime = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
device.ischeckedout = False
|
||||
device.currentuserid = None
|
||||
device.currentusername = None
|
||||
device.currentcheckoutdate = None
|
||||
db.session.commit()
|
||||
return success_response({'device_id': device.serialnumber},
|
||||
message='{} checked in'.format(device.serialnumber))
|
||||
|
||||
|
||||
def _kiosk_flag(key, default):
|
||||
from shopdb.api import Setting
|
||||
row = Setting.query.filter_by(key=key).first()
|
||||
if row is None or row.value is None or row.value == '':
|
||||
return default
|
||||
return str(row.value).strip().lower() in ('true', '1', 'yes', 'on')
|
||||
|
||||
Reference in New Issue
Block a user