diff --git a/app/navigator_routes.py b/app/navigator_routes.py index 5fa790c..57c8546 100644 --- a/app/navigator_routes.py +++ b/app/navigator_routes.py @@ -87,15 +87,19 @@ async def crate_contents(crate_id: int, ident=Depends(require_token), db=Depends crate = (await db.execute(text(""" SELECT c.id, c.name, c.label_text, c.rack_id, c.rack_level_index::int AS level, c.slot_number::int AS slot, c.direction, c.crate_purpose, - r.name AS rack_name + c.updated_at AS last_scanned, c.updated_by, r.name AS rack_name, rt.type AS rack_type, + (SELECT count(*) FROM inventory i WHERE i.crate_id = c.id AND i.in_stock) AS items_count FROM virtual_crate c LEFT JOIN virtual_rack r ON r.id = c.rack_id + LEFT JOIN virtual_rack_type rt ON rt.id = r.rack_type_id WHERE c.id = :id """), {"id": crate_id})).mappings().first() if not crate: raise HTTPException(404, "crate not found") items = [dict(r) for r in (await db.execute(text(""" - SELECT i.sku, i.slot_number::int AS slot, coalesce(i.title, dc.title) AS title, - dc.artist, i.price::float AS price, i.condition, i.in_stock + SELECT i.sku, i.release_id, i.slot_number::int AS slot, coalesce(i.title, dc.title) AS title, + dc.artist, i.price::float AS price, i.condition, i.in_stock, + (SELECT string_agg(DISTINCT g.genre_name, ', ') FROM disc_release_genre g WHERE g.release_id = i.release_id) AS genre, + (SELECT string_agg(DISTINCT s.style_name, ', ') FROM disc_release_style s WHERE s.release_id = i.release_id) AS style FROM inventory i LEFT JOIN disc_cache dc ON dc.release_id = i.release_id WHERE i.crate_id = :id ORDER BY i.slot_number NULLS LAST, i.sku @@ -103,6 +107,29 @@ async def crate_contents(crate_id: int, ident=Depends(require_token), db=Depends return {"ok": True, "crate": dict(crate), "items": items} +@router.get("/release/{release_id}") +async def release_info(release_id: int, ident=Depends(require_token), db=Depends(get_db)): + """Full release metadata (from the disc_release mirror) + every inventory copy + where each lives.""" + dr = (await db.execute(text(""" + SELECT dr.id, dr.title, dr.artists_sort AS artist, dr.country, dr.year, dr.thumb, dr.notes, dr.master_id, + (SELECT string_agg(label_name || coalesce(' ('||catno||')',''), ', ') FROM disc_release_label WHERE release_id=dr.id) AS label, + (SELECT string_agg(name || coalesce(' '||descriptions,''), ', ') FROM disc_release_format WHERE release_id=dr.id) AS format, + (SELECT string_agg(DISTINCT genre_name, ', ') FROM disc_release_genre WHERE release_id=dr.id) AS genre, + (SELECT string_agg(DISTINCT style_name, ', ') FROM disc_release_style WHERE release_id=dr.id) AS style, + (SELECT value FROM disc_release_identifier WHERE release_id=dr.id AND type ILIKE 'barcode' LIMIT 1) AS barcode + FROM disc_release dr WHERE dr.id = :r + """), {"r": release_id})).mappings().first() + items = [dict(r) for r in (await db.execute(text(""" + SELECT i.sku, i.price::float AS price, i.condition, i.sleeve_cond, i.in_stock, + i.crate_id, i.slot_number::int AS slot, c.label_text AS crate, r.name AS rack, + c.rack_level_index::int AS level + FROM inventory i LEFT JOIN virtual_crate c ON c.id = i.crate_id + LEFT JOIN virtual_rack r ON r.id = c.rack_id + WHERE i.release_id = :r ORDER BY i.in_stock DESC, i.sku + """), {"r": release_id})).mappings()] + return {"ok": True, "release": dict(dr) if dr else None, "items": items} + + # ── Reorganize — A-Z re-file planner (server scans + applies; client plans the moves) ────────── class ScanIn(BaseModel): crate_ids: list[int] diff --git a/site/search.html b/site/search.html index 61fd340..3b7e63d 100644 --- a/site/search.html +++ b/site/search.html @@ -57,7 +57,12 @@
-