RECORDGOD/schema.sql
type-two d75f27a691 feat: RecordGod founding — service scaffold + MariaDB→Postgres migration
- FastAPI twin of DealGod (async SQLAlchemy/asyncpg). wowplatter/v1: ping,
  inventory/lookup, inventory/intake (enriches from discogs_full by release_id).
- schema v2: unified inventory (vinyl+general), slim crate, sales/sale_items.
  Discogs catalog NOT copied — joined from discogs_full at read time.
- migrate.py: 34,543 shop rows moved (vs ~3.2M in the WowPlatter clone).
- Smoke test green; plan/readme carry the architecture + UI boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:18:50 +10:00

91 lines
4.5 KiB
SQL

-- RecordGod schema v2.
-- ONE inventory table (vinyl + general goods unified), sales history, slim crates.
-- The Discogs CATALOG is deliberately NOT stored here — it lives in the discogs_full Postgres
-- mirror and is joined on release_id at read time. We keep the shop's OWN data only:
-- WowPlatter crammed a ~3.2M-row Discogs clone into the shop DB; RecordGod keeps ~40k real rows.
CREATE TABLE IF NOT EXISTS inventory (
sku text PRIMARY KEY,
store_id int NOT NULL DEFAULT 1, -- always 1 for now; the multi-shop seam
kind text NOT NULL DEFAULT 'vinyl', -- vinyl|book|magazine|comic|card|game|other
release_id bigint, -- Discogs id (vinyl) → join discogs_full.release
identifier text, -- ISBN/UPC/barcode (general goods lookup key)
title text, -- denormalised cache; vinyl filled from mirror at intake
price numeric(10,2),
qty int NOT NULL DEFAULT 1,
in_stock boolean NOT NULL DEFAULT true, -- was enum('y','n')
condition text, -- media grade (vinyl) / condition desc (general)
sleeve_cond text, -- vinyl sleeve grade
brand text,
model text,
weight_g numeric(10,3),
location text,
crate_id int,
slot_number int,
status text DEFAULT 'publish',
notes text,
product_url text,
-- DealGod price intel (the engine writes these)
est_market_value numeric(10,2),
lowest_competitor numeric(10,2),
target_price numeric(10,2),
priced_at timestamptz,
-- Square sync (Square/WP authoritative)
square_item_id text,
square_catalog_object_id text,
square_sku text,
square_synced_at timestamptz,
-- intake / staging (live owns sale-state; local owns intake — see RECORDGOD_PLAN.md §6)
staged boolean NOT NULL DEFAULT false, -- true = intaken locally, not yet published live
attributes jsonb, -- per-kind extras (dimensions, etc.) — was longtext+CHECK
images jsonb,
raw_json jsonb, -- enrichment cache
sold_date timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS inventory_release_idx ON inventory (store_id, release_id) WHERE release_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS inventory_identifier_idx ON inventory (store_id, identifier) WHERE identifier IS NOT NULL;
CREATE INDEX IF NOT EXISTS inventory_instock_idx ON inventory (store_id, in_stock) WHERE in_stock;
CREATE INDEX IF NOT EXISTS inventory_crate_idx ON inventory (crate_id, slot_number);
CREATE INDEX IF NOT EXISTS inventory_staged_idx ON inventory (staged) WHERE staged;
CREATE TABLE IF NOT EXISTS crate (
id int PRIMARY KEY,
name text NOT NULL,
label_text text,
purpose text DEFAULT 'stock', -- stock|spacer|display|returns|hold|sale
notes text
-- 3D geometry (pos/rotation/mesh/genre-lists) intentionally stays in WowPlatter — front-end concern.
);
CREATE TABLE IF NOT EXISTS sales (
id bigint PRIMARY KEY,
sale_number text UNIQUE,
customer_id bigint,
total numeric(10,2) NOT NULL DEFAULT 0, -- consolidated (WP had total + total_amount)
tax_amount numeric(10,2) NOT NULL DEFAULT 0,
status text DEFAULT 'pending', -- consolidated (WP had status + payment_status)
payment_method text,
sale_date timestamptz,
created_at timestamptz
-- WooCommerce/WP stays authoritative for live sale-state; this is the reporting/analytics copy.
);
CREATE INDEX IF NOT EXISTS sales_date_idx ON sales (sale_date);
CREATE TABLE IF NOT EXISTS sale_items (
id bigint PRIMARY KEY,
sale_id bigint REFERENCES sales(id) ON DELETE CASCADE,
sku text,
release_id bigint,
item_name text,
qty int NOT NULL DEFAULT 1,
unit_price numeric(10,2) NOT NULL DEFAULT 0,
line_total numeric(10,2) NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS sale_items_sale_idx ON sale_items (sale_id);
CREATE INDEX IF NOT EXISTS sale_items_release_idx ON sale_items (release_id);
-- pgvector (semantic search / "records like this" / newsletter copy): add when keyword search
-- measurably falls short, not before.