Let a site choose the order its board rows run in

Rows ran in display-style order and then alphabetically, so what led the screen
was an accident of styling and the alphabet - a new type called Awareness
landed above Recertification for no better reason than the letter A.

Each type now carries a board position, lowest first, set on the Notification
Types page. The migration seeds Recognition at 10 and Recertification at 20 and
leaves everything else at 100, so an existing board keeps the order sites
already expect. Steps of ten leave room to slot a row in without renumbering
the rest.

A row shared by several types sits wherever its earliest-ordered type puts it,
so a category moves as a unit.
This commit is contained in:
cproudlock
2026-08-08 14:20:35 -04:00
parent adb8542018
commit 442a631557
6 changed files with 208 additions and 3 deletions

View File

@@ -140,6 +140,10 @@ def _apply_expiry_fields(t, data):
_DISPLAY_STYLES = ('standard', 'carousel', 'grid', 'banner')
# Ceiling on a type's board position. Wide enough to leave gaps between rows
# (10, 20, 30 ...) so inserting one later needs no renumbering.
_MAX_BOARD_ORDER = 999
# Ceiling on the per-type post-expiry tail. A day is already far longer than
# "recently ended"; anything more is an end time that should have been later.
_MAX_GRACE_MINUTES = 1440
@@ -164,6 +168,16 @@ def _apply_display_fields(t, data):
# Blank stores as NULL: "no category" is the absence of one, not the
# empty-string category that every uncategorised type would share.
t.boardcategory = category or None
if 'boardorder' in data:
raw = data.get('boardorder')
raw = 100 if raw in (None, '') else raw
try:
order = int(raw)
except (TypeError, ValueError):
return "boardorder must be a whole number"
if order < 0 or order > _MAX_BOARD_ORDER:
return "boardorder must be between 0 and %d" % _MAX_BOARD_ORDER
t.boardorder = order
if 'gracewindowminutes' in data:
raw = data.get('gracewindowminutes')
raw = 0 if raw in (None, '') else raw
@@ -185,11 +199,12 @@ def _config_version():
reach pages that are already open."""
types = NotificationType.query.order_by(NotificationType.notificationtypeid).all()
parts = [
"%s|%s|%s|%d|%d|%s|%s|%s|%d|%d|%s" % (
"%s|%s|%s|%d|%d|%s|%s|%s|%d|%d|%s|%d" % (
t.notificationtypeid, t.typecolor, t.displaystyle,
int(bool(t.splitperemployee)), int(bool(t.showemployeephoto)),
t.expirymode, t.expirydays, t.expiryhour, int(bool(t.isactive)),
int(t.gracewindowminutes or 0), t.boardcategory or '',
int(t.boardorder if t.boardorder is not None else 100),
)
for t in types
]
@@ -791,6 +806,7 @@ def get_shopfloor_notifications():
# blank gives the type a row of its own.
'displaystyle': (ntype.displaystyle or 'standard') if ntype else 'standard',
'boardcategory': (ntype.boardcategory or '') if ntype else '',
'boardorder': int(ntype.boardorder if ntype and ntype.boardorder is not None else 100),
}
# Employee info (photo only when the type wants it)