- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
114 lines
5.9 KiB
SQL
114 lines
5.9 KiB
SQL
-- ═══════════════════════════════════════════════════════════════════════════
|
|
-- 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);
|