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>
This commit is contained in:
jtricerolph 2026-07-12 19:53:26 +00:00
parent e7e1fda9f6
commit bc874ac9ff
25 changed files with 302 additions and 302 deletions

View file

@ -16,7 +16,7 @@ async def run_migration():
"""Add admin_restricted_pages column to kitchen_settings table.""" """Add admin_restricted_pages column to kitchen_settings table."""
logger.info("Running admin restricted pages migration...") logger.info("Running admin restricted pages migration...")
sql = "ALTER TABLE kitchen_settings ADD COLUMN admin_restricted_pages TEXT" sql = "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS admin_restricted_pages TEXT"
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:

View file

@ -19,7 +19,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE kitchen_settings ALTER TABLE kitchen_settings
ADD COLUMN forecast_api_url VARCHAR(500) ADD COLUMN IF NOT EXISTS forecast_api_url VARCHAR(500)
""" """
)) ))
print("+ Added forecast_api_url column") print("+ Added forecast_api_url column")
@ -34,7 +34,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE kitchen_settings ALTER TABLE kitchen_settings
ADD COLUMN forecast_api_key VARCHAR(500) ADD COLUMN IF NOT EXISTS forecast_api_key VARCHAR(500)
""" """
)) ))
print("+ Added forecast_api_key column") print("+ Added forecast_api_key column")
@ -49,7 +49,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE kitchen_settings ALTER TABLE kitchen_settings
ADD COLUMN budget_gp_target NUMERIC(5,2) DEFAULT 65.00 ADD COLUMN IF NOT EXISTS budget_gp_target NUMERIC(5,2) DEFAULT 65.00
""" """
)) ))
print("+ Added budget_gp_target column") print("+ Added budget_gp_target column")
@ -64,7 +64,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE kitchen_settings ALTER TABLE kitchen_settings
ADD COLUMN budget_lookback_weeks INTEGER DEFAULT 4 ADD COLUMN IF NOT EXISTS budget_lookback_weeks INTEGER DEFAULT 4
""" """
)) ))
print("+ Added budget_lookback_weeks column") print("+ Added budget_lookback_weeks column")

View file

@ -112,7 +112,7 @@ async def migrate():
# Add cost_distribution_max_days to kitchen_settings # Add cost_distribution_max_days to kitchen_settings
try: try:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN cost_distribution_max_days INTEGER DEFAULT 90" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS cost_distribution_max_days INTEGER DEFAULT 90"
)) ))
print("+ Added cost_distribution_max_days to kitchen_settings") print("+ Added cost_distribution_max_days to kitchen_settings")
except Exception as e: except Exception as e:

View file

@ -26,7 +26,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN notes TEXT" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS notes TEXT"
)) ))
logger.info("Added notes column to invoices") logger.info("Added notes column to invoices")
except Exception as e: except Exception as e:
@ -39,7 +39,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN dext_sent_at TIMESTAMP" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS dext_sent_at TIMESTAMP"
)) ))
logger.info("Added dext_sent_at column to invoices") logger.info("Added dext_sent_at column to invoices")
except Exception as e: except Exception as e:
@ -52,7 +52,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN dext_sent_by_user_id INTEGER REFERENCES users(id)" "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") logger.info("Added dext_sent_by_user_id column to invoices")
except Exception as e: except Exception as e:
@ -67,7 +67,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_host VARCHAR(255)" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_host VARCHAR(255)"
)) ))
logger.info("Added smtp_host column to kitchen_settings") logger.info("Added smtp_host column to kitchen_settings")
except Exception as e: except Exception as e:
@ -80,7 +80,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_port INTEGER DEFAULT 587" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_port INTEGER DEFAULT 587"
)) ))
logger.info("Added smtp_port column to kitchen_settings") logger.info("Added smtp_port column to kitchen_settings")
except Exception as e: except Exception as e:
@ -93,7 +93,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_username VARCHAR(255)" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_username VARCHAR(255)"
)) ))
logger.info("Added smtp_username column to kitchen_settings") logger.info("Added smtp_username column to kitchen_settings")
except Exception as e: except Exception as e:
@ -106,7 +106,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_password VARCHAR(500)" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_password VARCHAR(500)"
)) ))
logger.info("Added smtp_password column to kitchen_settings") logger.info("Added smtp_password column to kitchen_settings")
except Exception as e: except Exception as e:
@ -119,7 +119,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_use_tls BOOLEAN DEFAULT TRUE" "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") logger.info("Added smtp_use_tls column to kitchen_settings")
except Exception as e: except Exception as e:
@ -132,7 +132,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_from_email VARCHAR(255)" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS smtp_from_email VARCHAR(255)"
)) ))
logger.info("Added smtp_from_email column to kitchen_settings") logger.info("Added smtp_from_email column to kitchen_settings")
except Exception as e: except Exception as e:
@ -145,7 +145,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN smtp_from_name VARCHAR(255) DEFAULT 'Kitchen Invoice System'" "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") logger.info("Added smtp_from_name column to kitchen_settings")
except Exception as e: except Exception as e:
@ -160,7 +160,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN dext_email VARCHAR(255)" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS dext_email VARCHAR(255)"
)) ))
logger.info("Added dext_email column to kitchen_settings") logger.info("Added dext_email column to kitchen_settings")
except Exception as e: except Exception as e:
@ -173,7 +173,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN dext_include_notes BOOLEAN DEFAULT TRUE" "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") logger.info("Added dext_include_notes column to kitchen_settings")
except Exception as e: except Exception as e:
@ -186,7 +186,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN dext_include_non_stock BOOLEAN DEFAULT TRUE" "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") logger.info("Added dext_include_non_stock column to kitchen_settings")
except Exception as e: except Exception as e:
@ -199,7 +199,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE kitchen_settings ADD COLUMN dext_auto_send_enabled BOOLEAN DEFAULT FALSE" "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") logger.info("Added dext_auto_send_enabled column to kitchen_settings")
except Exception as e: except Exception as e:

View file

@ -13,7 +13,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE kitchen_settings ALTER TABLE kitchen_settings
ADD COLUMN dext_manual_send_enabled BOOLEAN DEFAULT TRUE ADD COLUMN IF NOT EXISTS dext_manual_send_enabled BOOLEAN DEFAULT TRUE
""" """
)) ))
print("✓ Added dext_manual_send_enabled column") print("✓ Added dext_manual_send_enabled column")

View file

@ -28,7 +28,7 @@ async def run_migration():
logger.info("Adding 'public_hash' column to dispute_attachments table") logger.info("Adding 'public_hash' column to dispute_attachments table")
await conn.execute(text(""" await conn.execute(text("""
ALTER TABLE dispute_attachments ALTER TABLE dispute_attachments
ADD COLUMN public_hash VARCHAR(64) UNIQUE; ADD COLUMN IF NOT EXISTS public_hash VARCHAR(64) UNIQUE;
""")) """))
# Create index for fast lookups # Create index for fast lookups

View file

@ -1,22 +1,22 @@
"""Add gross_sell_price column to recipes table.""" """Add gross_sell_price column to recipes table."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipes' AND column_name = 'gross_sell_price'" "WHERE table_name = 'recipes' AND column_name = 'gross_sell_price'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipes.gross_sell_price already exists, skipping") logger.info("recipes.gross_sell_price already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipes ADD COLUMN gross_sell_price NUMERIC(10,2) DEFAULT NULL" "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS gross_sell_price NUMERIC(10,2) DEFAULT NULL"
)) ))
logger.info("Added recipes.gross_sell_price column") logger.info("Added recipes.gross_sell_price column")

View file

@ -22,11 +22,11 @@ async def run_migration():
# Run each migration in its own transaction # Run each migration in its own transaction
migrations = [ migrations = [
"ALTER TABLE invoices ADD COLUMN document_type VARCHAR(50) DEFAULT 'invoice'", "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS document_type VARCHAR(50) DEFAULT 'invoice'",
"ALTER TABLE invoices ADD COLUMN order_number VARCHAR(100)", "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS order_number VARCHAR(100)",
"ALTER TABLE invoices ADD COLUMN duplicate_status VARCHAR(50)", "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS duplicate_status VARCHAR(50)",
"ALTER TABLE invoices ADD COLUMN duplicate_of_id INTEGER REFERENCES invoices(id)", "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS duplicate_of_id INTEGER REFERENCES invoices(id)",
"ALTER TABLE invoices ADD COLUMN related_document_id INTEGER REFERENCES invoices(id)", "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS related_document_id INTEGER REFERENCES invoices(id)",
] ]
for sql in migrations: for sql in migrations:
@ -76,7 +76,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE suppliers ADD COLUMN aliases JSON DEFAULT '[]'" "ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS aliases JSON DEFAULT '[]'"
)) ))
logger.info("Added aliases column to suppliers") logger.info("Added aliases column to suppliers")
except Exception as e: except Exception as e:
@ -89,7 +89,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN net_total NUMERIC(10, 2)" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS net_total NUMERIC(10, 2)"
)) ))
logger.info("Added net_total column to invoices") logger.info("Added net_total column to invoices")
except Exception as e: except Exception as e:
@ -102,7 +102,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN is_non_stock BOOLEAN DEFAULT FALSE" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS is_non_stock BOOLEAN DEFAULT FALSE"
)) ))
logger.info("Added is_non_stock column to line_items") logger.info("Added is_non_stock column to line_items")
except Exception as e: except Exception as e:
@ -115,7 +115,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN vendor_name VARCHAR(255)" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS vendor_name VARCHAR(255)"
)) ))
logger.info("Added vendor_name column to invoices") logger.info("Added vendor_name column to invoices")
except Exception as e: except Exception as e:
@ -128,7 +128,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN ocr_raw_json TEXT" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS ocr_raw_json TEXT"
)) ))
logger.info("Added ocr_raw_json column to invoices") logger.info("Added ocr_raw_json column to invoices")
except Exception as e: except Exception as e:
@ -141,7 +141,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE invoices ADD COLUMN supplier_match_type VARCHAR(20)" "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS supplier_match_type VARCHAR(20)"
)) ))
logger.info("Added supplier_match_type column to invoices") logger.info("Added supplier_match_type column to invoices")
except Exception as e: except Exception as e:
@ -189,7 +189,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN unit VARCHAR(50)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS unit VARCHAR(50)"
)) ))
logger.info("Added unit column to line_items") logger.info("Added unit column to line_items")
except Exception as e: except Exception as e:
@ -202,7 +202,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN order_quantity NUMERIC(10, 3)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS order_quantity NUMERIC(10, 3)"
)) ))
logger.info("Added order_quantity column to line_items") logger.info("Added order_quantity column to line_items")
except Exception as e: except Exception as e:
@ -215,7 +215,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN tax_rate VARCHAR(50)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS tax_rate VARCHAR(50)"
)) ))
logger.info("Added tax_rate column to line_items") logger.info("Added tax_rate column to line_items")
except Exception as e: except Exception as e:
@ -228,7 +228,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN tax_amount NUMERIC(10, 2)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS tax_amount NUMERIC(10, 2)"
)) ))
logger.info("Added tax_amount column to line_items") logger.info("Added tax_amount column to line_items")
except Exception as e: except Exception as e:
@ -241,7 +241,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN raw_content TEXT" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS raw_content TEXT"
)) ))
logger.info("Added raw_content column to line_items") logger.info("Added raw_content column to line_items")
except Exception as e: except Exception as e:
@ -254,7 +254,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN pack_quantity INTEGER" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS pack_quantity INTEGER"
)) ))
logger.info("Added pack_quantity column to line_items") logger.info("Added pack_quantity column to line_items")
except Exception as e: except Exception as e:
@ -267,7 +267,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN unit_size NUMERIC(10, 3)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS unit_size NUMERIC(10, 3)"
)) ))
logger.info("Added unit_size column to line_items") logger.info("Added unit_size column to line_items")
except Exception as e: except Exception as e:
@ -280,7 +280,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN unit_size_type VARCHAR(10)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS unit_size_type VARCHAR(10)"
)) ))
logger.info("Added unit_size_type column to line_items") logger.info("Added unit_size_type column to line_items")
except Exception as e: except Exception as e:
@ -293,7 +293,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN portions_per_unit INTEGER DEFAULT 1" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS portions_per_unit INTEGER DEFAULT 1"
)) ))
logger.info("Added portions_per_unit column to line_items") logger.info("Added portions_per_unit column to line_items")
except Exception as e: except Exception as e:
@ -306,7 +306,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN cost_per_item NUMERIC(10, 4)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS cost_per_item NUMERIC(10, 4)"
)) ))
logger.info("Added cost_per_item column to line_items") logger.info("Added cost_per_item column to line_items")
except Exception as e: except Exception as e:
@ -319,7 +319,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE line_items ADD COLUMN cost_per_portion NUMERIC(10, 4)" "ALTER TABLE line_items ADD COLUMN IF NOT EXISTS cost_per_portion NUMERIC(10, 4)"
)) ))
logger.info("Added cost_per_portion column to line_items") logger.info("Added cost_per_portion column to line_items")
except Exception as e: except Exception as e:
@ -384,7 +384,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE product_definitions ADD COLUMN saved_by_user_id INTEGER REFERENCES users(id)" "ALTER TABLE product_definitions ADD COLUMN IF NOT EXISTS saved_by_user_id INTEGER REFERENCES users(id)"
)) ))
logger.info("Added saved_by_user_id column to product_definitions") logger.info("Added saved_by_user_id column to product_definitions")
except Exception as e: except Exception as e:
@ -397,7 +397,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE product_definitions ADD COLUMN source_invoice_id INTEGER REFERENCES invoices(id) ON DELETE SET NULL" "ALTER TABLE product_definitions ADD COLUMN IF NOT EXISTS source_invoice_id INTEGER REFERENCES invoices(id) ON DELETE SET NULL"
)) ))
logger.info("Added source_invoice_id column to product_definitions") logger.info("Added source_invoice_id column to product_definitions")
except Exception as e: except Exception as e:
@ -410,7 +410,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE product_definitions ADD COLUMN source_invoice_number VARCHAR(100)" "ALTER TABLE product_definitions ADD COLUMN IF NOT EXISTS source_invoice_number VARCHAR(100)"
)) ))
logger.info("Added source_invoice_number column to product_definitions") logger.info("Added source_invoice_number column to product_definitions")
except Exception as e: except Exception as e:

View file

@ -23,7 +23,7 @@ async def migrate():
for col_name, col_type in COLUMNS: for col_name, col_type in COLUMNS:
try: try:
await conn.execute(text( await conn.execute(text(
f"ALTER TABLE kitchen_settings ADD COLUMN {col_name} {col_type}" f"ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS {col_name} {col_type}"
)) ))
print(f"+ Added {col_name} column to kitchen_settings") print(f"+ Added {col_name} column to kitchen_settings")
except Exception as e: except Exception as e:

View file

@ -26,9 +26,9 @@ async def run_migration():
# Add arrival tracking columns to newbook_daily_occupancy table # Add arrival tracking columns to newbook_daily_occupancy table
arrival_columns = [ arrival_columns = [
"ALTER TABLE newbook_daily_occupancy ADD COLUMN arrival_count INTEGER", "ALTER TABLE newbook_daily_occupancy ADD COLUMN IF NOT EXISTS arrival_count INTEGER",
"ALTER TABLE newbook_daily_occupancy ADD COLUMN arrival_booking_ids JSONB", "ALTER TABLE newbook_daily_occupancy ADD COLUMN IF NOT EXISTS arrival_booking_ids JSONB",
"ALTER TABLE newbook_daily_occupancy ADD COLUMN arrival_booking_details JSONB", "ALTER TABLE newbook_daily_occupancy ADD COLUMN IF NOT EXISTS arrival_booking_details JSONB",
] ]
for sql in arrival_columns: for sql in arrival_columns:

View file

@ -23,15 +23,15 @@ async def run_migration():
# Add Newbook columns to kitchen_settings table # Add Newbook columns to kitchen_settings table
settings_columns = [ settings_columns = [
"ALTER TABLE kitchen_settings ADD COLUMN newbook_api_username VARCHAR(255)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_username VARCHAR(255)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_api_password VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_password VARCHAR(500)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_api_key VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_key VARCHAR(500)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_api_region VARCHAR(10)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_api_region VARCHAR(10)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_instance_id VARCHAR(100)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_instance_id VARCHAR(100)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_last_sync TIMESTAMP", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_last_sync TIMESTAMP",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_auto_sync_enabled BOOLEAN DEFAULT FALSE", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_auto_sync_enabled BOOLEAN DEFAULT FALSE",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_breakfast_gl_codes VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_breakfast_gl_codes VARCHAR(500)",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_dinner_gl_codes VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_dinner_gl_codes VARCHAR(500)",
] ]
for sql in settings_columns: for sql in settings_columns:
@ -163,7 +163,7 @@ async def run_migration():
# Add room_count column if it doesn't exist # Add room_count column if it doesn't exist
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text("ALTER TABLE newbook_room_categories ADD COLUMN room_count INTEGER DEFAULT 0")) 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") logger.info("Added room_count column to newbook_room_categories")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():

View file

@ -1,28 +1,28 @@
"""Add batch_output_type, batch_yield_qty, batch_yield_unit to recipes table.""" """Add batch_output_type, batch_yield_qty, batch_yield_unit to recipes table."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipes' AND column_name = 'batch_output_type'" "WHERE table_name = 'recipes' AND column_name = 'batch_output_type'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipes.batch_output_type already exists, skipping") logger.info("recipes.batch_output_type already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipes ADD COLUMN batch_output_type VARCHAR(20) NOT NULL DEFAULT 'portions'" "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_output_type VARCHAR(20) NOT NULL DEFAULT 'portions'"
)) ))
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipes ADD COLUMN batch_yield_qty NUMERIC(10,3) DEFAULT NULL" "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_yield_qty NUMERIC(10,3) DEFAULT NULL"
)) ))
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipes ADD COLUMN batch_yield_unit VARCHAR(10) DEFAULT NULL" "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_yield_unit VARCHAR(10) DEFAULT NULL"
)) ))
logger.info("Added batch output type columns to recipes") logger.info("Added batch output type columns to recipes")

View file

@ -1,22 +1,22 @@
"""Add unit column to recipe_ingredients table for display unit override.""" """Add unit column to recipe_ingredients table for display unit override."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipe_ingredients' AND column_name = 'unit'" "WHERE table_name = 'recipe_ingredients' AND column_name = 'unit'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipe_ingredients.unit already exists, skipping") logger.info("recipe_ingredients.unit already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipe_ingredients ADD COLUMN unit VARCHAR(10) DEFAULT NULL" "ALTER TABLE recipe_ingredients ADD COLUMN IF NOT EXISTS unit VARCHAR(10) DEFAULT NULL"
)) ))
logger.info("Added unit column to recipe_ingredients") logger.info("Added unit column to recipe_ingredients")

View file

@ -1,33 +1,33 @@
"""Move yield_percent from ingredients to recipe_ingredients (per-use yield).""" """Move yield_percent from ingredients to recipe_ingredients (per-use yield)."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
# Check if column already exists # Check if column already exists
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipe_ingredients' AND column_name = 'yield_percent'" "WHERE table_name = 'recipe_ingredients' AND column_name = 'yield_percent'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipe_ingredients.yield_percent already exists, skipping") logger.info("recipe_ingredients.yield_percent already exists, skipping")
return return
# Add yield_percent to recipe_ingredients with default 100 # Add yield_percent to recipe_ingredients with default 100
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipe_ingredients ADD COLUMN yield_percent NUMERIC(5,2) DEFAULT 100.00 NOT NULL" "ALTER TABLE recipe_ingredients ADD COLUMN IF NOT EXISTS yield_percent NUMERIC(5,2) DEFAULT 100.00 NOT NULL"
)) ))
logger.info("Added recipe_ingredients.yield_percent column") logger.info("Added recipe_ingredients.yield_percent column")
# Populate from existing ingredient yields # Populate from existing ingredient yields
await conn.execute(text( await conn.execute(text(
"UPDATE recipe_ingredients ri " "UPDATE recipe_ingredients ri "
"SET yield_percent = i.yield_percent " "SET yield_percent = i.yield_percent "
"FROM ingredients i " "FROM ingredients i "
"WHERE ri.ingredient_id = i.id AND i.yield_percent != 100.00" "WHERE ri.ingredient_id = i.id AND i.yield_percent != 100.00"
)) ))
logger.info("Populated recipe_ingredients.yield_percent from existing ingredient values") logger.info("Populated recipe_ingredients.yield_percent from existing ingredient values")

View file

@ -24,7 +24,7 @@ async def run_migration():
logger.info("Running Resos arrival widget filter migration...") logger.info("Running Resos arrival widget filter migration...")
# Add arrival widget service filter column # Add arrival widget service filter column
sql = "ALTER TABLE kitchen_settings ADD COLUMN resos_arrival_widget_service_filter VARCHAR(255)" sql = "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_arrival_widget_service_filter VARCHAR(255)"
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:

View file

@ -23,16 +23,16 @@ async def run_migration():
# Add Resos columns to kitchen_settings table # Add Resos columns to kitchen_settings table
settings_columns = [ settings_columns = [
"ALTER TABLE kitchen_settings ADD COLUMN resos_api_key VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_api_key VARCHAR(500)",
"ALTER TABLE kitchen_settings ADD COLUMN resos_last_sync TIMESTAMP", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_last_sync TIMESTAMP",
"ALTER TABLE kitchen_settings ADD COLUMN resos_auto_sync_enabled BOOLEAN DEFAULT FALSE", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_auto_sync_enabled BOOLEAN DEFAULT FALSE",
"ALTER TABLE kitchen_settings ADD COLUMN resos_large_group_threshold INTEGER DEFAULT 8", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_large_group_threshold INTEGER DEFAULT 8",
"ALTER TABLE kitchen_settings ADD COLUMN resos_note_keywords TEXT", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_note_keywords TEXT",
"ALTER TABLE kitchen_settings ADD COLUMN resos_allergy_keywords TEXT", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_allergy_keywords TEXT",
"ALTER TABLE kitchen_settings ADD COLUMN resos_custom_field_mapping JSONB", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_custom_field_mapping JSONB",
"ALTER TABLE kitchen_settings ADD COLUMN resos_opening_hours_mapping JSONB", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_opening_hours_mapping JSONB",
"ALTER TABLE kitchen_settings ADD COLUMN resos_restaurant_table_entities TEXT", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_restaurant_table_entities TEXT",
"ALTER TABLE kitchen_settings ADD COLUMN resos_flag_icon_mapping JSONB", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_flag_icon_mapping JSONB",
] ]
for sql in settings_columns: for sql in settings_columns:
@ -154,7 +154,7 @@ async def run_migration():
# Add unique_flag_types column to resos_daily_stats if it doesn't exist # Add unique_flag_types column to resos_daily_stats if it doesn't exist
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text("ALTER TABLE resos_daily_stats ADD COLUMN unique_flag_types JSONB")) await conn.execute(text("ALTER TABLE resos_daily_stats ADD COLUMN IF NOT EXISTS unique_flag_types JSONB"))
logger.info("Added unique_flag_types column to resos_daily_stats") logger.info("Added unique_flag_types column to resos_daily_stats")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
@ -165,7 +165,7 @@ async def run_migration():
# Add table_name column to resos_bookings (Phase 8.1) # Add table_name column to resos_bookings (Phase 8.1)
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text("ALTER TABLE resos_bookings ADD COLUMN table_name VARCHAR(100)")) await conn.execute(text("ALTER TABLE resos_bookings ADD COLUMN IF NOT EXISTS table_name VARCHAR(100)"))
logger.info("Added table_name column to resos_bookings") logger.info("Added table_name column to resos_bookings")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
@ -176,7 +176,7 @@ async def run_migration():
# Add GL code columns for food/beverage split (Phase 8.1) # Add GL code columns for food/beverage split (Phase 8.1)
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text("ALTER TABLE kitchen_settings ADD COLUMN sambapos_food_gl_codes TEXT")) await conn.execute(text("ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_food_gl_codes TEXT"))
logger.info("Added sambapos_food_gl_codes column to kitchen_settings") logger.info("Added sambapos_food_gl_codes column to kitchen_settings")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
@ -186,7 +186,7 @@ async def run_migration():
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.execute(text("ALTER TABLE kitchen_settings ADD COLUMN sambapos_beverage_gl_codes TEXT")) await conn.execute(text("ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_beverage_gl_codes TEXT"))
logger.info("Added sambapos_beverage_gl_codes column to kitchen_settings") logger.info("Added sambapos_beverage_gl_codes column to kitchen_settings")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():

View file

@ -27,8 +27,8 @@ async def run_migration():
logger.info("Running Resos manual breakfast configuration migration...") logger.info("Running Resos manual breakfast configuration migration...")
migrations = [ migrations = [
"ALTER TABLE kitchen_settings ADD COLUMN resos_enable_manual_breakfast BOOLEAN DEFAULT FALSE", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_enable_manual_breakfast BOOLEAN DEFAULT FALSE",
"ALTER TABLE kitchen_settings ADD COLUMN resos_manual_breakfast_periods JSONB" "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_manual_breakfast_periods JSONB"
] ]
async with engine.begin() as conn: async with engine.begin() as conn:

View file

@ -17,7 +17,7 @@ async def run_migration():
logger.info("Running SambaPOS excluded items migration...") logger.info("Running SambaPOS excluded items migration...")
# Add column for excluded items (TEXT to allow for many items with long names) # Add column for excluded items (TEXT to allow for many items with long names)
sql = "ALTER TABLE kitchen_settings ADD COLUMN sambapos_excluded_items TEXT" sql = "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_excluded_items TEXT"
try: try:
async with engine.begin() as conn: async with engine.begin() as conn:

View file

@ -1,22 +1,22 @@
"""Add sambapos_portion_name column to recipes table.""" """Add sambapos_portion_name column to recipes table."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipes' AND column_name = 'sambapos_portion_name'" "WHERE table_name = 'recipes' AND column_name = 'sambapos_portion_name'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipes.sambapos_portion_name already exists, skipping") logger.info("recipes.sambapos_portion_name already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipes ADD COLUMN sambapos_portion_name VARCHAR(255) DEFAULT NULL" "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS sambapos_portion_name VARCHAR(255) DEFAULT NULL"
)) ))
logger.info("Added recipes.sambapos_portion_name column") logger.info("Added recipes.sambapos_portion_name column")

View file

@ -18,12 +18,12 @@ async def run_migration():
# Add SambaPOS columns to kitchen_settings table # Add SambaPOS columns to kitchen_settings table
settings_columns = [ settings_columns = [
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_host VARCHAR(255)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_host VARCHAR(255)",
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_port INTEGER DEFAULT 1433", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_port INTEGER DEFAULT 1433",
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_name VARCHAR(255)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_name VARCHAR(255)",
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_username VARCHAR(255)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_username VARCHAR(255)",
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_password VARCHAR(500)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_password VARCHAR(500)",
"ALTER TABLE kitchen_settings ADD COLUMN sambapos_tracked_categories VARCHAR(1000)", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_tracked_categories VARCHAR(1000)",
] ]
for sql in settings_columns: for sql in settings_columns:

View file

@ -1,23 +1,23 @@
"""Add title column to recipe_steps table.""" """Add title column to recipe_steps table."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
# Check if column already exists # Check if column already exists
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipe_steps' AND column_name = 'title'" "WHERE table_name = 'recipe_steps' AND column_name = 'title'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipe_steps.title already exists, skipping") logger.info("recipe_steps.title already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipe_steps ADD COLUMN title VARCHAR(255) DEFAULT NULL" "ALTER TABLE recipe_steps ADD COLUMN IF NOT EXISTS title VARCHAR(255) DEFAULT NULL"
)) ))
logger.info("Added recipe_steps.title column") logger.info("Added recipe_steps.title column")

View file

@ -1,22 +1,22 @@
"""Add portions_needed_unit to recipe_sub_recipes table.""" """Add portions_needed_unit to recipe_sub_recipes table."""
import logging import logging
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
result = await conn.execute(text( result = await conn.execute(text(
"SELECT column_name FROM information_schema.columns " "SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'recipe_sub_recipes' AND column_name = 'portions_needed_unit'" "WHERE table_name = 'recipe_sub_recipes' AND column_name = 'portions_needed_unit'"
)) ))
if result.scalar_one_or_none(): if result.scalar_one_or_none():
logger.info("recipe_sub_recipes.portions_needed_unit already exists, skipping") logger.info("recipe_sub_recipes.portions_needed_unit already exists, skipping")
return return
await conn.execute(text( await conn.execute(text(
"ALTER TABLE recipe_sub_recipes ADD COLUMN portions_needed_unit VARCHAR(10) DEFAULT NULL" "ALTER TABLE recipe_sub_recipes ADD COLUMN IF NOT EXISTS portions_needed_unit VARCHAR(10) DEFAULT NULL"
)) ))
logger.info("Added portions_needed_unit column to recipe_sub_recipes") logger.info("Added portions_needed_unit column to recipe_sub_recipes")

View file

@ -1,40 +1,40 @@
""" """
Migration: Add order_email and account_number columns to suppliers table. Migration: Add order_email and account_number columns to suppliers table.
Used for Purchase Order email sending and supplier identification. Used for Purchase Order email sending and supplier identification.
""" """
import asyncio import asyncio
from sqlalchemy import text from sqlalchemy import text
from database import engine from database import engine
async def migrate(): async def migrate():
async with engine.begin() as conn: async with engine.begin() as conn:
# Add order_email column # Add order_email column
try: try:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE suppliers ADD COLUMN order_email VARCHAR(255)" "ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS order_email VARCHAR(255)"
)) ))
print("+ Added order_email column to suppliers") print("+ Added order_email column to suppliers")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
print("- order_email column already exists, skipping") print("- order_email column already exists, skipping")
else: else:
raise raise
# Add account_number column # Add account_number column
try: try:
await conn.execute(text( await conn.execute(text(
"ALTER TABLE suppliers ADD COLUMN account_number VARCHAR(100)" "ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS account_number VARCHAR(100)"
)) ))
print("+ Added account_number column to suppliers") print("+ Added account_number column to suppliers")
except Exception as e: except Exception as e:
if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
print("- account_number column already exists, skipping") print("- account_number column already exists, skipping")
else: else:
raise raise
if __name__ == "__main__": if __name__ == "__main__":
print("Running migration: add_supplier_po_fields") print("Running migration: add_supplier_po_fields")
asyncio.run(migrate()) asyncio.run(migrate())
print("Migration complete!") print("Migration complete!")

View file

@ -13,7 +13,7 @@ async def migrate():
await conn.execute(text( await conn.execute(text(
""" """
ALTER TABLE suppliers ALTER TABLE suppliers
ADD COLUMN skip_dext BOOLEAN NOT NULL DEFAULT FALSE ADD COLUMN IF NOT EXISTS skip_dext BOOLEAN NOT NULL DEFAULT FALSE
""" """
)) ))
print("✓ Added skip_dext column to suppliers table") print("✓ Added skip_dext column to suppliers table")

View file

@ -18,8 +18,8 @@ async def run_migration():
# Add VAT rate columns to kitchen_settings table # Add VAT rate columns to kitchen_settings table
settings_columns = [ settings_columns = [
"ALTER TABLE kitchen_settings ADD COLUMN newbook_breakfast_vat_rate NUMERIC(5, 4) DEFAULT 0.10", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_breakfast_vat_rate NUMERIC(5, 4) DEFAULT 0.10",
"ALTER TABLE kitchen_settings ADD COLUMN newbook_dinner_vat_rate NUMERIC(5, 4) DEFAULT 0.10", "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS newbook_dinner_vat_rate NUMERIC(5, 4) DEFAULT 0.10",
] ]
for sql in settings_columns: for sql in settings_columns:
@ -36,8 +36,8 @@ async def run_migration():
# Add GL group columns to newbook_gl_accounts table (if not present) # Add GL group columns to newbook_gl_accounts table (if not present)
gl_columns = [ gl_columns = [
"ALTER TABLE newbook_gl_accounts ADD COLUMN gl_group_id VARCHAR(50)", "ALTER TABLE newbook_gl_accounts ADD COLUMN IF NOT EXISTS gl_group_id VARCHAR(50)",
"ALTER TABLE newbook_gl_accounts ADD COLUMN gl_group_name VARCHAR(255)", "ALTER TABLE newbook_gl_accounts ADD COLUMN IF NOT EXISTS gl_group_name VARCHAR(255)",
] ]
for sql in gl_columns: for sql in gl_columns: