diff --git a/app/admin_routes.py b/app/admin_routes.py index a34031d..87b483c 100644 --- a/app/admin_routes.py +++ b/app/admin_routes.py @@ -1,5 +1,6 @@ import re import secrets +from datetime import datetime, timedelta import httpx from fastapi import APIRouter, Depends, HTTPException, Query @@ -547,9 +548,24 @@ class BulkIn(BaseModel): value: float | int | str | None = None +async def _fresh_sku(db): + """Canonical SKU = 14-digit product-added timestamp (YYYYMMDDHHMMSS) — the format SCANGOD's EPC/RFID + codec requires (packs SKU 14 digits × 10^9 + release_id onto the UHF chip). Bumps a second on + collision so a same-second batch stays unique against the sku PRIMARY KEY. + ponytail: check-then-insert; a truly concurrent same-second add on another connection could still hit + the PK and be ON CONFLICT DO NOTHING'd. Fine for counter/admin stock entry; add retry-on-conflict if + bulk-import concurrency grows.""" + base = datetime.now() + for i in range(120): + sku = (base + timedelta(seconds=i)).strftime("%Y%m%d%H%M%S") + if not (await db.execute(text("SELECT 1 FROM inventory WHERE sku = :s"), {"s": sku})).first(): + return sku + return (base + timedelta(seconds=120)).strftime("%Y%m%d%H%M%S") + + @router.post("/inventory/add") async def inv_add(body: InvItemIn, ident=Depends(require_token), db=Depends(get_db)): - sku = (body.sku or "").strip() or ("RG" + secrets.token_hex(4).upper()) + sku = (body.sku or "").strip() or await _fresh_sku(db) title = body.title if body.release_id and not title: # pull the cached title if intaking by release r = (await db.execute(text("SELECT title FROM disc_cache WHERE release_id=:r"), @@ -629,7 +645,7 @@ async def inv_import(body: ImportIn, ident=Depends(require_token), db=Depends(ge if not rid: not_found.append(ln) continue - sku = "RG" + secrets.token_hex(4).upper() + sku = await _fresh_sku(db) await db.execute(text(""" INSERT INTO inventory (sku, store_id, kind, release_id, title, price, qty, in_stock, condition, crate_id, status, created_at, updated_at)