Carry the level everywhere a position is drawn, and gate it per occurrence
Some checks failed
CI / backend (push) Failing after 9s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 11s
CI / migrations-mysql (push) Failing after 7s

The hover mini-map said "This asset has a position (2835, 1410) but no level"
for every asset in the product. When 0.11.0 gave LocationMapTooltip a levelid
prop, NONE of its seven call sites were taught to pass one - printer, machine and
PC detail pages, the toner report, enforcement reports, the warranty chip and the
dashboard cards - so the component correctly reported a missing level and the
preview never drew. Two payloads behind those views also emitted mapx/mapy with
no level: the toner report and the enforcement report.

The map PDF export had the ORIGINAL bug still in it: it plotted every filtered
asset onto the sheet, so exporting the ground floor printed second-floor markers
on it. Worse than on screen, because nobody can correct a sheet once it has been
printed and carried onto the floor. It now exports only the level being viewed.

The legacy import loader sent mapleft/maptop with no level at three call sites.
That loader is the one still to run against production, and every marker it
created would have been undrawable. It now resolves the site's default level -
the legacy schema predates levels and has one floor plan, so that is what its
coordinates mean.

THE GATE MISSED ALL OF THIS because it asked whether a FILE mentions 'levelid',
not whether each position does: one module emitted 'mapx' six times and 'levelid'
once and passed. It now checks per occurrence, covers scripts/ as well as shopdb/
and plugins/, and fails any Vue file that binds tooltip coordinates without
:levelid. Both new rules were confirmed to fail the build against planted
violations before being relied on.

Printer QR labels: the asset number is no longer printed. A label now reads name
(8201-HPLaserJetPro), QR, FQDN, then IP. The name falls back to the assetnumber
because that is where sites actually keep it - every printer here has an empty
name field, so preferring the Windows queue name alone would have printed a blank
line on every label.
This commit is contained in:
cproudlock
2026-08-18 09:36:45 -04:00
parent 8bde89c47e
commit f34b9ca710
16 changed files with 516 additions and 400 deletions

View File

@@ -169,21 +169,44 @@ fi
# whether all of them travel with their level, because reading them by eye is
# how the twentieth gets missed.
echo "==> Checking that emitted map positions carry their level (ADR-017)..."
POSITION_FILES=$(grep -rln "'mapx':" --include='*.py' shopdb/ plugins/ 2>/dev/null \
| grep -v '/tests\?/' || true)
# PER OCCURRENCE, not per file. The file-level form passed a module that emitted
# 'mapx' six times and 'levelid' once, and two payloads shipped without a level:
# the toner report and the enforcement report, both feeding a hover preview that
# then said "no level". Every 'mapx' must have a 'levelid' in the same literal -
# the window is wide enough for a comment between them, and no wider.
MISSING_LEVEL=""
for candidate in $POSITION_FILES; do
if ! grep -q "'levelid'" "$candidate"; then
MISSING_LEVEL="$MISSING_LEVEL$candidate"$'\n'
while IFS= read -r hit; do
[ -z "$hit" ] && continue
file=${hit%%:*}
line=${hit#*:}; line=${line%%:*}
if ! sed -n "${line},$((line + 8))p" "$file" | grep -q "'levelid'"; then
MISSING_LEVEL="$MISSING_LEVEL$file:$line"$'\n'
fi
done
done <<EOF
$(grep -rn "'mapx':" --include='*.py' shopdb/ plugins/ scripts/ 2>/dev/null | grep -v '/tests\?/' || true)
EOF
if [ -n "$MISSING_LEVEL" ]; then
echo "FAIL: these emit 'mapx' but never 'levelid' - a position with no level"
echo " cannot be rendered on the right drawing:"
echo "FAIL: these emit 'mapx' with no 'levelid' beside it - a position with no"
echo " level cannot be drawn on the right floor plan (ADR-017):"
echo "$MISSING_LEVEL" | sed 's/^/ /'
VIOLATIONS=$((VIOLATIONS + 1))
fi
# The same rule for the hover preview: binding coordinates into
# LocationMapTooltip without a level makes it report "no level" for every asset,
# which is exactly what shipped in 0.11.0 - all seven call sites missed it.
TOOLTIP_MISSING=""
for candidate in $(grep -rl "LocationMapTooltip" --include='*.vue' frontend/src plugins/ 2>/dev/null | grep -v plugins-staged || true); do
grep -q ':left=' "$candidate" || continue
grep -q ':levelid=' "$candidate" || TOOLTIP_MISSING="$TOOLTIP_MISSING$candidate"$'\n'
done
if [ -n "$TOOLTIP_MISSING" ]; then
echo "FAIL: these bind LocationMapTooltip coordinates without :levelid, so the"
echo " preview cannot know which drawing to use (ADR-017):"
echo "$TOOLTIP_MISSING" | sed 's/^/ /'
VIOLATIONS=$((VIOLATIONS + 1))
fi
# ENFORCING. It was report-only while the backlog was worked off, and the hit
# count then did not move for weeks - a rule that only prints is read as no rule.
# Set SITE_LITERALS_ENFORCE=0 to drop back to reporting for a local run.

View File

@@ -100,6 +100,22 @@ class Harness:
self.ids = IdMap(idmap_path or default)
self.source = Source()
self.errors = []
self._defaultlevelid = None
@property
def defaultlevelid(self):
"""The level imported map positions belong to (ADR-017).
The legacy schema predates levels: it has ONE floor plan, so every
mapleft/maptop it carries is a coordinate on this site's default level.
Importing them without a level produces markers the map refuses to draw
- it will not guess a drawing for coordinates that do not name one.
"""
if self._defaultlevelid is None:
from shopdb.core.models import MapLevel
level = MapLevel.default_level()
self._defaultlevelid = level.levelid if level else None
return self._defaultlevelid
def _silence_sql_logging(self):
import logging

View File

@@ -287,6 +287,7 @@ def stage_assets(h):
'serialnumber': (m['serialnumber'] or '').strip() or None,
'businessunitid': h.ids.get('businessunit', m['businessunitid']),
'mapx': m['mapleft'], 'mapy': m['maptop'],
'levelid': h.defaultlevelid,
'notes': m['machinenotes'],
'dateadded': str(m['dateadded']) if m['dateadded'] else None,
'modifieddate': str(m['lastupdated']) if m['lastupdated'] else None,
@@ -345,6 +346,7 @@ def stage_printers(h):
'iscsf': _truthy_bit(p['iscsf']),
'installpath': p['installpath'], 'pin': p['printerpin'],
'notes': p['printernotes'], 'mapx': p['mapleft'], 'mapy': p['maptop'],
'levelid': h.defaultlevelid,
'modelnumberid': h.ids.get('model', p['modelid']),
'locationid': h.ids.get('location', p['machineid']),
}
@@ -386,7 +388,8 @@ def stage_metrology(h):
'name': (f"{pcname} {toolname}").strip() or toolname,
'measuringtooltypeid': typeid,
'businessunitid': h.ids.get('businessunit', m['businessunitid']),
'mapx': m['mapleft'], 'mapy': m['maptop']})
'mapx': m['mapleft'], 'mapy': m['maptop'],
'levelid': h.defaultlevelid})
if status not in (200, 201):
continue
tool_assetid = _id_of(data, 'assetid')