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:
127
scripts/migrate_saas_schema.py
Normal file
127
scripts/migrate_saas_schema.py
Normal 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()
|
||||||
Reference in New Issue
Block a user