-- ═══════════════════════════════════════════════════════════════════════════ -- v4.27 — Yonke M1: used-parts (parts-usadas) data model -- Additive + idempotent. Runs against tenant DBs. -- -- Enables businesses that sell ONLY used parts (yonkes/deshuesaderos): -- * condition (nuevo/usado/reconstruido) + grade (A/B/C/D) -- * unique 1-of-1 pieces (is_unique_piece) with a donor vehicle -- * multiple photos per item (inventory_images) -- * ownership (propio/consigna) + consignment tables -- * warranty expressed in days (for used parts) -- ═══════════════════════════════════════════════════════════════════════════ -- ─── 1. inventory: condition / donor / ownership / unique-piece columns ───── ALTER TABLE inventory ADD COLUMN IF NOT EXISTS condition VARCHAR(20) DEFAULT 'nuevo', ADD COLUMN IF NOT EXISTS condition_grade VARCHAR(2), ADD COLUMN IF NOT EXISTS is_unique_piece BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS serial_number VARCHAR(100), ADD COLUMN IF NOT EXISTS donor_vin VARCHAR(20), ADD COLUMN IF NOT EXISTS donor_make VARCHAR(60), ADD COLUMN IF NOT EXISTS donor_model VARCHAR(60), ADD COLUMN IF NOT EXISTS donor_year INTEGER, ADD COLUMN IF NOT EXISTS donor_mileage_km INTEGER, ADD COLUMN IF NOT EXISTS ownership VARCHAR(20) DEFAULT 'propio', ADD COLUMN IF NOT EXISTS acquisition_cost NUMERIC(12,2); -- CHECK constraints (guarded so re-runs are safe) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'inventory_condition_chk') THEN ALTER TABLE inventory ADD CONSTRAINT inventory_condition_chk CHECK (condition IS NULL OR condition IN ('nuevo','usado','reconstruido')); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'inventory_ownership_chk') THEN ALTER TABLE inventory ADD CONSTRAINT inventory_ownership_chk CHECK (ownership IS NULL OR ownership IN ('propio','consigna')); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'inventory_grade_chk') THEN ALTER TABLE inventory ADD CONSTRAINT inventory_grade_chk CHECK (condition_grade IS NULL OR condition_grade IN ('A','B','C','D')); END IF; END $$; CREATE INDEX IF NOT EXISTS idx_inventory_condition ON inventory(condition); CREATE INDEX IF NOT EXISTS idx_inventory_donor_vin ON inventory(donor_vin) WHERE donor_vin IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_inventory_donor_vehicle ON inventory(donor_make, donor_model, donor_year) WHERE is_unique_piece; -- ─── 2. Relax part_number NOT NULL so codeless unique pieces can exist ────── -- A standard UNIQUE index in PostgreSQL already treats NULLs as distinct, so -- the existing full unique index (idx_inventory_part_unique) keeps enforcing -- uniqueness on non-null codes while allowing many NULLs. We deliberately do -- NOT turn it into a partial index: that would break `ON CONFLICT (part_number)` -- used by the importers. ALTER TABLE inventory ALTER COLUMN part_number DROP NOT NULL; -- ─── 3. inventory_images: multiple photos per item ───────────────────────── CREATE TABLE IF NOT EXISTS inventory_images ( id SERIAL PRIMARY KEY, inventory_id INTEGER NOT NULL REFERENCES inventory(id) ON DELETE CASCADE, url VARCHAR(500) NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, is_primary BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_inventory_images_inv ON inventory_images(inventory_id, sort_order); CREATE UNIQUE INDEX IF NOT EXISTS idx_inventory_images_primary ON inventory_images(inventory_id) WHERE is_primary; -- Migrate the existing single image_url into the new table (first photo). INSERT INTO inventory_images (inventory_id, url, sort_order, is_primary) SELECT i.id, i.image_url, 0, TRUE FROM inventory i WHERE i.image_url IS NOT NULL AND i.image_url <> '' AND NOT EXISTS (SELECT 1 FROM inventory_images ii WHERE ii.inventory_id = i.id); -- ─── 4. warranties: express warranty in days (used parts) ────────────────── DO $$ BEGIN IF to_regclass('public.warranties') IS NOT NULL THEN EXECUTE 'ALTER TABLE warranties ADD COLUMN IF NOT EXISTS warranty_days INTEGER'; END IF; END $$; -- ─── 5. Consignments (third-party parts held for sale) ───────────────────── CREATE TABLE IF NOT EXISTS consignments ( id SERIAL PRIMARY KEY, consignor_name VARCHAR(200) NOT NULL, consignor_rfc VARCHAR(13), consignor_phone VARCHAR(30), commission_pct NUMERIC(5,2) DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'open', -- open | closed | settled notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS consignment_items ( id SERIAL PRIMARY KEY, consignment_id INTEGER NOT NULL REFERENCES consignments(id) ON DELETE CASCADE, inventory_id INTEGER REFERENCES inventory(id) ON DELETE SET NULL, agreed_price NUMERIC(12,2), commission_amount NUMERIC(12,2), status VARCHAR(20) NOT NULL DEFAULT 'in_stock', -- in_stock | sold | returned | settled created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_consignment_items_cons ON consignment_items(consignment_id); CREATE INDEX IF NOT EXISTS idx_consignment_items_inv ON consignment_items(inventory_id);