Files
shopdb-flask/shopdb/utils/badges.py

71 lines
2.6 KiB
Python

"""What a scanned badge looks like, in one place.
Two kiosks read the same physical badges and disagreed about them. The parts
kiosk understood both shapes a scanner emits; the USB kiosk took the string
raw and compared it to an SSO, so the wrapped form was simply "not recognized".
An operator whose keyboard-wedge scanner emits `0270015376BZ` could take a
3D-printed part and could not check out a drive - with the same badge, at two
cabinets a few metres apart.
The shapes:
270015376 the SSO as printed, what a keypad entry looks like
0270015376BZ the PayNo wrapper some scanners emit around it
Both mean the same person. Nothing else is a badge - and in particular a string
of NON-ASCII digits is not, however much it looks like one: Arabic-Indic digits
pass Python's str.isdigit(), and a badge written in them was accepted, stored as
the employee's SSO, and joined to nothing afterwards.
This lives in core rather than in either plugin because a plugin may not import
another plugin, and because a badge is a property of the site, not of a
cabinet.
"""
import re
# The PayNo wrapper: a leading zero, the digits, and a BZ suffix.
PAYNO_BADGE = re.compile(r'^0(\d+)BZ$', re.IGNORECASE)
# ASCII digits only. str.isdigit() is true for Arabic-Indic and other decimal
# forms, and int() happily parses them, so a badge in those characters resolved
# to a real employee and was then written back in a form nothing else matches.
ASCII_DIGITS = re.compile(r'^[0-9]+$')
class BadgeShapeError(ValueError):
"""The scanned string is not a badge this site recognises."""
def normalise_badge(raw):
"""Return the plain SSO digits for a scanned badge.
Accepts the bare number and the PayNo wrapper, and raises BadgeShapeError
for anything else. The caller decides what to do about a badge that is
well-formed but matches no employee - that is a directory question, not a
shape one.
"""
badge = (raw or '').strip()
if not badge:
raise BadgeShapeError('Scan or enter a badge')
if ASCII_DIGITS.match(badge):
return badge
match = PAYNO_BADGE.match(badge)
if match and ASCII_DIGITS.match(match.group(1)):
return match.group(1)
raise BadgeShapeError('Unrecognized badge format')
def badge_kind(raw):
"""'sso' or 'payno' for a recognised badge; raises for anything else.
The external HR lookup searches a different column for each, so the shape
survives normalisation for that one caller.
"""
badge = (raw or '').strip()
if ASCII_DIGITS.match(badge):
return 'sso'
if PAYNO_BADGE.match(badge):
return 'payno'
raise BadgeShapeError('Unrecognized badge format')