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:
parent
61601f1067
commit
00e08a86dd
@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
@ -547,9 +548,24 @@ class BulkIn(BaseModel):
|
|||||||
value: float | int | str | None = None
|
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")
|
@router.post("/inventory/add")
|
||||||
async def inv_add(body: InvItemIn, ident=Depends(require_token), db=Depends(get_db)):
|
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
|
title = body.title
|
||||||
if body.release_id and not title: # pull the cached title if intaking by release
|
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"),
|
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:
|
if not rid:
|
||||||
not_found.append(ln)
|
not_found.append(ln)
|
||||||
continue
|
continue
|
||||||
sku = "RG" + secrets.token_hex(4).upper()
|
sku = await _fresh_sku(db)
|
||||||
await db.execute(text("""
|
await db.execute(text("""
|
||||||
INSERT INTO inventory (sku, store_id, kind, release_id, title, price, qty, in_stock, condition,
|
INSERT INTO inventory (sku, store_id, kind, release_id, title, price, qty, in_stock, condition,
|
||||||
crate_id, status, created_at, updated_at)
|
crate_id, status, created_at, updated_at)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user