#!/usr/bin/env python3 """ Migration: SaaS schema changes - Update roles (ADMIN, OWNER, TALLER, BODEGA) - Extend users table with business_name, is_active, created_at, last_login - Create sessions, warehouse_inventory, inventory_uploads, inventory_column_mappings tables - Add indexes """ import psycopg2 import sys DB_URL = "postgresql://nexus:nexus_autoparts_2026@localhost/nexus_autoparts" STATEMENTS = [ # ── Roles: UPSERT to desired values ── """ INSERT INTO roles (id_rol, name_rol) OVERRIDING SYSTEM VALUE VALUES (1, 'ADMIN') ON CONFLICT (id_rol) DO UPDATE SET name_rol = EXCLUDED.name_rol; """, """ INSERT INTO roles (id_rol, name_rol) OVERRIDING SYSTEM VALUE VALUES (2, 'OWNER') ON CONFLICT (id_rol) DO UPDATE SET name_rol = EXCLUDED.name_rol; """, """ INSERT INTO roles (id_rol, name_rol) OVERRIDING SYSTEM VALUE VALUES (3, 'TALLER') ON CONFLICT (id_rol) DO UPDATE SET name_rol = EXCLUDED.name_rol; """, """ INSERT INTO roles (id_rol, name_rol) OVERRIDING SYSTEM VALUE VALUES (4, 'BODEGA') ON CONFLICT (id_rol) DO UPDATE SET name_rol = EXCLUDED.name_rol; """, # ── Extend users table ── "ALTER TABLE users ADD COLUMN IF NOT EXISTS business_name VARCHAR(200);", "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT false;", "ALTER TABLE users ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT now();", "ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login TIMESTAMP;", # ── Unique index on users(email) ── """ CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email); """, # ── Sessions table ── """ CREATE TABLE IF NOT EXISTS sessions ( id_session SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id_user) ON DELETE CASCADE, refresh_token VARCHAR(500) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT now() ); """, # ── Warehouse inventory table ── """ CREATE TABLE IF NOT EXISTS warehouse_inventory ( id_inventory BIGSERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id_user), part_id INTEGER NOT NULL REFERENCES parts(id_part), price NUMERIC(12,2), stock_quantity INTEGER DEFAULT 0, min_order_quantity INTEGER DEFAULT 1, warehouse_location VARCHAR(100) DEFAULT 'Principal', updated_at TIMESTAMP DEFAULT now(), UNIQUE(user_id, part_id, warehouse_location) ); """, # ── Inventory uploads table ── """ CREATE TABLE IF NOT EXISTS inventory_uploads ( id_upload SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id_user), filename VARCHAR(200), status VARCHAR(20) DEFAULT 'pending', rows_total INTEGER, rows_imported INTEGER, rows_errors INTEGER, error_log TEXT, created_at TIMESTAMP DEFAULT now(), completed_at TIMESTAMP ); """, # ── Inventory column mappings table ── """ CREATE TABLE IF NOT EXISTS inventory_column_mappings ( id_mapping SERIAL PRIMARY KEY, user_id INTEGER NOT NULL UNIQUE REFERENCES users(id_user), mapping JSONB DEFAULT '{}'::jsonb ); """, # ── Indexes ── "CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(refresh_token);", "CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);", "CREATE INDEX IF NOT EXISTS idx_wi_part ON warehouse_inventory(part_id);", "CREATE INDEX IF NOT EXISTS idx_wi_user ON warehouse_inventory(user_id);", # ── Activate existing admin users ── "UPDATE users SET is_active = true WHERE id_rol = 1;", ] def main(): conn = psycopg2.connect(DB_URL) conn.autocommit = True cur = conn.cursor() for i, sql in enumerate(STATEMENTS, 1): label = sql.strip().split('\n')[0].strip()[:80] try: cur.execute(sql) print(f" [{i:2d}/{len(STATEMENTS)}] OK {label}") except Exception as e: print(f" [{i:2d}/{len(STATEMENTS)}] ERR {label}\n {e}") sys.exit(1) cur.close() conn.close() print(f"\nMigration complete — {len(STATEMENTS)} statements executed.") if __name__ == "__main__": main()