kitchen/backend/migrations/add_dext_integration.py
jtricerolph bc874ac9ff Fix migration transaction poisoning: ADD COLUMN IF NOT EXISTS
Migrations used try/except around ADD COLUMN inside a single engine.begin()
block. When a 'column already exists' error was caught, asyncpg left the
transaction in aborted state, causing all subsequent DDL in the block to fail
with InFailedSQLTransactionError. Replace with IF NOT EXISTS to prevent the
error entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:53:26 +00:00

215 lines
8.6 KiB
Python

"""
Migration script to add Dext integration features:
- Invoice notes field
- Dext sent tracking (sent_at, sent_by_user_id)
- SMTP configuration in kitchen_settings
- Dext configuration in kitchen_settings
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 new columns and tables for Dext integration."""
logger.info("Running Dext integration database migrations...")
# ===== INVOICE TABLE MIGRATIONS =====
# Add notes column to invoices table
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN IF NOT EXISTS notes TEXT"
))
logger.info("Added notes column to invoices")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("notes column already exists")
else:
logger.warning(f"notes column: {e}")
# Add dext_sent_at column to invoices table
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN IF NOT EXISTS dext_sent_at TIMESTAMP"
))
logger.info("Added dext_sent_at column to invoices")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_sent_at column already exists")
else:
logger.warning(f"dext_sent_at column: {e}")
# Add dext_sent_by_user_id column to invoices table
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN IF NOT EXISTS dext_sent_by_user_id INTEGER REFERENCES users(id)"
))
logger.info("Added dext_sent_by_user_id column to invoices")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_sent_by_user_id column already exists")
else:
logger.warning(f"dext_sent_by_user_id column: {e}")
# ===== KITCHEN_SETTINGS TABLE MIGRATIONS - SMTP =====
# Add smtp_host column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_host VARCHAR(255)"
))
logger.info("Added smtp_host column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_host column already exists")
else:
logger.warning(f"smtp_host column: {e}")
# Add smtp_port column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_port INTEGER DEFAULT 587"
))
logger.info("Added smtp_port column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_port column already exists")
else:
logger.warning(f"smtp_port column: {e}")
# Add smtp_username column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_username VARCHAR(255)"
))
logger.info("Added smtp_username column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_username column already exists")
else:
logger.warning(f"smtp_username column: {e}")
# Add smtp_password column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_password VARCHAR(500)"
))
logger.info("Added smtp_password column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_password column already exists")
else:
logger.warning(f"smtp_password column: {e}")
# Add smtp_use_tls column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_use_tls BOOLEAN DEFAULT TRUE"
))
logger.info("Added smtp_use_tls column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_use_tls column already exists")
else:
logger.warning(f"smtp_use_tls column: {e}")
# Add smtp_from_email column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_from_email VARCHAR(255)"
))
logger.info("Added smtp_from_email column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_from_email column already exists")
else:
logger.warning(f"smtp_from_email column: {e}")
# Add smtp_from_name column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_from_name VARCHAR(255) DEFAULT 'Kitchen Invoice System'"
))
logger.info("Added smtp_from_name column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("smtp_from_name column already exists")
else:
logger.warning(f"smtp_from_name column: {e}")
# ===== KITCHEN_SETTINGS TABLE MIGRATIONS - DEXT =====
# Add dext_email column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS dext_email VARCHAR(255)"
))
logger.info("Added dext_email column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_email column already exists")
else:
logger.warning(f"dext_email column: {e}")
# Add dext_include_notes column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS dext_include_notes BOOLEAN DEFAULT TRUE"
))
logger.info("Added dext_include_notes column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_include_notes column already exists")
else:
logger.warning(f"dext_include_notes column: {e}")
# Add dext_include_non_stock column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS dext_include_non_stock BOOLEAN DEFAULT TRUE"
))
logger.info("Added dext_include_non_stock column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_include_non_stock column already exists")
else:
logger.warning(f"dext_include_non_stock column: {e}")
# Add dext_auto_send_enabled column
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS dext_auto_send_enabled BOOLEAN DEFAULT FALSE"
))
logger.info("Added dext_auto_send_enabled column to kitchen_settings")
except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
logger.info("dext_auto_send_enabled column already exists")
else:
logger.warning(f"dext_auto_send_enabled column: {e}")
logger.info("Dext integration migration completed successfully!")
if __name__ == "__main__":
asyncio.run(run_migration())