From bc874ac9ff2275c7a18cf418c30b346e24e3d8bc Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Sun, 12 Jul 2026 19:53:26 +0000 Subject: [PATCH] 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 --- .../migrations/add_admin_restricted_pages.py | 2 +- backend/migrations/add_budget_settings.py | 8 +- backend/migrations/add_cost_distributions.py | 2 +- backend/migrations/add_dext_integration.py | 28 +++---- backend/migrations/add_dext_manual_send.py | 2 +- .../add_dispute_attachment_public_hash.py | 2 +- backend/migrations/add_gross_sell_price.py | 44 +++++----- backend/migrations/add_invoice_features.py | 50 ++++++------ backend/migrations/add_kitchen_details.py | 2 +- .../add_newbook_arrival_tracking.py | 6 +- backend/migrations/add_newbook_tables.py | 20 ++--- backend/migrations/add_recipe_batch_type.py | 56 ++++++------- .../migrations/add_recipe_ingredient_unit.py | 44 +++++----- .../migrations/add_recipe_ingredient_yield.py | 66 +++++++-------- .../add_resos_arrival_widget_filter.py | 2 +- backend/migrations/add_resos_integration.py | 28 +++---- .../migrations/add_resos_manual_breakfast.py | 4 +- .../migrations/add_sambapos_excluded_items.py | 2 +- .../migrations/add_sambapos_portion_name.py | 44 +++++----- backend/migrations/add_sambapos_settings.py | 12 +-- backend/migrations/add_step_title.py | 46 +++++------ backend/migrations/add_sub_recipe_unit.py | 44 +++++----- backend/migrations/add_supplier_po_fields.py | 80 +++++++++---------- backend/migrations/add_supplier_skip_dext.py | 2 +- backend/migrations/add_vat_rate_settings.py | 8 +- 25 files changed, 302 insertions(+), 302 deletions(-) diff --git a/backend/migrations/add_admin_restricted_pages.py b/backend/migrations/add_admin_restricted_pages.py index 730f72d..eb933a9 100644 --- a/backend/migrations/add_admin_restricted_pages.py +++ b/backend/migrations/add_admin_restricted_pages.py @@ -16,7 +16,7 @@ async def run_migration(): """Add admin_restricted_pages column to kitchen_settings table.""" 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: async with engine.begin() as conn: diff --git a/backend/migrations/add_budget_settings.py b/backend/migrations/add_budget_settings.py index 5764cef..dc94860 100644 --- a/backend/migrations/add_budget_settings.py +++ b/backend/migrations/add_budget_settings.py @@ -19,7 +19,7 @@ async def migrate(): await conn.execute(text( """ 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") @@ -34,7 +34,7 @@ async def migrate(): await conn.execute(text( """ 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") @@ -49,7 +49,7 @@ async def migrate(): await conn.execute(text( """ 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") @@ -64,7 +64,7 @@ async def migrate(): await conn.execute(text( """ 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") diff --git a/backend/migrations/add_cost_distributions.py b/backend/migrations/add_cost_distributions.py index 720283a..a38ce18 100644 --- a/backend/migrations/add_cost_distributions.py +++ b/backend/migrations/add_cost_distributions.py @@ -112,7 +112,7 @@ async def migrate(): # Add cost_distribution_max_days to kitchen_settings try: 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") except Exception as e: diff --git a/backend/migrations/add_dext_integration.py b/backend/migrations/add_dext_integration.py index 99f2a21..af5ef8d 100644 --- a/backend/migrations/add_dext_integration.py +++ b/backend/migrations/add_dext_integration.py @@ -26,7 +26,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -39,7 +39,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -52,7 +52,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -67,7 +67,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -80,7 +80,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -93,7 +93,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -106,7 +106,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -119,7 +119,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -132,7 +132,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -145,7 +145,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -160,7 +160,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -173,7 +173,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -186,7 +186,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -199,7 +199,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: diff --git a/backend/migrations/add_dext_manual_send.py b/backend/migrations/add_dext_manual_send.py index 76f3e74..3f9d1e0 100644 --- a/backend/migrations/add_dext_manual_send.py +++ b/backend/migrations/add_dext_manual_send.py @@ -13,7 +13,7 @@ async def migrate(): await conn.execute(text( """ 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") diff --git a/backend/migrations/add_dispute_attachment_public_hash.py b/backend/migrations/add_dispute_attachment_public_hash.py index ba46bb3..e1ccf86 100644 --- a/backend/migrations/add_dispute_attachment_public_hash.py +++ b/backend/migrations/add_dispute_attachment_public_hash.py @@ -28,7 +28,7 @@ async def run_migration(): logger.info("Adding 'public_hash' column to dispute_attachments table") await conn.execute(text(""" 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 diff --git a/backend/migrations/add_gross_sell_price.py b/backend/migrations/add_gross_sell_price.py index ae8f4bd..19e88d9 100644 --- a/backend/migrations/add_gross_sell_price.py +++ b/backend/migrations/add_gross_sell_price.py @@ -1,22 +1,22 @@ -"""Add gross_sell_price column to recipes table.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipes' AND column_name = 'gross_sell_price'" - )) - if result.scalar_one_or_none(): - logger.info("recipes.gross_sell_price already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipes ADD COLUMN gross_sell_price NUMERIC(10,2) DEFAULT NULL" - )) - logger.info("Added recipes.gross_sell_price column") +"""Add gross_sell_price column to recipes table.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipes' AND column_name = 'gross_sell_price'" + )) + if result.scalar_one_or_none(): + logger.info("recipes.gross_sell_price already exists, skipping") + return + + await conn.execute(text( + "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS gross_sell_price NUMERIC(10,2) DEFAULT NULL" + )) + logger.info("Added recipes.gross_sell_price column") diff --git a/backend/migrations/add_invoice_features.py b/backend/migrations/add_invoice_features.py index 9c643ff..4496584 100644 --- a/backend/migrations/add_invoice_features.py +++ b/backend/migrations/add_invoice_features.py @@ -22,11 +22,11 @@ async def run_migration(): # Run each migration in its own transaction migrations = [ - "ALTER TABLE invoices ADD COLUMN document_type VARCHAR(50) DEFAULT 'invoice'", - "ALTER TABLE invoices ADD COLUMN order_number VARCHAR(100)", - "ALTER TABLE invoices ADD COLUMN duplicate_status VARCHAR(50)", - "ALTER TABLE invoices ADD COLUMN 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 document_type VARCHAR(50) DEFAULT 'invoice'", + "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS order_number VARCHAR(100)", + "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS duplicate_status VARCHAR(50)", + "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS duplicate_of_id INTEGER REFERENCES invoices(id)", + "ALTER TABLE invoices ADD COLUMN IF NOT EXISTS related_document_id INTEGER REFERENCES invoices(id)", ] for sql in migrations: @@ -76,7 +76,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -89,7 +89,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -102,7 +102,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -115,7 +115,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -128,7 +128,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -141,7 +141,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -189,7 +189,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -202,7 +202,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -215,7 +215,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -228,7 +228,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -241,7 +241,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -254,7 +254,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -267,7 +267,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -280,7 +280,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -293,7 +293,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -306,7 +306,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -319,7 +319,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -384,7 +384,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -397,7 +397,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: @@ -410,7 +410,7 @@ async def run_migration(): try: async with engine.begin() as conn: 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") except Exception as e: diff --git a/backend/migrations/add_kitchen_details.py b/backend/migrations/add_kitchen_details.py index bf58c38..f93518a 100644 --- a/backend/migrations/add_kitchen_details.py +++ b/backend/migrations/add_kitchen_details.py @@ -23,7 +23,7 @@ async def migrate(): for col_name, col_type in COLUMNS: try: 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") except Exception as e: diff --git a/backend/migrations/add_newbook_arrival_tracking.py b/backend/migrations/add_newbook_arrival_tracking.py index 861d5e1..e6406d9 100644 --- a/backend/migrations/add_newbook_arrival_tracking.py +++ b/backend/migrations/add_newbook_arrival_tracking.py @@ -26,9 +26,9 @@ async def run_migration(): # Add arrival tracking columns to newbook_daily_occupancy table arrival_columns = [ - "ALTER TABLE newbook_daily_occupancy ADD COLUMN arrival_count INTEGER", - "ALTER TABLE newbook_daily_occupancy ADD COLUMN 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_count INTEGER", + "ALTER TABLE newbook_daily_occupancy ADD COLUMN IF NOT EXISTS arrival_booking_ids JSONB", + "ALTER TABLE newbook_daily_occupancy ADD COLUMN IF NOT EXISTS arrival_booking_details JSONB", ] for sql in arrival_columns: diff --git a/backend/migrations/add_newbook_tables.py b/backend/migrations/add_newbook_tables.py index b5bbdda..59f7aae 100644 --- a/backend/migrations/add_newbook_tables.py +++ b/backend/migrations/add_newbook_tables.py @@ -23,15 +23,15 @@ async def run_migration(): # Add Newbook columns to kitchen_settings table settings_columns = [ - "ALTER TABLE kitchen_settings ADD COLUMN newbook_api_username VARCHAR(255)", - "ALTER TABLE kitchen_settings ADD COLUMN newbook_api_password VARCHAR(500)", - "ALTER TABLE kitchen_settings ADD COLUMN newbook_api_key VARCHAR(500)", - "ALTER TABLE kitchen_settings ADD COLUMN newbook_api_region VARCHAR(10)", - "ALTER TABLE kitchen_settings ADD COLUMN newbook_instance_id VARCHAR(100)", - "ALTER TABLE kitchen_settings ADD COLUMN newbook_last_sync TIMESTAMP", - "ALTER TABLE kitchen_settings ADD COLUMN 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 newbook_dinner_gl_codes VARCHAR(500)", + "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: @@ -163,7 +163,7 @@ async def run_migration(): # 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 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") except Exception as e: if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): diff --git a/backend/migrations/add_recipe_batch_type.py b/backend/migrations/add_recipe_batch_type.py index de35b95..85d7786 100644 --- a/backend/migrations/add_recipe_batch_type.py +++ b/backend/migrations/add_recipe_batch_type.py @@ -1,28 +1,28 @@ -"""Add batch_output_type, batch_yield_qty, batch_yield_unit to recipes table.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipes' AND column_name = 'batch_output_type'" - )) - if result.scalar_one_or_none(): - logger.info("recipes.batch_output_type already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipes ADD COLUMN batch_output_type VARCHAR(20) NOT NULL DEFAULT 'portions'" - )) - await conn.execute(text( - "ALTER TABLE recipes ADD COLUMN batch_yield_qty NUMERIC(10,3) DEFAULT NULL" - )) - await conn.execute(text( - "ALTER TABLE recipes ADD COLUMN batch_yield_unit VARCHAR(10) DEFAULT NULL" - )) - logger.info("Added batch output type columns to recipes") +"""Add batch_output_type, batch_yield_qty, batch_yield_unit to recipes table.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipes' AND column_name = 'batch_output_type'" + )) + if result.scalar_one_or_none(): + logger.info("recipes.batch_output_type already exists, skipping") + return + + await conn.execute(text( + "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_output_type VARCHAR(20) NOT NULL DEFAULT 'portions'" + )) + await conn.execute(text( + "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_yield_qty NUMERIC(10,3) DEFAULT NULL" + )) + await conn.execute(text( + "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS batch_yield_unit VARCHAR(10) DEFAULT NULL" + )) + logger.info("Added batch output type columns to recipes") diff --git a/backend/migrations/add_recipe_ingredient_unit.py b/backend/migrations/add_recipe_ingredient_unit.py index 08b63d0..d490bd0 100644 --- a/backend/migrations/add_recipe_ingredient_unit.py +++ b/backend/migrations/add_recipe_ingredient_unit.py @@ -1,22 +1,22 @@ -"""Add unit column to recipe_ingredients table for display unit override.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipe_ingredients' AND column_name = 'unit'" - )) - if result.scalar_one_or_none(): - logger.info("recipe_ingredients.unit already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipe_ingredients ADD COLUMN unit VARCHAR(10) DEFAULT NULL" - )) - logger.info("Added unit column to recipe_ingredients") +"""Add unit column to recipe_ingredients table for display unit override.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipe_ingredients' AND column_name = 'unit'" + )) + if result.scalar_one_or_none(): + logger.info("recipe_ingredients.unit already exists, skipping") + return + + await conn.execute(text( + "ALTER TABLE recipe_ingredients ADD COLUMN IF NOT EXISTS unit VARCHAR(10) DEFAULT NULL" + )) + logger.info("Added unit column to recipe_ingredients") diff --git a/backend/migrations/add_recipe_ingredient_yield.py b/backend/migrations/add_recipe_ingredient_yield.py index bad5942..7e00797 100644 --- a/backend/migrations/add_recipe_ingredient_yield.py +++ b/backend/migrations/add_recipe_ingredient_yield.py @@ -1,33 +1,33 @@ -"""Move yield_percent from ingredients to recipe_ingredients (per-use yield).""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - # Check if column already exists - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipe_ingredients' AND column_name = 'yield_percent'" - )) - if result.scalar_one_or_none(): - logger.info("recipe_ingredients.yield_percent already exists, skipping") - return - - # Add yield_percent to recipe_ingredients with default 100 - await conn.execute(text( - "ALTER TABLE recipe_ingredients ADD COLUMN yield_percent NUMERIC(5,2) DEFAULT 100.00 NOT NULL" - )) - logger.info("Added recipe_ingredients.yield_percent column") - - # Populate from existing ingredient yields - await conn.execute(text( - "UPDATE recipe_ingredients ri " - "SET yield_percent = i.yield_percent " - "FROM ingredients i " - "WHERE ri.ingredient_id = i.id AND i.yield_percent != 100.00" - )) - logger.info("Populated recipe_ingredients.yield_percent from existing ingredient values") +"""Move yield_percent from ingredients to recipe_ingredients (per-use yield).""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + # Check if column already exists + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipe_ingredients' AND column_name = 'yield_percent'" + )) + if result.scalar_one_or_none(): + logger.info("recipe_ingredients.yield_percent already exists, skipping") + return + + # Add yield_percent to recipe_ingredients with default 100 + await conn.execute(text( + "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") + + # Populate from existing ingredient yields + await conn.execute(text( + "UPDATE recipe_ingredients ri " + "SET yield_percent = i.yield_percent " + "FROM ingredients i " + "WHERE ri.ingredient_id = i.id AND i.yield_percent != 100.00" + )) + logger.info("Populated recipe_ingredients.yield_percent from existing ingredient values") diff --git a/backend/migrations/add_resos_arrival_widget_filter.py b/backend/migrations/add_resos_arrival_widget_filter.py index 7e163dc..ba3937f 100644 --- a/backend/migrations/add_resos_arrival_widget_filter.py +++ b/backend/migrations/add_resos_arrival_widget_filter.py @@ -24,7 +24,7 @@ async def run_migration(): logger.info("Running Resos arrival widget filter migration...") # 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: async with engine.begin() as conn: diff --git a/backend/migrations/add_resos_integration.py b/backend/migrations/add_resos_integration.py index eb0df00..d621fb0 100644 --- a/backend/migrations/add_resos_integration.py +++ b/backend/migrations/add_resos_integration.py @@ -23,16 +23,16 @@ async def run_migration(): # Add Resos columns to kitchen_settings table settings_columns = [ - "ALTER TABLE kitchen_settings ADD COLUMN resos_api_key VARCHAR(500)", - "ALTER TABLE kitchen_settings ADD COLUMN resos_last_sync TIMESTAMP", - "ALTER TABLE kitchen_settings ADD COLUMN 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 resos_note_keywords TEXT", - "ALTER TABLE kitchen_settings ADD COLUMN resos_allergy_keywords TEXT", - "ALTER TABLE kitchen_settings ADD COLUMN resos_custom_field_mapping JSONB", - "ALTER TABLE kitchen_settings ADD COLUMN resos_opening_hours_mapping JSONB", - "ALTER TABLE kitchen_settings ADD COLUMN 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_api_key VARCHAR(500)", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_last_sync TIMESTAMP", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_auto_sync_enabled BOOLEAN DEFAULT FALSE", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_large_group_threshold INTEGER DEFAULT 8", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_note_keywords TEXT", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_allergy_keywords TEXT", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_custom_field_mapping JSONB", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_opening_hours_mapping JSONB", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_restaurant_table_entities TEXT", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_flag_icon_mapping JSONB", ] 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 try: 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") except Exception as e: 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) try: 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") except Exception as e: 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) try: 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") except Exception as e: if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): @@ -186,7 +186,7 @@ async def run_migration(): try: 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") except Exception as e: if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): diff --git a/backend/migrations/add_resos_manual_breakfast.py b/backend/migrations/add_resos_manual_breakfast.py index da64f8f..a33cc10 100644 --- a/backend/migrations/add_resos_manual_breakfast.py +++ b/backend/migrations/add_resos_manual_breakfast.py @@ -27,8 +27,8 @@ async def run_migration(): logger.info("Running Resos manual breakfast configuration migration...") migrations = [ - "ALTER TABLE kitchen_settings ADD COLUMN 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_enable_manual_breakfast BOOLEAN DEFAULT FALSE", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS resos_manual_breakfast_periods JSONB" ] async with engine.begin() as conn: diff --git a/backend/migrations/add_sambapos_excluded_items.py b/backend/migrations/add_sambapos_excluded_items.py index 599f13e..66b63b2 100644 --- a/backend/migrations/add_sambapos_excluded_items.py +++ b/backend/migrations/add_sambapos_excluded_items.py @@ -17,7 +17,7 @@ async def run_migration(): logger.info("Running SambaPOS excluded items migration...") # 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: async with engine.begin() as conn: diff --git a/backend/migrations/add_sambapos_portion_name.py b/backend/migrations/add_sambapos_portion_name.py index 9d0202c..5de3b4f 100644 --- a/backend/migrations/add_sambapos_portion_name.py +++ b/backend/migrations/add_sambapos_portion_name.py @@ -1,22 +1,22 @@ -"""Add sambapos_portion_name column to recipes table.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipes' AND column_name = 'sambapos_portion_name'" - )) - if result.scalar_one_or_none(): - logger.info("recipes.sambapos_portion_name already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipes ADD COLUMN sambapos_portion_name VARCHAR(255) DEFAULT NULL" - )) - logger.info("Added recipes.sambapos_portion_name column") +"""Add sambapos_portion_name column to recipes table.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipes' AND column_name = 'sambapos_portion_name'" + )) + if result.scalar_one_or_none(): + logger.info("recipes.sambapos_portion_name already exists, skipping") + return + + await conn.execute(text( + "ALTER TABLE recipes ADD COLUMN IF NOT EXISTS sambapos_portion_name VARCHAR(255) DEFAULT NULL" + )) + logger.info("Added recipes.sambapos_portion_name column") diff --git a/backend/migrations/add_sambapos_settings.py b/backend/migrations/add_sambapos_settings.py index b8a3b2a..81a469f 100644 --- a/backend/migrations/add_sambapos_settings.py +++ b/backend/migrations/add_sambapos_settings.py @@ -18,12 +18,12 @@ async def run_migration(): # Add SambaPOS columns to kitchen_settings table settings_columns = [ - "ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_host VARCHAR(255)", - "ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_port INTEGER DEFAULT 1433", - "ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_name VARCHAR(255)", - "ALTER TABLE kitchen_settings ADD COLUMN sambapos_db_username VARCHAR(255)", - "ALTER TABLE kitchen_settings ADD COLUMN 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_db_host VARCHAR(255)", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_port INTEGER DEFAULT 1433", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_name VARCHAR(255)", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_username VARCHAR(255)", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_db_password VARCHAR(500)", + "ALTER TABLE kitchen_settings ADD COLUMN IF NOT EXISTS sambapos_tracked_categories VARCHAR(1000)", ] for sql in settings_columns: diff --git a/backend/migrations/add_step_title.py b/backend/migrations/add_step_title.py index fb2b73a..7bb224c 100644 --- a/backend/migrations/add_step_title.py +++ b/backend/migrations/add_step_title.py @@ -1,23 +1,23 @@ -"""Add title column to recipe_steps table.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - # Check if column already exists - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipe_steps' AND column_name = 'title'" - )) - if result.scalar_one_or_none(): - logger.info("recipe_steps.title already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipe_steps ADD COLUMN title VARCHAR(255) DEFAULT NULL" - )) - logger.info("Added recipe_steps.title column") +"""Add title column to recipe_steps table.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + # Check if column already exists + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipe_steps' AND column_name = 'title'" + )) + if result.scalar_one_or_none(): + logger.info("recipe_steps.title already exists, skipping") + return + + await conn.execute(text( + "ALTER TABLE recipe_steps ADD COLUMN IF NOT EXISTS title VARCHAR(255) DEFAULT NULL" + )) + logger.info("Added recipe_steps.title column") diff --git a/backend/migrations/add_sub_recipe_unit.py b/backend/migrations/add_sub_recipe_unit.py index 0f8e514..8d15226 100644 --- a/backend/migrations/add_sub_recipe_unit.py +++ b/backend/migrations/add_sub_recipe_unit.py @@ -1,22 +1,22 @@ -"""Add portions_needed_unit to recipe_sub_recipes table.""" -import logging -from sqlalchemy import text -from database import engine - -logger = logging.getLogger(__name__) - - -async def migrate(): - async with engine.begin() as conn: - result = await conn.execute(text( - "SELECT column_name FROM information_schema.columns " - "WHERE table_name = 'recipe_sub_recipes' AND column_name = 'portions_needed_unit'" - )) - if result.scalar_one_or_none(): - logger.info("recipe_sub_recipes.portions_needed_unit already exists, skipping") - return - - await conn.execute(text( - "ALTER TABLE recipe_sub_recipes ADD COLUMN portions_needed_unit VARCHAR(10) DEFAULT NULL" - )) - logger.info("Added portions_needed_unit column to recipe_sub_recipes") +"""Add portions_needed_unit to recipe_sub_recipes table.""" +import logging +from sqlalchemy import text +from database import engine + +logger = logging.getLogger(__name__) + + +async def migrate(): + async with engine.begin() as conn: + result = await conn.execute(text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'recipe_sub_recipes' AND column_name = 'portions_needed_unit'" + )) + if result.scalar_one_or_none(): + logger.info("recipe_sub_recipes.portions_needed_unit already exists, skipping") + return + + await conn.execute(text( + "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") diff --git a/backend/migrations/add_supplier_po_fields.py b/backend/migrations/add_supplier_po_fields.py index ad35012..ce723f9 100644 --- a/backend/migrations/add_supplier_po_fields.py +++ b/backend/migrations/add_supplier_po_fields.py @@ -1,40 +1,40 @@ -""" -Migration: Add order_email and account_number columns to suppliers table. -Used for Purchase Order email sending and supplier identification. -""" -import asyncio -from sqlalchemy import text -from database import engine - - -async def migrate(): - async with engine.begin() as conn: - # Add order_email column - try: - await conn.execute(text( - "ALTER TABLE suppliers ADD COLUMN order_email VARCHAR(255)" - )) - print("+ Added order_email column to suppliers") - except Exception as e: - if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): - print("- order_email column already exists, skipping") - else: - raise - - # Add account_number column - try: - await conn.execute(text( - "ALTER TABLE suppliers ADD COLUMN account_number VARCHAR(100)" - )) - print("+ Added account_number column to suppliers") - except Exception as e: - if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): - print("- account_number column already exists, skipping") - else: - raise - - -if __name__ == "__main__": - print("Running migration: add_supplier_po_fields") - asyncio.run(migrate()) - print("Migration complete!") +""" +Migration: Add order_email and account_number columns to suppliers table. +Used for Purchase Order email sending and supplier identification. +""" +import asyncio +from sqlalchemy import text +from database import engine + + +async def migrate(): + async with engine.begin() as conn: + # Add order_email column + try: + await conn.execute(text( + "ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS order_email VARCHAR(255)" + )) + print("+ Added order_email column to suppliers") + except Exception as e: + if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): + print("- order_email column already exists, skipping") + else: + raise + + # Add account_number column + try: + await conn.execute(text( + "ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS account_number VARCHAR(100)" + )) + print("+ Added account_number column to suppliers") + except Exception as e: + if "already exists" in str(e).lower() or "duplicate column" in str(e).lower(): + print("- account_number column already exists, skipping") + else: + raise + + +if __name__ == "__main__": + print("Running migration: add_supplier_po_fields") + asyncio.run(migrate()) + print("Migration complete!") diff --git a/backend/migrations/add_supplier_skip_dext.py b/backend/migrations/add_supplier_skip_dext.py index 1e8043a..0d751b7 100644 --- a/backend/migrations/add_supplier_skip_dext.py +++ b/backend/migrations/add_supplier_skip_dext.py @@ -13,7 +13,7 @@ async def migrate(): await conn.execute(text( """ 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") diff --git a/backend/migrations/add_vat_rate_settings.py b/backend/migrations/add_vat_rate_settings.py index 7549764..1b17119 100644 --- a/backend/migrations/add_vat_rate_settings.py +++ b/backend/migrations/add_vat_rate_settings.py @@ -18,8 +18,8 @@ async def run_migration(): # Add VAT rate columns to kitchen_settings table settings_columns = [ - "ALTER TABLE kitchen_settings ADD COLUMN 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_breakfast_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: @@ -36,8 +36,8 @@ async def run_migration(): # Add GL group columns to newbook_gl_accounts table (if not present) gl_columns = [ - "ALTER TABLE newbook_gl_accounts ADD COLUMN 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_id VARCHAR(50)", + "ALTER TABLE newbook_gl_accounts ADD COLUMN IF NOT EXISTS gl_group_name VARCHAR(255)", ] for sql in gl_columns: