PRICEGOD's overlay quick-add posts the DealGod target/median; store it on inventory.est_market_value so the value lens lights up on staged stock. Verified: intake w/ est_market_value=45.53 → staged row.
105 lines
4.3 KiB
Python
105 lines
4.3 KiB
Python
import base64
|
|
import secrets
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import text
|
|
|
|
from . import __version__
|
|
from .auth import require_token
|
|
from .db import get_db
|
|
from .mirror import enrich as mirror_enrich
|
|
|
|
# The wowplatter/v1 contract — RecordGod's front door (see RECORDGOD_PLAN.md §3).
|
|
# Routes 1, 2, 5 live here. Routes 3-4 (price/labels) are deliberately NOT defined yet:
|
|
# PRICEGOD treats 404 as "pending" and degrades gracefully. ponytail: 404 already says it.
|
|
router = APIRouter(prefix="/wowplatter/v1", tags=["wowplatter/v1"])
|
|
|
|
|
|
@router.get("/ping")
|
|
async def ping(ident=Depends(require_token)):
|
|
"""Connection test + identity — lights PRICEGOD's green dot and store chip."""
|
|
return {"ok": True, "store": ident["store"], "plan": ident["plan"], "version": __version__}
|
|
|
|
|
|
@router.get("/inventory/lookup")
|
|
async def inventory_lookup(
|
|
release_id: int | None = Query(None),
|
|
barcode: str | None = Query(None),
|
|
ident=Depends(require_token),
|
|
db=Depends(get_db),
|
|
):
|
|
""""Do WE have this?" — the most-used call. release_id first, identifier (barcode) fallback."""
|
|
if release_id is None and barcode is None:
|
|
return {"ok": False, "reason": "need release_id or barcode"}
|
|
|
|
where, params = (
|
|
("release_id = :rid", {"rid": release_id})
|
|
if release_id is not None
|
|
else ("identifier = :bc", {"bc": barcode})
|
|
)
|
|
params["sid"] = ident["store_id"]
|
|
|
|
row = (await db.execute(text(
|
|
f"SELECT sku, price, qty, condition, status, product_url, release_id "
|
|
f"FROM inventory WHERE {where} AND store_id = :sid AND in_stock LIMIT 1"
|
|
), params)).mappings().first()
|
|
|
|
if not row:
|
|
return {"ok": True, "in_stock": False, "release_id": release_id}
|
|
return {
|
|
"ok": True, "in_stock": True, **dict(row),
|
|
"price": float(row["price"]) if row["price"] is not None else None,
|
|
}
|
|
|
|
|
|
class IntakeIn(BaseModel):
|
|
release_id: int | None = None
|
|
identifier: str | None = None
|
|
condition: str | None = None
|
|
sleeve: str | None = None
|
|
notes: str | None = None
|
|
price: float | None = None
|
|
est_market_value: float | None = None # DealGod target/median, carried from the PRICEGOD overlay
|
|
sku: str | None = None
|
|
kind: str = "vinyl"
|
|
|
|
|
|
def _new_sku() -> str:
|
|
# ponytail: MRP- + 8 base32 chars ≈ 1e12 space — collisions negligible at shop scale; the
|
|
# sku PK + ON CONFLICT upsert means a clash just updates that row. Tighten if it ever bites.
|
|
return "MRP-" + base64.b32encode(secrets.token_bytes(5)).decode().rstrip("=")[:8]
|
|
|
|
|
|
@router.post("/inventory/intake")
|
|
async def inventory_intake(body: IntakeIn, ident=Depends(require_token), db=Depends(get_db)):
|
|
"""New-stock intake. Enriches from the discogs_full mirror by release_id, stages locally
|
|
(staged=true). Publishing to live is a separate, non-destructive step (PLAN §6)."""
|
|
title = artist = None
|
|
weight = None
|
|
if body.release_id:
|
|
# ponytail: mirror down → stage un-enriched rather than fail the intake (enrich() swallows)
|
|
meta = (await mirror_enrich([body.release_id])).get(body.release_id)
|
|
if meta:
|
|
title, artist, weight = meta["title"], meta["artist"], meta["weight"]
|
|
|
|
sku = body.sku or _new_sku()
|
|
await db.execute(text("""
|
|
INSERT INTO inventory (sku, store_id, kind, release_id, identifier, title, price,
|
|
condition, sleeve_cond, weight_g, notes, est_market_value, staged, status)
|
|
VALUES (:sku, :sid, :kind, :rid, :ident, :title, :price,
|
|
:cond, :sleeve, :wt, :notes, :emv, true, 'staged')
|
|
ON CONFLICT (sku) DO UPDATE SET
|
|
release_id = EXCLUDED.release_id, title = EXCLUDED.title, price = EXCLUDED.price,
|
|
condition = EXCLUDED.condition, sleeve_cond = EXCLUDED.sleeve_cond,
|
|
notes = EXCLUDED.notes, est_market_value = EXCLUDED.est_market_value, updated_at = now()
|
|
"""), {
|
|
"sku": sku, "sid": ident["store_id"], "kind": body.kind, "rid": body.release_id,
|
|
"ident": body.identifier, "title": title, "price": body.price,
|
|
"cond": body.condition, "sleeve": body.sleeve, "wt": weight, "notes": body.notes,
|
|
"emv": body.est_market_value,
|
|
})
|
|
await db.commit()
|
|
return {"ok": True, "sku": sku, "staged": True,
|
|
"title": title, "artist": artist, "enriched": title is not None}
|