From 442a63155774f90a2ba30e06f6ff264e4fd8953e Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sat, 8 Aug 2026 14:20:35 -0400 Subject: [PATCH] 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. --- plugins/notifications/api/routes.py | 18 ++- .../frontend/views/NotificationTypesList.vue | 14 +++ .../migrations/versions/0005_boardorder.py | 61 ++++++++++ plugins/notifications/models/notification.py | 10 +- tests/test_plugin_migrations.py | 2 +- tests/test_plugins/test_board_order.py | 106 ++++++++++++++++++ 6 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 plugins/notifications/migrations/versions/0005_boardorder.py create mode 100644 tests/test_plugins/test_board_order.py diff --git a/plugins/notifications/api/routes.py b/plugins/notifications/api/routes.py index cf9275b..3e30c6b 100644 --- a/plugins/notifications/api/routes.py +++ b/plugins/notifications/api/routes.py @@ -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) diff --git a/plugins/notifications/frontend/views/NotificationTypesList.vue b/plugins/notifications/frontend/views/NotificationTypesList.vue index 40e717d..6cde7e3 100644 --- a/plugins/notifications/frontend/views/NotificationTypesList.vue +++ b/plugins/notifications/frontend/views/NotificationTypesList.vue @@ -33,6 +33,7 @@ {{ t.typecolor }} + #{{ t.boardorder ?? 100 }} {{ t.displaystyle || 'standard' }} {{ t.boardcategory }} @@ -90,6 +91,17 @@ + +