fix(backend): create accepts capture-only payload; fix INSERT binding count

create_slab required ALL ALLOWED_FIELDS present, but the Review screen fills
taxonomy/content after create (CREATE.md §1). Now requires only the capture
fields (id, sku, dims, widths); the rest stay nullable for Review.

Also fixed a pre-existing off-by-one: vals re-filtered cols by 'in data',
dropping the 6 server-managed columns and under-counting bindings (19 vs 18).
Split allowed_cols/server_cols so vals always matches cols.

Verified: 9/9 slabs tests pass; live POST /slabs with the app's capture-math
payload returns 201.
This commit is contained in:
Ty
2026-09-20 14:45:32 -07:00
parent 1133201f30
commit c8d6d5eda0
+18 -3
View File
@@ -95,9 +95,19 @@ def _check_taxonomy(conn, table: str, val: Any) -> bool:
return conn.execute(f"SELECT 1 FROM {table} WHERE id=?", (val,)).fetchone() is not None
# Capture-only fields required to create a slab. The Review screen fills the
# taxonomy/content fields after create (CREATE.md §1: "every other field stays
# editable until create"), so create must accept a capture-math payload.
# client_rev is server-managed (set to 1 at create) — never required from the client.
REQUIRED_CREATE_FIELDS = [
"id", "sku", "length_in", "thickness_in", "sqft", "bdft",
"width_min_in", "width_max_in", "width_avg_in",
]
@router.post("", status_code=201)
async def create_slab(payload: Dict[str, Any]) -> Dict[str, Any]:
missing = [f for f in ALLOWED_FIELDS if f not in payload]
missing = [f for f in REQUIRED_CREATE_FIELDS if f not in payload]
if missing:
return _err("validation", "missing fields", {"fields": missing}, status=400)
conn = get_connection()
@@ -105,9 +115,14 @@ async def create_slab(payload: Dict[str, Any]) -> Dict[str, Any]:
return _err("duplicate_sku", "SKU already exists", {"sku": payload["sku"]}, status=409)
now = _now_iso()
data = _encode(payload)
cols = [f for f in ALLOWED_FIELDS if f in data] + ["status", "client_rev", "server_rev", "deleted_at", "created_at", "updated_at"]
vals = [data[f] for f in cols if f in data] + ["calibrated", 1, 1, None, now, now]
allowed_cols = [f for f in ALLOWED_FIELDS if f in data]
server_cols = ["status", "client_rev", "server_rev", "deleted_at", "created_at", "updated_at"]
server_vals = ["calibrated", 1, 1, None, now, now]
cols = allowed_cols + server_cols
# One value per col, in cols order: data values for the allowed fields, then the 6 server values.
vals = [data[f] for f in allowed_cols] + server_vals
ph = ",".join(["?"] * len(cols))
assert len(vals) == len(cols), f"create_slab binding mismatch: {len(vals)} vals vs {len(cols)} cols"
conn.execute(f"INSERT INTO slabs ({', '.join(cols)}) VALUES ({ph})", vals)
conn.commit()
row = conn.execute("SELECT * FROM slabs WHERE id=?", (payload["id"],)).fetchone()