feat: add SaaS schema migration — sessions, inventory, mappings tables

Creates sessions, warehouse_inventory, inventory_uploads,
inventory_column_mappings tables. Extends users with business_name,
is_active, last_login. Updates roles to ADMIN/OWNER/TALLER/BODEGA.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 22:24:33 +00:00
parent 6ef39d212c
commit c5e5f6ef7e

View File

@@ -0,0 +1,127 @@
#!/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()