""" Migration script to add Newbook PMS integration tables and settings: - Newbook credential fields on kitchen_settings - newbook_gl_accounts table - newbook_daily_revenue table - newbook_daily_occupancy table - newbook_sync_log table Run this script once after deploying the new code. """ import asyncio import logging from sqlalchemy import text from database import engine logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) async def run_migration(): """Add Newbook integration tables and settings columns.""" logger.info("Running Newbook integration migrations...") # Add Newbook columns to kitchen_settings table settings_columns = [ "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_username VARCHAR(255)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_password VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_key VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_region VARCHAR(10)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_instance_id VARCHAR(100)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_last_sync TIMESTAMP", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_auto_sync_enabled BOOLEAN DEFAULT FALSE", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_breakfast_gl_codes VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_dinner_gl_codes VARCHAR(500)", ] for sql in settings_columns: try: async with engine.begin() as conn: await conn.execute(text(sql)) col_name = sql.split("ADD COLUMN")[1].split()[0] logger.info(f"Added column: {col_name}") except Exception as e: if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): logger.info(f"Column already exists, skipping") else: logger.warning(f"Column migration warning: {e}") # Create newbook_gl_accounts table create_gl_accounts = """ CREATE TABLE IF NOT EXISTS newbook_gl_accounts ( id SERIAL PRIMARY KEY, kitchen_id INTEGER NOT NULL REFERENCES kitchens(id), gl_account_id VARCHAR(50) NOT NULL, gl_code VARCHAR(50), gl_name VARCHAR(255) NOT NULL, gl_type VARCHAR(100), is_tracked BOOLEAN DEFAULT FALSE, display_order INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_newbook_gl_account UNIQUE(kitchen_id, gl_account_id) ) """ try: async with engine.begin() as conn: await conn.execute(text(create_gl_accounts)) logger.info("Created newbook_gl_accounts table") except Exception as e: logger.warning(f"newbook_gl_accounts table: {e}") # Create newbook_daily_revenue table create_daily_revenue = """ CREATE TABLE IF NOT EXISTS newbook_daily_revenue ( id SERIAL PRIMARY KEY, kitchen_id INTEGER NOT NULL REFERENCES kitchens(id), gl_account_id INTEGER NOT NULL REFERENCES newbook_gl_accounts(id), date DATE NOT NULL, amount_net NUMERIC(12, 2) NOT NULL, amount_gross NUMERIC(12, 2), fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_newbook_revenue_per_day UNIQUE(kitchen_id, gl_account_id, date) ) """ try: async with engine.begin() as conn: await conn.execute(text(create_daily_revenue)) logger.info("Created newbook_daily_revenue table") except Exception as e: logger.warning(f"newbook_daily_revenue table: {e}") # Create newbook_daily_occupancy table create_daily_occupancy = """ CREATE TABLE IF NOT EXISTS newbook_daily_occupancy ( id SERIAL PRIMARY KEY, kitchen_id INTEGER NOT NULL REFERENCES kitchens(id), date DATE NOT NULL, total_rooms INTEGER, occupied_rooms INTEGER, occupancy_percentage NUMERIC(5, 2), total_guests INTEGER, breakfast_allocation_qty INTEGER, breakfast_allocation_netvalue NUMERIC(12, 2), dinner_allocation_qty INTEGER, dinner_allocation_netvalue NUMERIC(12, 2), is_forecast BOOLEAN DEFAULT FALSE, fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_newbook_occupancy_per_day UNIQUE(kitchen_id, date) ) """ try: async with engine.begin() as conn: await conn.execute(text(create_daily_occupancy)) logger.info("Created newbook_daily_occupancy table") except Exception as e: logger.warning(f"newbook_daily_occupancy table: {e}") # Create newbook_sync_log table create_sync_log = """ CREATE TABLE IF NOT EXISTS newbook_sync_log ( id SERIAL PRIMARY KEY, kitchen_id INTEGER NOT NULL REFERENCES kitchens(id), sync_type VARCHAR(50) NOT NULL, started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP, status VARCHAR(20) DEFAULT 'running', records_fetched INTEGER DEFAULT 0, error_message TEXT, date_from DATE, date_to DATE ) """ try: async with engine.begin() as conn: await conn.execute(text(create_sync_log)) logger.info("Created newbook_sync_log table") except Exception as e: logger.warning(f"newbook_sync_log table: {e}") # Create newbook_room_categories table create_room_categories = """ CREATE TABLE IF NOT EXISTS newbook_room_categories ( id SERIAL PRIMARY KEY, kitchen_id INTEGER NOT NULL REFERENCES kitchens(id), site_id VARCHAR(50) NOT NULL, site_name VARCHAR(255) NOT NULL, site_type VARCHAR(100), room_count INTEGER DEFAULT 0, is_included BOOLEAN DEFAULT TRUE, display_order INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_newbook_room_category UNIQUE(kitchen_id, site_id) ) """ try: async with engine.begin() as conn: await conn.execute(text(create_room_categories)) logger.info("Created newbook_room_categories table") except Exception as e: logger.warning(f"newbook_room_categories table: {e}") # Add room_count column if it doesn't exist try: async with engine.begin() as conn: await conn.execute(text("ALTER TABLE newbook_room_categories ADD COLUMN IF NOT EXISTS room_count INTEGER DEFAULT 0")) logger.info("Added room_count column to newbook_room_categories") except Exception as e: if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): logger.info("room_count column already exists, skipping") else: logger.warning(f"room_count column: {e}") # Create indexes indexes = [ "CREATE INDEX IF NOT EXISTS ix_newbook_gl_accounts_kitchen_id ON newbook_gl_accounts(kitchen_id)", "CREATE INDEX IF NOT EXISTS ix_newbook_revenue_date ON newbook_daily_revenue(date)", "CREATE INDEX IF NOT EXISTS ix_newbook_revenue_kitchen_id ON newbook_daily_revenue(kitchen_id)", "CREATE INDEX IF NOT EXISTS ix_newbook_occupancy_date ON newbook_daily_occupancy(date)", "CREATE INDEX IF NOT EXISTS ix_newbook_occupancy_kitchen_id ON newbook_daily_occupancy(kitchen_id)", "CREATE INDEX IF NOT EXISTS ix_newbook_sync_log_kitchen_id ON newbook_sync_log(kitchen_id)", "CREATE INDEX IF NOT EXISTS ix_newbook_room_categories_kitchen_id ON newbook_room_categories(kitchen_id)", ] for sql in indexes: try: async with engine.begin() as conn: await conn.execute(text(sql)) except Exception as e: logger.warning(f"Index: {e}") logger.info("Newbook migration completed successfully!") if __name__ == "__main__": asyncio.run(run_migration())