-- 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); -- Cached Discogs catalog for enrichment — RecordGod's OWN copy of just the releases it stocks, -- so the store is self-contained: no live dependency on the full Discogs dump OR dealgod's DB. -- Populated by build_disc_cache.py from the full mirror on ultra. CREATE TABLE IF NOT EXISTS disc_cache ( release_id bigint PRIMARY KEY, title text, artist text, thumb text, weight int ); -- Encrypted credential vault. Values are Fernet ciphertext; the key lives in the environment -- (RECORDGOD_SECRET_KEY), never here. Woo/Discogs/Cloudflare/DealGod tokens land here via /settings. CREATE TABLE IF NOT EXISTS app_secret ( name text PRIMARY KEY, value_enc bytea NOT NULL, updated_at timestamptz NOT NULL DEFAULT now() ); -- POS customers (from wp_rmp_disc_sales_customers) — the counter's customer dropdown + add-new. CREATE TABLE IF NOT EXISTS customer ( id bigint PRIMARY KEY, wp_user_id bigint, first_name text NOT NULL DEFAULT '', last_name text DEFAULT '', email text, phone text, address text, is_guest boolean NOT NULL DEFAULT false, created_at timestamptz DEFAULT now() ); CREATE INDEX IF NOT EXISTS customer_name_idx ON customer (lower(first_name), lower(last_name)); CREATE INDEX IF NOT EXISTS customer_email_idx ON customer (lower(email)); -- Named discounts / promotions (from wp_rmp_disc_sales_discounts) — the Promotions tab + POS promo picker. CREATE TABLE IF NOT EXISTS sales_discount ( id bigint PRIMARY KEY, name text NOT NULL, percentage numeric(5,2) NOT NULL DEFAULT 0, is_active boolean NOT NULL DEFAULT true, manual_active boolean NOT NULL DEFAULT false, -- currently switched on at the counter discount_type text DEFAULT 'manual', -- manual|promotion|tax description text, bubble_text text, bubble_color text, start_at timestamptz, end_at timestamptz, created_at timestamptz DEFAULT now() ); -- Sales settings (from wp_rmp_disc_sales_settings) — currency / tax / default discount, key-value. CREATE TABLE IF NOT EXISTS sales_setting ( setting_key text PRIMARY KEY, setting_value text, is_active boolean NOT NULL DEFAULT true, updated_at timestamptz DEFAULT now() ); -- pgvector (semantic search / "records like this" / newsletter copy): add when keyword search -- measurably falls short, not before.