feat(sku): canonical 14-digit timestamp SKUs so RFID/EPC tagging works (new items)

RecordGod generated 'RG'+random-hex SKUs, which the SCANGOD EPC codec rejects
(it requires 14 digits: SKU × 10^9 + release_id packed onto the UHF chip). New
inventory now gets a 14-digit product-added timestamp (YYYYMMDDHHMMSS) via
_fresh_sku(), which bumps a second on collision to stay unique against the sku
PRIMARY KEY. release_id stays its own column (the sku+space+release_id string is
only the physical chip encoding, which the codec already builds).

New items only — existing 'RG' SKUs are untouched (they keep working; they just
aren't RFID-taggable until re-SKU'd, a separate migration if ever wanted).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-05 03:34:27 +10:00
parent 61601f1067
commit 00e08a86dd

View File

@ -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)