@@ -350,6 +350,9 @@ async function refresh(w) {
.muted { color: var(--text-light); }
.status-badge { padding: 0.15rem 0.6rem; border-radius: 12px; font-size: 0.78rem; font-weight: 600; }
.filters { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
+/* Keep the "Status" label + its select on one line so it aligns with the
+ single-line search box next to it. */
+.filters label { display: inline-flex; align-items: center; gap: 0.4rem; }
.filters .form-control { max-width: 320px; }
.result-count { color: var(--text-light); font-size: 0.85rem; }
.servicelevel-cell { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
diff --git a/plugins/warranty/api/routes.py b/plugins/warranty/api/routes.py
index 41ddb5d..890e322 100644
--- a/plugins/warranty/api/routes.py
+++ b/plugins/warranty/api/routes.py
@@ -9,6 +9,7 @@ from datetime import date, datetime, timezone
from flask import Blueprint, request
from flask_jwt_extended import jwt_required
+from sqlalchemy.orm import joinedload
from shopdb.api import (
db, Asset,
@@ -41,12 +42,15 @@ def _asset_summary(asset):
}
-def _warranty_payload(warranty, today=None):
- """to_dict plus the linked-asset summaries."""
+def _warranty_payload(warranty, today=None, assetmap=None):
+ """to_dict plus the linked-asset summaries. Pass assetmap (assetid -> Asset)
+ to avoid a per-link query when serializing a list; without it, falls back to
+ a per-link get (fine for a single warranty)."""
data = warranty.to_dict(today)
assets = []
for link in warranty.links:
- asset = db.session.get(Asset, link.assetid)
+ asset = assetmap.get(link.assetid) if assetmap is not None \
+ else db.session.get(Asset, link.assetid)
if asset:
assets.append(_asset_summary(asset))
data['assets'] = assets
@@ -86,10 +90,17 @@ def list_warranties():
if assetid:
query = (query.join(WarrantyAsset, WarrantyAsset.warrantyid == Warranty.warrantyid)
.filter(WarrantyAsset.assetid == assetid))
- warranties = query.order_by(Warranty.enddate.is_(None), Warranty.enddate).all()
+ warranties = (query.options(joinedload(Warranty.links))
+ .order_by(Warranty.enddate.is_(None), Warranty.enddate).all())
+
+ # Batch-fetch every linked asset in ONE query (was N+1: a db.session.get per
+ # link per warranty, ~1.8s for the full list).
+ assetids = {link.assetid for w in warranties for link in w.links}
+ assetmap = ({a.assetid: a for a in Asset.query.filter(Asset.assetid.in_(assetids)).all()}
+ if assetids else {})
today = date.today()
- items = [_warranty_payload(w, today) for w in warranties]
+ items = [_warranty_payload(w, today, assetmap) for w in warranties]
status_filter = request.args.get('status')
if status_filter: