From f34b9ca710912e67d1779940abb36b08c1edb500 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Tue, 18 Aug 2026 09:36:45 -0400 Subject: [PATCH] Carry the level everywhere a position is drawn, and gate it per occurrence 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. --- frontend/src/components/DashboardCards.vue | 2 +- frontend/src/components/dashboardCards.js | 10 +- frontend/src/views/MapView.vue | 7 +- plugins/computers/frontend/views/PCDetail.vue | 1 + plugins/geenforce/api/routes.py | 3 + .../frontend/views/EnforcementReports.vue | 2 +- .../machines/frontend/views/MachineDetail.vue | 1 + plugins/printers/api/asset_routes.py | 6 +- .../printers/frontend/views/PrinterDetail.vue | 1 + .../frontend/views/PrinterQRBatch.vue | 32 +- .../frontend/views/PrinterQRSingle.vue | 33 +- .../printers/frontend/views/TonerReport.vue | 757 +++++++++--------- .../frontend/components/MachineMapChip.vue | 1 + scripts/check-naming-and-style.sh | 39 +- scripts/site_imports/wjf/harness.py | 16 + scripts/site_imports/wjf/run.py | 5 +- 16 files changed, 516 insertions(+), 400 deletions(-) diff --git a/frontend/src/components/DashboardCards.vue b/frontend/src/components/DashboardCards.vue index 72c37ab..90ced35 100644 --- a/frontend/src/components/DashboardCards.vue +++ b/frontend/src/components/DashboardCards.vue @@ -22,7 +22,7 @@ diff --git a/frontend/src/components/dashboardCards.js b/frontend/src/components/dashboardCards.js index b4315c1..d35e5ed 100644 --- a/frontend/src/components/dashboardCards.js +++ b/frontend/src/components/dashboardCards.js @@ -127,7 +127,15 @@ export function mapHover(card, item) { const x = item[spec.x] const y = item[spec.y] if (x === null || x === undefined || y === null || y === undefined) return null - return { x, y, label: spec.label ? (item[spec.label] || '') : '' } + // The level travels with the coordinates (ADR-017) - they are pixels of ONE + // drawing. Read `levelid` unless the card names another field, so a card that + // predates levels still previews on the right floor instead of none. + const levelid = item[spec.level || 'levelid'] + return { + x, y, + levelid: levelid === undefined ? null : levelid, + label: spec.label ? (item[spec.label] || '') : '', + } } export function cardRows(card) { diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 724204a..9e2b6ca 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -289,7 +289,12 @@ async function exportPdf() { try { await loadMapConfig() await exportMapPdf({ - assets: filteredAssets.value, + // Only this level's markers. The sheet is one drawing, so a marker + // positioned against another level would be printed on the wrong floor + // plan - the same failure the on-screen map had, in a form nobody can + // correct after it is printed and carried onto the floor. + assets: filteredAssets.value.filter( + asset => (asset.levelid ?? null) === shownLevelId.value), // blueprintUrlFor applies withBase - the raw setting value is a // root-relative /api path, which 404s under a subpath mount like /ops. // The PDF always uses the light blueprint: it prints on white paper. diff --git a/plugins/computers/frontend/views/PCDetail.vue b/plugins/computers/frontend/views/PCDetail.vue index ce28eaa..633aa2e 100644 --- a/plugins/computers/frontend/views/PCDetail.vue +++ b/plugins/computers/frontend/views/PCDetail.vue @@ -185,6 +185,7 @@ v-if="computer.mapx != null && computer.mapy != null" :left="computer.mapx" :top="computer.mapy" + :levelid="computer.levelid" :machineName="computer.assetnumber" > {{ computer.locationname || 'On Map' }} diff --git a/plugins/geenforce/api/routes.py b/plugins/geenforce/api/routes.py index 606ed33..4e69133 100644 --- a/plugins/geenforce/api/routes.py +++ b/plugins/geenforce/api/routes.py @@ -1223,6 +1223,9 @@ def list_reports(): 'location': known.get('location'), 'mapx': known.get('mapx'), 'mapy': known.get('mapy'), + # Coordinates are pixels of ONE level (ADR-017), so the level goes + # with them or the hover preview has nothing to draw on. + 'levelid': known.get('levelid'), 'machinenumber': known.get('machinenumber'), 'machineassetid': known.get('machineassetid'), 'machinepluginid': known.get('machinepluginid'), diff --git a/plugins/geenforce/frontend/views/EnforcementReports.vue b/plugins/geenforce/frontend/views/EnforcementReports.vue index a9ae87a..0fd4d3e 100644 --- a/plugins/geenforce/frontend/views/EnforcementReports.vue +++ b/plugins/geenforce/frontend/views/EnforcementReports.vue @@ -51,7 +51,7 @@ empty map would be worse than none. --> diff --git a/plugins/machines/frontend/views/MachineDetail.vue b/plugins/machines/frontend/views/MachineDetail.vue index f1e8b51..4e7810c 100644 --- a/plugins/machines/frontend/views/MachineDetail.vue +++ b/plugins/machines/frontend/views/MachineDetail.vue @@ -167,6 +167,7 @@ v-if="machine.mapx != null && machine.mapy != null" :left="machine.mapx" :top="machine.mapy" + :levelid="machine.levelid" :machineName="machine.assetnumber" > {{ machine.locationname || 'On Map' }} diff --git a/plugins/printers/api/asset_routes.py b/plugins/printers/api/asset_routes.py index abe0998..e84282c 100644 --- a/plugins/printers/api/asset_routes.py +++ b/plugins/printers/api/asset_routes.py @@ -1042,7 +1042,7 @@ def _get_low_supplies_data(): 'model': model_number, 'location': location_name, 'mapx': asset.mapx, - 'levelid': asset.levelid, + 'levelid': asset.levelid, 'mapy': asset.mapy, 'supplies': annotated }) @@ -1479,6 +1479,10 @@ def dashboard_supplies(): # card, it just has nothing to preview. 'mapx': printer.get('mapx'), 'mapy': printer.get('mapy'), + # The level those pixels belong to (ADR-017). Without it the hover + # preview cannot draw the marker and says so, which is what the + # dashboard card and the toner report were both doing. + 'levelid': printer.get('levelid'), 'iscritical': any(s['status'] == 'critical' for s in depleted), 'supplies': [{ 'text': '{} {}%'.format(_shortsupplyname(supply.get('name')), diff --git a/plugins/printers/frontend/views/PrinterDetail.vue b/plugins/printers/frontend/views/PrinterDetail.vue index b5c4229..a57b780 100644 --- a/plugins/printers/frontend/views/PrinterDetail.vue +++ b/plugins/printers/frontend/views/PrinterDetail.vue @@ -169,6 +169,7 @@ v-if="printer.mapx != null && printer.mapy != null" :left="printer.mapx" :top="printer.mapy" + :levelid="printer.levelid" :machineName="printer.name || printer.assetnumber" > View on Map diff --git a/plugins/printers/frontend/views/PrinterQRBatch.vue b/plugins/printers/frontend/views/PrinterQRBatch.vue index 2a09231..be2e6dc 100644 --- a/plugins/printers/frontend/views/PrinterQRBatch.vue +++ b/plugins/printers/frontend/views/PrinterQRBatch.vue @@ -43,14 +43,13 @@ :class="[`pos-${pos}`, page[pos - 1] ? 'filled' : 'empty']" >