Fix Alembic issue

This commit is contained in:
Urtzi Alfaro
2025-10-01 11:24:06 +02:00
parent 7cc4b957a5
commit 2eeebfc1e0
62 changed files with 6114 additions and 3676 deletions

View File

@@ -1,7 +1,6 @@
"""Alembic environment configuration for inventory service"""
import asyncio
import logging
import os
import sys
from logging.config import fileConfig
@@ -25,7 +24,7 @@ try:
from shared.database.base import Base
# Import all models to ensure they are registered with Base.metadata
from app.models import * # Import all models
from app.models import * # noqa: F401, F403
except ImportError as e:
print(f"Import error in migrations env.py: {e}")
@@ -35,12 +34,19 @@ except ImportError as e:
# this is the Alembic Config object
config = context.config
# Set database URL from environment variables or settings
# Try service-specific DATABASE_URL first, then fall back to generic
database_url = os.getenv('INVENTORY_DATABASE_URL') or os.getenv('DATABASE_URL')
# Determine service name from file path
service_name = os.path.basename(os.path.dirname(os.path.dirname(__file__)))
service_name_upper = service_name.upper().replace('-', '_')
# Set database URL from environment variables with multiple fallback strategies
database_url = (
os.getenv(f'{service_name_upper}_DATABASE_URL') or # Service-specific
os.getenv('DATABASE_URL') # Generic fallback
)
# If DATABASE_URL is not set, construct from individual components
if not database_url:
# Try generic PostgreSQL environment variables first
postgres_host = os.getenv('POSTGRES_HOST')
postgres_port = os.getenv('POSTGRES_PORT', '5432')
postgres_db = os.getenv('POSTGRES_DB')
@@ -50,11 +56,28 @@ if not database_url:
if all([postgres_host, postgres_db, postgres_user, postgres_password]):
database_url = f"postgresql+asyncpg://{postgres_user}:{postgres_password}@{postgres_host}:{postgres_port}/{postgres_db}"
else:
# Fallback to settings
database_url = getattr(settings, 'DATABASE_URL', None)
# Try service-specific environment variables
db_host = os.getenv(f'{service_name_upper}_DB_HOST', f'{service_name}-db-service')
db_port = os.getenv(f'{service_name_upper}_DB_PORT', '5432')
db_name = os.getenv(f'{service_name_upper}_DB_NAME', f'{service_name.replace("-", "_")}_db')
db_user = os.getenv(f'{service_name_upper}_DB_USER', f'{service_name.replace("-", "_")}_user')
db_password = os.getenv(f'{service_name_upper}_DB_PASSWORD')
if database_url:
config.set_main_option("sqlalchemy.url", database_url)
if db_password:
database_url = f"postgresql+asyncpg://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
else:
# Final fallback: try to get from settings object
try:
database_url = getattr(settings, 'DATABASE_URL', None)
except Exception:
pass
if not database_url:
error_msg = f"ERROR: No database URL configured for {service_name} service"
print(error_msg)
raise Exception(error_msg)
config.set_main_option("sqlalchemy.url", database_url)
# Interpret the config file for Python logging
if config.config_file_name is not None:
@@ -63,6 +86,7 @@ if config.config_file_name is not None:
# Set target metadata
target_metadata = Base.metadata
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode."""
url = config.get_main_option("sqlalchemy.url")
@@ -78,7 +102,9 @@ def run_migrations_offline() -> None:
with context.begin_transaction():
context.run_migrations()
def do_run_migrations(connection: Connection) -> None:
"""Execute migrations with the given connection."""
context.configure(
connection=connection,
target_metadata=target_metadata,
@@ -89,8 +115,9 @@ def do_run_migrations(connection: Connection) -> None:
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations() -> None:
"""Run migrations in 'online' mode."""
"""Run migrations in 'online' mode with async support."""
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
@@ -102,10 +129,12 @@ async def run_async_migrations() -> None:
await connectable.dispose()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:

View File

@@ -1,492 +0,0 @@
"""Initial schema for inventory service
Revision ID: 0000001
Revises:
Create Date: 2025-09-30 18:00:00.0000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '0001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# Create ingredients table
op.create_table('ingredients',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(255), nullable=False),
sa.Column('sku', sa.String(100), nullable=True),
sa.Column('barcode', sa.String(50), nullable=True),
sa.Column('product_type', sa.Enum('INGREDIENT', 'FINISHED_PRODUCT', name='producttype'), nullable=False),
sa.Column('ingredient_category', sa.Enum('FLOUR', 'YEAST', 'DAIRY', 'EGGS', 'SUGAR', 'FATS', 'SALT', 'SPICES', 'ADDITIVES', 'PACKAGING', 'CLEANING', 'OTHER', name='ingredientcategory'), nullable=True),
sa.Column('product_category', sa.Enum('BREAD', 'CROISSANTS', 'PASTRIES', 'CAKES', 'COOKIES', 'MUFFINS', 'SANDWICHES', 'SEASONAL', 'BEVERAGES', 'OTHER_PRODUCTS', name='productcategory'), nullable=True),
sa.Column('subcategory', sa.String(100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('brand', sa.String(100), nullable=True),
sa.Column('unit_of_measure', sa.Enum('KILOGRAMS', 'GRAMS', 'LITERS', 'MILLILITERS', 'UNITS', 'PIECES', 'PACKAGES', 'BAGS', 'BOXES', name='unitofmeasure'), nullable=False),
sa.Column('package_size', sa.Float(), nullable=True),
sa.Column('average_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('last_purchase_price', sa.Numeric(10, 2), nullable=True),
sa.Column('standard_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('low_stock_threshold', sa.Float(), nullable=False),
sa.Column('reorder_point', sa.Float(), nullable=False),
sa.Column('reorder_quantity', sa.Float(), nullable=False),
sa.Column('max_stock_level', sa.Float(), nullable=True),
sa.Column('shelf_life_days', sa.Integer(), nullable=True),
sa.Column('display_life_hours', sa.Integer(), nullable=True),
sa.Column('best_before_hours', sa.Integer(), nullable=True),
sa.Column('storage_instructions', sa.Text(), nullable=True),
sa.Column('central_baker_product_code', sa.String(100), nullable=True),
sa.Column('delivery_days', sa.String(20), nullable=True),
sa.Column('minimum_order_quantity', sa.Float(), nullable=True),
sa.Column('pack_size', sa.Integer(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('is_perishable', sa.Boolean(), nullable=True),
sa.Column('allergen_info', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('nutritional_info', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_ingredients_tenant_id'), 'ingredients', ['tenant_id'], unique=False)
op.create_index(op.f('ix_ingredients_name'), 'ingredients', ['name'], unique=False)
op.create_index(op.f('ix_ingredients_sku'), 'ingredients', ['sku'], unique=False)
op.create_index(op.f('ix_ingredients_barcode'), 'ingredients', ['barcode'], unique=False)
op.create_index(op.f('ix_ingredients_product_type'), 'ingredients', ['product_type'], unique=False)
op.create_index(op.f('ix_ingredients_ingredient_category'), 'ingredients', ['ingredient_category'], unique=False)
op.create_index(op.f('ix_ingredients_product_category'), 'ingredients', ['product_category'], unique=False)
op.create_index('idx_ingredients_tenant_name', 'ingredients', ['tenant_id', 'name'], unique=True)
op.create_index('idx_ingredients_tenant_sku', 'ingredients', ['tenant_id', 'sku'], unique=False)
op.create_index('idx_ingredients_barcode', 'ingredients', ['barcode'], unique=False)
op.create_index('idx_ingredients_product_type', 'ingredients', ['tenant_id', 'product_type'], unique=False)
op.create_index('idx_ingredients_ingredient_category', 'ingredients', ['tenant_id', 'ingredient_category'], unique=False)
op.create_index('idx_ingredients_product_category', 'ingredients', ['tenant_id', 'product_category'], unique=False)
op.create_index('idx_ingredients_stock_levels', 'ingredients', ['tenant_id', 'low_stock_threshold', 'reorder_point'], unique=False)
# Create stock table
op.create_table('stock',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('supplier_id', sa.UUID(), nullable=True),
sa.Column('batch_number', sa.String(100), nullable=True),
sa.Column('lot_number', sa.String(100), nullable=True),
sa.Column('supplier_batch_ref', sa.String(100), nullable=True),
sa.Column('production_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('transformation_reference', sa.String(100), nullable=True),
sa.Column('current_quantity', sa.Float(), nullable=False),
sa.Column('reserved_quantity', sa.Float(), nullable=False),
sa.Column('available_quantity', sa.Float(), nullable=False),
sa.Column('received_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('best_before_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('original_expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('transformation_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('final_expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('unit_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('total_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('storage_location', sa.String(100), nullable=True),
sa.Column('warehouse_zone', sa.String(50), nullable=True),
sa.Column('shelf_position', sa.String(50), nullable=True),
sa.Column('requires_refrigeration', sa.Boolean(), nullable=True),
sa.Column('requires_freezing', sa.Boolean(), nullable=True),
sa.Column('storage_temperature_min', sa.Float(), nullable=True),
sa.Column('storage_temperature_max', sa.Float(), nullable=True),
sa.Column('storage_humidity_max', sa.Float(), nullable=True),
sa.Column('shelf_life_days', sa.Integer(), nullable=True),
sa.Column('storage_instructions', sa.Text(), nullable=True),
sa.Column('is_available', sa.Boolean(), nullable=True),
sa.Column('is_expired', sa.Boolean(), nullable=True),
sa.Column('quality_status', sa.String(20), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_stock_tenant_id'), 'stock', ['tenant_id'], unique=False)
op.create_index(op.f('ix_stock_ingredient_id'), 'stock', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_supplier_id'), 'stock', ['supplier_id'], unique=False)
op.create_index(op.f('ix_stock_batch_number'), 'stock', ['batch_number'], unique=False)
op.create_index(op.f('ix_stock_lot_number'), 'stock', ['lot_number'], unique=False)
op.create_index(op.f('ix_stock_transformation_reference'), 'stock', ['transformation_reference'], unique=False)
op.create_index(op.f('ix_stock_expiration_date'), 'stock', ['expiration_date'], unique=False)
op.create_index(op.f('ix_stock_is_expired'), 'stock', ['is_expired'], unique=False)
op.create_index('idx_stock_tenant_ingredient', 'stock', ['tenant_id', 'ingredient_id'], unique=False)
op.create_index('idx_stock_expiration', 'stock', ['tenant_id', 'expiration_date', 'is_available'], unique=False)
op.create_index('idx_stock_batch', 'stock', ['tenant_id', 'batch_number'], unique=False)
op.create_index('idx_stock_low_levels', 'stock', ['tenant_id', 'current_quantity', 'is_available'], unique=False)
op.create_index('idx_stock_quality', 'stock', ['tenant_id', 'quality_status', 'is_available'], unique=False)
op.create_index('idx_stock_production_stage', 'stock', ['tenant_id', 'production_stage', 'is_available'], unique=False)
op.create_index('idx_stock_transformation', 'stock', ['tenant_id', 'transformation_reference'], unique=False)
op.create_index('idx_stock_final_expiration', 'stock', ['tenant_id', 'final_expiration_date', 'is_available'], unique=False)
# Create stock_movements table
op.create_table('stock_movements',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('movement_type', sa.Enum('PURCHASE', 'PRODUCTION_USE', 'ADJUSTMENT', 'WASTE', 'TRANSFER', 'RETURN', 'INITIAL_STOCK', name='stockmovementtype'), nullable=False),
sa.Column('quantity', sa.Float(), nullable=False),
sa.Column('unit_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('total_cost', sa.Numeric(10, 2), nullable=True),
sa.Column('quantity_before', sa.Float(), nullable=True),
sa.Column('quantity_after', sa.Float(), nullable=True),
sa.Column('reference_number', sa.String(100), nullable=True),
sa.Column('supplier_id', sa.UUID(), nullable=True),
sa.Column('notes', sa.Text(), nullable=True),
sa.Column('reason_code', sa.String(50), nullable=True),
sa.Column('movement_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_stock_movements_tenant_id'), 'stock_movements', ['tenant_id'], unique=False)
op.create_index(op.f('ix_stock_movements_ingredient_id'), 'stock_movements', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_movements_stock_id'), 'stock_movements', ['stock_id'], unique=False)
op.create_index(op.f('ix_stock_movements_movement_type'), 'stock_movements', ['movement_type'], unique=False)
op.create_index(op.f('ix_stock_movements_reference_number'), 'stock_movements', ['reference_number'], unique=False)
op.create_index(op.f('ix_stock_movements_supplier_id'), 'stock_movements', ['supplier_id'], unique=False)
op.create_index(op.f('ix_stock_movements_movement_date'), 'stock_movements', ['movement_date'], unique=False)
op.create_index('idx_movements_tenant_date', 'stock_movements', ['tenant_id', 'movement_date'], unique=False)
op.create_index('idx_movements_tenant_ingredient', 'stock_movements', ['tenant_id', 'ingredient_id', 'movement_date'], unique=False)
op.create_index('idx_movements_type', 'stock_movements', ['tenant_id', 'movement_type', 'movement_date'], unique=False)
op.create_index('idx_movements_reference', 'stock_movements', ['reference_number'], unique=False)
op.create_index('idx_movements_supplier', 'stock_movements', ['supplier_id', 'movement_date'], unique=False)
# Create product_transformations table
op.create_table('product_transformations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('transformation_reference', sa.String(100), nullable=False),
sa.Column('source_ingredient_id', sa.UUID(), nullable=False),
sa.Column('target_ingredient_id', sa.UUID(), nullable=False),
sa.Column('source_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('target_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('source_quantity', sa.Float(), nullable=False),
sa.Column('target_quantity', sa.Float(), nullable=False),
sa.Column('conversion_ratio', sa.Float(), nullable=False),
sa.Column('expiration_calculation_method', sa.String(50), nullable=False),
sa.Column('expiration_days_offset', sa.Integer(), nullable=True),
sa.Column('transformation_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('process_notes', sa.Text(), nullable=True),
sa.Column('performed_by', sa.UUID(), nullable=True),
sa.Column('source_batch_numbers', sa.Text(), nullable=True),
sa.Column('target_batch_number', sa.String(100), nullable=True),
sa.Column('is_completed', sa.Boolean(), nullable=True),
sa.Column('is_reversed', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['source_ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['target_ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_product_transformations_tenant_id'), 'product_transformations', ['tenant_id'], unique=False)
op.create_index(op.f('ix_product_transformations_transformation_reference'), 'product_transformations', ['transformation_reference'], unique=False)
op.create_index(op.f('ix_product_transformations_source_ingredient_id'), 'product_transformations', ['source_ingredient_id'], unique=False)
op.create_index(op.f('ix_product_transformations_target_ingredient_id'), 'product_transformations', ['target_ingredient_id'], unique=False)
op.create_index('idx_transformations_tenant_date', 'product_transformations', ['tenant_id', 'transformation_date'], unique=False)
op.create_index('idx_transformations_reference', 'product_transformations', ['transformation_reference'], unique=False)
op.create_index('idx_transformations_source', 'product_transformations', ['tenant_id', 'source_ingredient_id'], unique=False)
op.create_index('idx_transformations_target', 'product_transformations', ['tenant_id', 'target_ingredient_id'], unique=False)
op.create_index('idx_transformations_stages', 'product_transformations', ['source_stage', 'target_stage'], unique=False)
# Create stock_alerts table
op.create_table('stock_alerts',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('alert_type', sa.String(50), nullable=False),
sa.Column('severity', sa.String(20), nullable=False),
sa.Column('title', sa.String(255), nullable=False),
sa.Column('message', sa.Text(), nullable=False),
sa.Column('current_quantity', sa.Float(), nullable=True),
sa.Column('threshold_value', sa.Float(), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('is_acknowledged', sa.Boolean(), nullable=True),
sa.Column('acknowledged_by', sa.UUID(), nullable=True),
sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_resolved', sa.Boolean(), nullable=True),
sa.Column('resolved_by', sa.UUID(), nullable=True),
sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('resolution_notes', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_stock_alerts_tenant_id'), 'stock_alerts', ['tenant_id'], unique=False)
op.create_index(op.f('ix_stock_alerts_ingredient_id'), 'stock_alerts', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_alerts_stock_id'), 'stock_alerts', ['stock_id'], unique=False)
op.create_index(op.f('ix_stock_alerts_alert_type'), 'stock_alerts', ['alert_type'], unique=False)
op.create_index(op.f('ix_stock_alerts_is_resolved'), 'stock_alerts', ['is_resolved'], unique=False)
op.create_index(op.f('ix_stock_alerts_is_active'), 'stock_alerts', ['is_active'], unique=False)
op.create_index('idx_alerts_tenant_active', 'stock_alerts', ['tenant_id', 'is_active', 'created_at'], unique=False)
op.create_index('idx_alerts_type_severity', 'stock_alerts', ['alert_type', 'severity', 'is_active'], unique=False)
op.create_index('idx_alerts_ingredient', 'stock_alerts', ['ingredient_id', 'is_active'], unique=False)
op.create_index('idx_alerts_unresolved', 'stock_alerts', ['tenant_id', 'is_resolved', 'is_active'], unique=False)
# Create food_safety_compliance table
op.create_table('food_safety_compliance',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('standard', sa.Enum('HACCP', 'FDA', 'USDA', 'FSMA', 'SQF', 'BRC', 'IFS', 'ISO22000', 'ORGANIC', 'NON_GMO', 'ALLERGEN_FREE', 'KOSHER', 'HALAL', name='foodsafetystandard'), nullable=False),
sa.Column('compliance_status', sa.Enum('COMPLIANT', 'NON_COMPLIANT', 'PENDING_REVIEW', 'EXPIRED', 'WARNING', name='compliancestatus'), nullable=False),
sa.Column('certification_number', sa.String(100), nullable=True),
sa.Column('certifying_body', sa.String(200), nullable=True),
sa.Column('certification_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('requirements', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('compliance_notes', sa.Text(), nullable=True),
sa.Column('documentation_url', sa.String(500), nullable=True),
sa.Column('last_audit_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('next_audit_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('auditor_name', sa.String(200), nullable=True),
sa.Column('audit_score', sa.Float(), nullable=True),
sa.Column('risk_level', sa.String(20), nullable=False),
sa.Column('risk_factors', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('mitigation_measures', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('requires_monitoring', sa.Boolean(), nullable=False),
sa.Column('monitoring_frequency_days', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.Column('updated_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_food_safety_compliance_tenant_id'), 'food_safety_compliance', ['tenant_id'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_ingredient_id'), 'food_safety_compliance', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_standard'), 'food_safety_compliance', ['standard'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_expiration_date'), 'food_safety_compliance', ['expiration_date'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_next_audit_date'), 'food_safety_compliance', ['next_audit_date'], unique=False)
# Create temperature_logs table
op.create_table('temperature_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('storage_location', sa.String(100), nullable=False),
sa.Column('warehouse_zone', sa.String(50), nullable=True),
sa.Column('equipment_id', sa.String(100), nullable=True),
sa.Column('temperature_celsius', sa.Float(), nullable=False),
sa.Column('humidity_percentage', sa.Float(), nullable=True),
sa.Column('target_temperature_min', sa.Float(), nullable=True),
sa.Column('target_temperature_max', sa.Float(), nullable=True),
sa.Column('is_within_range', sa.Boolean(), nullable=False),
sa.Column('alert_triggered', sa.Boolean(), nullable=False),
sa.Column('deviation_minutes', sa.Integer(), nullable=True),
sa.Column('measurement_method', sa.String(50), nullable=False),
sa.Column('device_id', sa.String(100), nullable=True),
sa.Column('calibration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('recorded_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=False),
sa.Column('recorded_by', sa.UUID(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_temperature_logs_tenant_id'), 'temperature_logs', ['tenant_id'], unique=False)
op.create_index(op.f('ix_temperature_logs_storage_location'), 'temperature_logs', ['storage_location'], unique=False)
op.create_index(op.f('ix_temperature_logs_equipment_id'), 'temperature_logs', ['equipment_id'], unique=False)
op.create_index(op.f('ix_temperature_logs_recorded_at'), 'temperature_logs', ['recorded_at'], unique=False)
# Create food_safety_alerts table
op.create_table('food_safety_alerts',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('alert_code', sa.String(50), nullable=False),
sa.Column('alert_type', sa.Enum('TEMPERATURE_VIOLATION', 'EXPIRATION_WARNING', 'EXPIRED_PRODUCT', 'CONTAMINATION_RISK', 'ALLERGEN_CROSS_CONTAMINATION', 'STORAGE_VIOLATION', 'QUALITY_DEGRADATION', 'RECALL_NOTICE', 'CERTIFICATION_EXPIRY', 'SUPPLIER_COMPLIANCE_ISSUE', name='foodsafetyalerttype'), nullable=False),
sa.Column('severity', sa.String(20), nullable=False),
sa.Column('risk_level', sa.String(20), nullable=False),
sa.Column('source_entity_type', sa.String(50), nullable=False),
sa.Column('source_entity_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=True),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('title', sa.String(200), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.Column('detailed_message', sa.Text(), nullable=True),
sa.Column('regulatory_requirement', sa.String(100), nullable=True),
sa.Column('compliance_standard', sa.Enum('HACCP', 'FDA', 'USDA', 'FSMA', 'SQF', 'BRC', 'IFS', 'ISO22000', 'ORGANIC', 'NON_GMO', 'ALLERGEN_FREE', 'KOSHER', 'HALAL', name='foodsafetystandard'), nullable=True),
sa.Column('regulatory_action_required', sa.Boolean(), nullable=False),
sa.Column('trigger_condition', sa.String(200), nullable=True),
sa.Column('threshold_value', sa.Numeric(15, 4), nullable=True),
sa.Column('actual_value', sa.Numeric(15, 4), nullable=True),
sa.Column('alert_data', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('environmental_factors', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('affected_products', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('public_health_risk', sa.Boolean(), nullable=False),
sa.Column('business_impact', sa.Text(), nullable=True),
sa.Column('estimated_loss', sa.Numeric(12, 2), nullable=True),
sa.Column('status', sa.String(50), nullable=False),
sa.Column('alert_state', sa.String(50), nullable=False),
sa.Column('immediate_actions_taken', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('investigation_notes', sa.Text(), nullable=True),
sa.Column('resolution_action', sa.String(200), nullable=True),
sa.Column('resolution_notes', sa.Text(), nullable=True),
sa.Column('corrective_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('preventive_measures', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('first_occurred_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('last_occurred_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('escalation_deadline', sa.DateTime(timezone=True), nullable=True),
sa.Column('occurrence_count', sa.Integer(), nullable=False),
sa.Column('is_recurring', sa.Boolean(), nullable=False),
sa.Column('recurrence_pattern', sa.String(100), nullable=True),
sa.Column('assigned_to', sa.UUID(), nullable=True),
sa.Column('assigned_role', sa.String(50), nullable=True),
sa.Column('escalated_to', sa.UUID(), nullable=True),
sa.Column('escalation_level', sa.Integer(), nullable=False),
sa.Column('notification_sent', sa.Boolean(), nullable=False),
sa.Column('notification_methods', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('notification_recipients', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('regulatory_notification_required', sa.Boolean(), nullable=False),
sa.Column('regulatory_notification_sent', sa.Boolean(), nullable=False),
sa.Column('documentation', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('audit_trail', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('external_reference', sa.String(100), nullable=True),
sa.Column('detection_time', sa.DateTime(timezone=True), nullable=True),
sa.Column('response_time_minutes', sa.Integer(), nullable=True),
sa.Column('resolution_time_minutes', sa.Integer(), nullable=True),
sa.Column('alert_accuracy', sa.Boolean(), nullable=True),
sa.Column('false_positive', sa.Boolean(), nullable=False),
sa.Column('feedback_notes', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('timezone(\'utc\', now())'), nullable=False),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.Column('updated_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_food_safety_alerts_tenant_id'), 'food_safety_alerts', ['tenant_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_alert_code'), 'food_safety_alerts', ['alert_code'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_alert_type'), 'food_safety_alerts', ['alert_type'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_severity'), 'food_safety_alerts', ['severity'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_source_entity_id'), 'food_safety_alerts', ['source_entity_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_ingredient_id'), 'food_safety_alerts', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_stock_id'), 'food_safety_alerts', ['stock_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_first_occurred_at'), 'food_safety_alerts', ['first_occurred_at'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_status'), 'food_safety_alerts', ['status'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_assigned_to'), 'food_safety_alerts', ['assigned_to'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_escalated_to'), 'food_safety_alerts', ['escalated_to'], unique=False)
def downgrade() -> None:
# Drop food_safety_alerts table
op.drop_index(op.f('ix_food_safety_alerts_escalated_to'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_assigned_to'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_status'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_first_occurred_at'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_stock_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_ingredient_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_source_entity_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_severity'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_alert_type'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_alert_code'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_tenant_id'), table_name='food_safety_alerts')
op.drop_table('food_safety_alerts')
# Drop temperature_logs table
op.drop_index(op.f('ix_temperature_logs_recorded_at'), table_name='temperature_logs')
op.drop_index(op.f('ix_temperature_logs_equipment_id'), table_name='temperature_logs')
op.drop_index(op.f('ix_temperature_logs_storage_location'), table_name='temperature_logs')
op.drop_index(op.f('ix_temperature_logs_tenant_id'), table_name='temperature_logs')
op.drop_table('temperature_logs')
# Drop food_safety_compliance table
op.drop_index(op.f('ix_food_safety_compliance_next_audit_date'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_expiration_date'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_standard'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_ingredient_id'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_tenant_id'), table_name='food_safety_compliance')
op.drop_table('food_safety_compliance')
# Drop stock_alerts table
op.drop_index('idx_alerts_unresolved', table_name='stock_alerts')
op.drop_index('idx_alerts_ingredient', table_name='stock_alerts')
op.drop_index('idx_alerts_type_severity', table_name='stock_alerts')
op.drop_index('idx_alerts_tenant_active', table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_is_active'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_is_resolved'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_alert_type'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_stock_id'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_ingredient_id'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_tenant_id'), table_name='stock_alerts')
op.drop_table('stock_alerts')
# Drop product_transformations table
op.drop_index('idx_transformations_stages', table_name='product_transformations')
op.drop_index('idx_transformations_target', table_name='product_transformations')
op.drop_index('idx_transformations_source', table_name='product_transformations')
op.drop_index('idx_transformations_reference', table_name='product_transformations')
op.drop_index('idx_transformations_tenant_date', table_name='product_transformations')
op.drop_index(op.f('ix_product_transformations_target_ingredient_id'), table_name='product_transformations')
op.drop_index(op.f('ix_product_transformations_source_ingredient_id'), table_name='product_transformations')
op.drop_index(op.f('ix_product_transformations_transformation_reference'), table_name='product_transformations')
op.drop_index(op.f('ix_product_transformations_tenant_id'), table_name='product_transformations')
op.drop_table('product_transformations')
# Drop stock_movements table
op.drop_index('idx_movements_supplier', table_name='stock_movements')
op.drop_index('idx_movements_reference', table_name='stock_movements')
op.drop_index('idx_movements_type', table_name='stock_movements')
op.drop_index('idx_movements_tenant_ingredient', table_name='stock_movements')
op.drop_index('idx_movements_tenant_date', table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_movement_date'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_supplier_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_reference_number'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_movement_type'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_stock_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_ingredient_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_tenant_id'), table_name='stock_movements')
op.drop_table('stock_movements')
# Drop stock table
op.drop_index('idx_stock_final_expiration', table_name='stock')
op.drop_index('idx_stock_transformation', table_name='stock')
op.drop_index('idx_stock_production_stage', table_name='stock')
op.drop_index('idx_stock_quality', table_name='stock')
op.drop_index('idx_stock_low_levels', table_name='stock')
op.drop_index('idx_stock_batch', table_name='stock')
op.drop_index('idx_stock_expiration', table_name='stock')
op.drop_index('idx_stock_tenant_ingredient', table_name='stock')
op.drop_index(op.f('ix_stock_is_expired'), table_name='stock')
op.drop_index(op.f('ix_stock_expiration_date'), table_name='stock')
op.drop_index(op.f('ix_stock_transformation_reference'), table_name='stock')
op.drop_index(op.f('ix_stock_lot_number'), table_name='stock')
op.drop_index(op.f('ix_stock_batch_number'), table_name='stock')
op.drop_index(op.f('ix_stock_supplier_id'), table_name='stock')
op.drop_index(op.f('ix_stock_ingredient_id'), table_name='stock')
op.drop_index(op.f('ix_stock_tenant_id'), table_name='stock')
op.drop_table('stock')
# Drop ingredients table
op.drop_index('idx_ingredients_stock_levels', table_name='ingredients')
op.drop_index('idx_ingredients_product_category', table_name='ingredients')
op.drop_index('idx_ingredients_ingredient_category', table_name='ingredients')
op.drop_index('idx_ingredients_product_type', table_name='ingredients')
op.drop_index('idx_ingredients_barcode', table_name='ingredients')
op.drop_index('idx_ingredients_tenant_sku', table_name='ingredients')
op.drop_index('idx_ingredients_tenant_name', table_name='ingredients')
op.drop_index(op.f('ix_ingredients_product_category'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_ingredient_category'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_product_type'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_barcode'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_sku'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_name'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_tenant_id'), table_name='ingredients')
op.drop_table('ingredients')

View File

@@ -0,0 +1,456 @@
"""initial_schema_20251001_1118
Revision ID: d0a91cdc45f1
Revises:
Create Date: 2025-10-01 11:19:01.146238+02:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'd0a91cdc45f1'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('ingredients',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('sku', sa.String(length=100), nullable=True),
sa.Column('barcode', sa.String(length=50), nullable=True),
sa.Column('product_type', sa.Enum('INGREDIENT', 'FINISHED_PRODUCT', name='producttype'), nullable=False),
sa.Column('ingredient_category', sa.Enum('FLOUR', 'YEAST', 'DAIRY', 'EGGS', 'SUGAR', 'FATS', 'SALT', 'SPICES', 'ADDITIVES', 'PACKAGING', 'CLEANING', 'OTHER', name='ingredientcategory'), nullable=True),
sa.Column('product_category', sa.Enum('BREAD', 'CROISSANTS', 'PASTRIES', 'CAKES', 'COOKIES', 'MUFFINS', 'SANDWICHES', 'SEASONAL', 'BEVERAGES', 'OTHER_PRODUCTS', name='productcategory'), nullable=True),
sa.Column('subcategory', sa.String(length=100), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('brand', sa.String(length=100), nullable=True),
sa.Column('unit_of_measure', sa.Enum('KILOGRAMS', 'GRAMS', 'LITERS', 'MILLILITERS', 'UNITS', 'PIECES', 'PACKAGES', 'BAGS', 'BOXES', name='unitofmeasure'), nullable=False),
sa.Column('package_size', sa.Float(), nullable=True),
sa.Column('average_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('last_purchase_price', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('standard_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('low_stock_threshold', sa.Float(), nullable=False),
sa.Column('reorder_point', sa.Float(), nullable=False),
sa.Column('reorder_quantity', sa.Float(), nullable=False),
sa.Column('max_stock_level', sa.Float(), nullable=True),
sa.Column('shelf_life_days', sa.Integer(), nullable=True),
sa.Column('display_life_hours', sa.Integer(), nullable=True),
sa.Column('best_before_hours', sa.Integer(), nullable=True),
sa.Column('storage_instructions', sa.Text(), nullable=True),
sa.Column('central_baker_product_code', sa.String(length=100), nullable=True),
sa.Column('delivery_days', sa.String(length=20), nullable=True),
sa.Column('minimum_order_quantity', sa.Float(), nullable=True),
sa.Column('pack_size', sa.Integer(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('is_perishable', sa.Boolean(), nullable=True),
sa.Column('allergen_info', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('nutritional_info', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_ingredients_barcode', 'ingredients', ['barcode'], unique=False)
op.create_index('idx_ingredients_ingredient_category', 'ingredients', ['tenant_id', 'ingredient_category', 'is_active'], unique=False)
op.create_index('idx_ingredients_product_category', 'ingredients', ['tenant_id', 'product_category', 'is_active'], unique=False)
op.create_index('idx_ingredients_product_type', 'ingredients', ['tenant_id', 'product_type', 'is_active'], unique=False)
op.create_index('idx_ingredients_stock_levels', 'ingredients', ['tenant_id', 'low_stock_threshold', 'reorder_point'], unique=False)
op.create_index('idx_ingredients_tenant_name', 'ingredients', ['tenant_id', 'name'], unique=True)
op.create_index('idx_ingredients_tenant_sku', 'ingredients', ['tenant_id', 'sku'], unique=False)
op.create_index(op.f('ix_ingredients_barcode'), 'ingredients', ['barcode'], unique=False)
op.create_index(op.f('ix_ingredients_ingredient_category'), 'ingredients', ['ingredient_category'], unique=False)
op.create_index(op.f('ix_ingredients_name'), 'ingredients', ['name'], unique=False)
op.create_index(op.f('ix_ingredients_product_category'), 'ingredients', ['product_category'], unique=False)
op.create_index(op.f('ix_ingredients_product_type'), 'ingredients', ['product_type'], unique=False)
op.create_index(op.f('ix_ingredients_sku'), 'ingredients', ['sku'], unique=False)
op.create_index(op.f('ix_ingredients_tenant_id'), 'ingredients', ['tenant_id'], unique=False)
op.create_table('temperature_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('storage_location', sa.String(length=100), nullable=False),
sa.Column('warehouse_zone', sa.String(length=50), nullable=True),
sa.Column('equipment_id', sa.String(length=100), nullable=True),
sa.Column('temperature_celsius', sa.Float(), nullable=False),
sa.Column('humidity_percentage', sa.Float(), nullable=True),
sa.Column('target_temperature_min', sa.Float(), nullable=True),
sa.Column('target_temperature_max', sa.Float(), nullable=True),
sa.Column('is_within_range', sa.Boolean(), nullable=False),
sa.Column('alert_triggered', sa.Boolean(), nullable=False),
sa.Column('deviation_minutes', sa.Integer(), nullable=True),
sa.Column('measurement_method', sa.String(length=50), nullable=False),
sa.Column('device_id', sa.String(length=100), nullable=True),
sa.Column('calibration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('recorded_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('recorded_by', sa.UUID(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_temperature_logs_recorded_at'), 'temperature_logs', ['recorded_at'], unique=False)
op.create_index(op.f('ix_temperature_logs_storage_location'), 'temperature_logs', ['storage_location'], unique=False)
op.create_index(op.f('ix_temperature_logs_tenant_id'), 'temperature_logs', ['tenant_id'], unique=False)
op.create_table('food_safety_compliance',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('standard', sa.Enum('HACCP', 'FDA', 'USDA', 'FSMA', 'SQF', 'BRC', 'IFS', 'ISO22000', 'ORGANIC', 'NON_GMO', 'ALLERGEN_FREE', 'KOSHER', 'HALAL', name='foodsafetystandard'), nullable=False),
sa.Column('compliance_status', sa.Enum('COMPLIANT', 'NON_COMPLIANT', 'PENDING_REVIEW', 'EXPIRED', 'WARNING', name='compliancestatus'), nullable=False),
sa.Column('certification_number', sa.String(length=100), nullable=True),
sa.Column('certifying_body', sa.String(length=200), nullable=True),
sa.Column('certification_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('requirements', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('compliance_notes', sa.Text(), nullable=True),
sa.Column('documentation_url', sa.String(length=500), nullable=True),
sa.Column('last_audit_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('next_audit_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('auditor_name', sa.String(length=200), nullable=True),
sa.Column('audit_score', sa.Float(), nullable=True),
sa.Column('risk_level', sa.String(length=20), nullable=False),
sa.Column('risk_factors', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('mitigation_measures', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('requires_monitoring', sa.Boolean(), nullable=False),
sa.Column('monitoring_frequency_days', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.Column('updated_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_food_safety_compliance_expiration_date'), 'food_safety_compliance', ['expiration_date'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_ingredient_id'), 'food_safety_compliance', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_next_audit_date'), 'food_safety_compliance', ['next_audit_date'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_standard'), 'food_safety_compliance', ['standard'], unique=False)
op.create_index(op.f('ix_food_safety_compliance_tenant_id'), 'food_safety_compliance', ['tenant_id'], unique=False)
op.create_table('product_transformations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('transformation_reference', sa.String(length=100), nullable=False),
sa.Column('source_ingredient_id', sa.UUID(), nullable=False),
sa.Column('target_ingredient_id', sa.UUID(), nullable=False),
sa.Column('source_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('target_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('source_quantity', sa.Float(), nullable=False),
sa.Column('target_quantity', sa.Float(), nullable=False),
sa.Column('conversion_ratio', sa.Float(), nullable=False),
sa.Column('expiration_calculation_method', sa.String(length=50), nullable=False),
sa.Column('expiration_days_offset', sa.Integer(), nullable=True),
sa.Column('transformation_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('process_notes', sa.Text(), nullable=True),
sa.Column('performed_by', sa.UUID(), nullable=True),
sa.Column('source_batch_numbers', sa.Text(), nullable=True),
sa.Column('target_batch_number', sa.String(length=100), nullable=True),
sa.Column('is_completed', sa.Boolean(), nullable=True),
sa.Column('is_reversed', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['source_ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['target_ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_transformations_reference', 'product_transformations', ['transformation_reference'], unique=False)
op.create_index('idx_transformations_source', 'product_transformations', ['tenant_id', 'source_ingredient_id'], unique=False)
op.create_index('idx_transformations_stages', 'product_transformations', ['source_stage', 'target_stage'], unique=False)
op.create_index('idx_transformations_target', 'product_transformations', ['tenant_id', 'target_ingredient_id'], unique=False)
op.create_index('idx_transformations_tenant_date', 'product_transformations', ['tenant_id', 'transformation_date'], unique=False)
op.create_index(op.f('ix_product_transformations_tenant_id'), 'product_transformations', ['tenant_id'], unique=False)
op.create_index(op.f('ix_product_transformations_transformation_reference'), 'product_transformations', ['transformation_reference'], unique=False)
op.create_table('stock',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('supplier_id', sa.UUID(), nullable=True),
sa.Column('batch_number', sa.String(length=100), nullable=True),
sa.Column('lot_number', sa.String(length=100), nullable=True),
sa.Column('supplier_batch_ref', sa.String(length=100), nullable=True),
sa.Column('production_stage', sa.Enum('raw_ingredient', 'par_baked', 'fully_baked', 'prepared_dough', 'frozen_product', name='productionstage'), nullable=False),
sa.Column('transformation_reference', sa.String(length=100), nullable=True),
sa.Column('current_quantity', sa.Float(), nullable=False),
sa.Column('reserved_quantity', sa.Float(), nullable=False),
sa.Column('available_quantity', sa.Float(), nullable=False),
sa.Column('received_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('best_before_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('original_expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('transformation_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('final_expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('unit_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('total_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('storage_location', sa.String(length=100), nullable=True),
sa.Column('warehouse_zone', sa.String(length=50), nullable=True),
sa.Column('shelf_position', sa.String(length=50), nullable=True),
sa.Column('requires_refrigeration', sa.Boolean(), nullable=True),
sa.Column('requires_freezing', sa.Boolean(), nullable=True),
sa.Column('storage_temperature_min', sa.Float(), nullable=True),
sa.Column('storage_temperature_max', sa.Float(), nullable=True),
sa.Column('storage_humidity_max', sa.Float(), nullable=True),
sa.Column('shelf_life_days', sa.Integer(), nullable=True),
sa.Column('storage_instructions', sa.Text(), nullable=True),
sa.Column('is_available', sa.Boolean(), nullable=True),
sa.Column('is_expired', sa.Boolean(), nullable=True),
sa.Column('quality_status', sa.String(length=20), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_stock_batch', 'stock', ['tenant_id', 'batch_number'], unique=False)
op.create_index('idx_stock_expiration', 'stock', ['tenant_id', 'expiration_date', 'is_available'], unique=False)
op.create_index('idx_stock_final_expiration', 'stock', ['tenant_id', 'final_expiration_date', 'is_available'], unique=False)
op.create_index('idx_stock_low_levels', 'stock', ['tenant_id', 'current_quantity', 'is_available'], unique=False)
op.create_index('idx_stock_production_stage', 'stock', ['tenant_id', 'production_stage', 'is_available'], unique=False)
op.create_index('idx_stock_quality', 'stock', ['tenant_id', 'quality_status', 'is_available'], unique=False)
op.create_index('idx_stock_tenant_ingredient', 'stock', ['tenant_id', 'ingredient_id'], unique=False)
op.create_index('idx_stock_transformation', 'stock', ['tenant_id', 'transformation_reference'], unique=False)
op.create_index(op.f('ix_stock_batch_number'), 'stock', ['batch_number'], unique=False)
op.create_index(op.f('ix_stock_expiration_date'), 'stock', ['expiration_date'], unique=False)
op.create_index(op.f('ix_stock_ingredient_id'), 'stock', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_is_expired'), 'stock', ['is_expired'], unique=False)
op.create_index(op.f('ix_stock_lot_number'), 'stock', ['lot_number'], unique=False)
op.create_index(op.f('ix_stock_production_stage'), 'stock', ['production_stage'], unique=False)
op.create_index(op.f('ix_stock_supplier_id'), 'stock', ['supplier_id'], unique=False)
op.create_index(op.f('ix_stock_tenant_id'), 'stock', ['tenant_id'], unique=False)
op.create_index(op.f('ix_stock_transformation_reference'), 'stock', ['transformation_reference'], unique=False)
op.create_table('food_safety_alerts',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('alert_code', sa.String(length=50), nullable=False),
sa.Column('alert_type', sa.Enum('TEMPERATURE_VIOLATION', 'EXPIRATION_WARNING', 'EXPIRED_PRODUCT', 'CONTAMINATION_RISK', 'ALLERGEN_CROSS_CONTAMINATION', 'STORAGE_VIOLATION', 'QUALITY_DEGRADATION', 'RECALL_NOTICE', 'CERTIFICATION_EXPIRY', 'SUPPLIER_COMPLIANCE_ISSUE', name='foodsafetyalerttype'), nullable=False),
sa.Column('severity', sa.String(length=20), nullable=False),
sa.Column('risk_level', sa.String(length=20), nullable=False),
sa.Column('source_entity_type', sa.String(length=50), nullable=False),
sa.Column('source_entity_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=True),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('title', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.Column('detailed_message', sa.Text(), nullable=True),
sa.Column('regulatory_requirement', sa.String(length=100), nullable=True),
sa.Column('compliance_standard', sa.Enum('HACCP', 'FDA', 'USDA', 'FSMA', 'SQF', 'BRC', 'IFS', 'ISO22000', 'ORGANIC', 'NON_GMO', 'ALLERGEN_FREE', 'KOSHER', 'HALAL', name='foodsafetystandard'), nullable=True),
sa.Column('regulatory_action_required', sa.Boolean(), nullable=False),
sa.Column('trigger_condition', sa.String(length=200), nullable=True),
sa.Column('threshold_value', sa.Numeric(precision=15, scale=4), nullable=True),
sa.Column('actual_value', sa.Numeric(precision=15, scale=4), nullable=True),
sa.Column('alert_data', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('environmental_factors', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('affected_products', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('public_health_risk', sa.Boolean(), nullable=False),
sa.Column('business_impact', sa.Text(), nullable=True),
sa.Column('estimated_loss', sa.Numeric(precision=12, scale=2), nullable=True),
sa.Column('status', sa.String(length=50), nullable=False),
sa.Column('alert_state', sa.String(length=50), nullable=False),
sa.Column('immediate_actions_taken', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('investigation_notes', sa.Text(), nullable=True),
sa.Column('resolution_action', sa.String(length=200), nullable=True),
sa.Column('resolution_notes', sa.Text(), nullable=True),
sa.Column('corrective_actions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('preventive_measures', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('first_occurred_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('last_occurred_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('escalation_deadline', sa.DateTime(timezone=True), nullable=True),
sa.Column('occurrence_count', sa.Integer(), nullable=False),
sa.Column('is_recurring', sa.Boolean(), nullable=False),
sa.Column('recurrence_pattern', sa.String(length=100), nullable=True),
sa.Column('assigned_to', sa.UUID(), nullable=True),
sa.Column('assigned_role', sa.String(length=50), nullable=True),
sa.Column('escalated_to', sa.UUID(), nullable=True),
sa.Column('escalation_level', sa.Integer(), nullable=False),
sa.Column('notification_sent', sa.Boolean(), nullable=False),
sa.Column('notification_methods', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('notification_recipients', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('regulatory_notification_required', sa.Boolean(), nullable=False),
sa.Column('regulatory_notification_sent', sa.Boolean(), nullable=False),
sa.Column('documentation', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('audit_trail', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('external_reference', sa.String(length=100), nullable=True),
sa.Column('detection_time', sa.DateTime(timezone=True), nullable=True),
sa.Column('response_time_minutes', sa.Integer(), nullable=True),
sa.Column('resolution_time_minutes', sa.Integer(), nullable=True),
sa.Column('alert_accuracy', sa.Boolean(), nullable=True),
sa.Column('false_positive', sa.Boolean(), nullable=False),
sa.Column('feedback_notes', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.Column('updated_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_food_safety_alerts_alert_code'), 'food_safety_alerts', ['alert_code'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_alert_type'), 'food_safety_alerts', ['alert_type'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_first_occurred_at'), 'food_safety_alerts', ['first_occurred_at'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_ingredient_id'), 'food_safety_alerts', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_severity'), 'food_safety_alerts', ['severity'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_source_entity_id'), 'food_safety_alerts', ['source_entity_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_status'), 'food_safety_alerts', ['status'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_stock_id'), 'food_safety_alerts', ['stock_id'], unique=False)
op.create_index(op.f('ix_food_safety_alerts_tenant_id'), 'food_safety_alerts', ['tenant_id'], unique=False)
op.create_table('stock_alerts',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('alert_type', sa.String(length=50), nullable=False),
sa.Column('severity', sa.String(length=20), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('message', sa.Text(), nullable=False),
sa.Column('current_quantity', sa.Float(), nullable=True),
sa.Column('threshold_value', sa.Float(), nullable=True),
sa.Column('expiration_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.Column('is_acknowledged', sa.Boolean(), nullable=True),
sa.Column('acknowledged_by', sa.UUID(), nullable=True),
sa.Column('acknowledged_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_resolved', sa.Boolean(), nullable=True),
sa.Column('resolved_by', sa.UUID(), nullable=True),
sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('resolution_notes', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_alerts_ingredient', 'stock_alerts', ['ingredient_id', 'is_active'], unique=False)
op.create_index('idx_alerts_tenant_active', 'stock_alerts', ['tenant_id', 'is_active', 'created_at'], unique=False)
op.create_index('idx_alerts_type_severity', 'stock_alerts', ['alert_type', 'severity', 'is_active'], unique=False)
op.create_index('idx_alerts_unresolved', 'stock_alerts', ['tenant_id', 'is_resolved', 'is_active'], unique=False)
op.create_index(op.f('ix_stock_alerts_alert_type'), 'stock_alerts', ['alert_type'], unique=False)
op.create_index(op.f('ix_stock_alerts_ingredient_id'), 'stock_alerts', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_alerts_stock_id'), 'stock_alerts', ['stock_id'], unique=False)
op.create_index(op.f('ix_stock_alerts_tenant_id'), 'stock_alerts', ['tenant_id'], unique=False)
op.create_table('stock_movements',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('ingredient_id', sa.UUID(), nullable=False),
sa.Column('stock_id', sa.UUID(), nullable=True),
sa.Column('movement_type', sa.Enum('PURCHASE', 'PRODUCTION_USE', 'ADJUSTMENT', 'WASTE', 'TRANSFER', 'RETURN', 'INITIAL_STOCK', name='stockmovementtype'), nullable=False),
sa.Column('quantity', sa.Float(), nullable=False),
sa.Column('unit_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('total_cost', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('quantity_before', sa.Float(), nullable=True),
sa.Column('quantity_after', sa.Float(), nullable=True),
sa.Column('reference_number', sa.String(length=100), nullable=True),
sa.Column('supplier_id', sa.UUID(), nullable=True),
sa.Column('notes', sa.Text(), nullable=True),
sa.Column('reason_code', sa.String(length=50), nullable=True),
sa.Column('movement_date', sa.DateTime(timezone=True), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_by', sa.UUID(), nullable=True),
sa.ForeignKeyConstraint(['ingredient_id'], ['ingredients.id'], ),
sa.ForeignKeyConstraint(['stock_id'], ['stock.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_movements_reference', 'stock_movements', ['reference_number'], unique=False)
op.create_index('idx_movements_supplier', 'stock_movements', ['supplier_id', 'movement_date'], unique=False)
op.create_index('idx_movements_tenant_date', 'stock_movements', ['tenant_id', 'movement_date'], unique=False)
op.create_index('idx_movements_tenant_ingredient', 'stock_movements', ['tenant_id', 'ingredient_id', 'movement_date'], unique=False)
op.create_index('idx_movements_type', 'stock_movements', ['tenant_id', 'movement_type', 'movement_date'], unique=False)
op.create_index(op.f('ix_stock_movements_ingredient_id'), 'stock_movements', ['ingredient_id'], unique=False)
op.create_index(op.f('ix_stock_movements_movement_date'), 'stock_movements', ['movement_date'], unique=False)
op.create_index(op.f('ix_stock_movements_movement_type'), 'stock_movements', ['movement_type'], unique=False)
op.create_index(op.f('ix_stock_movements_reference_number'), 'stock_movements', ['reference_number'], unique=False)
op.create_index(op.f('ix_stock_movements_stock_id'), 'stock_movements', ['stock_id'], unique=False)
op.create_index(op.f('ix_stock_movements_supplier_id'), 'stock_movements', ['supplier_id'], unique=False)
op.create_index(op.f('ix_stock_movements_tenant_id'), 'stock_movements', ['tenant_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_stock_movements_tenant_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_supplier_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_stock_id'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_reference_number'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_movement_type'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_movement_date'), table_name='stock_movements')
op.drop_index(op.f('ix_stock_movements_ingredient_id'), table_name='stock_movements')
op.drop_index('idx_movements_type', table_name='stock_movements')
op.drop_index('idx_movements_tenant_ingredient', table_name='stock_movements')
op.drop_index('idx_movements_tenant_date', table_name='stock_movements')
op.drop_index('idx_movements_supplier', table_name='stock_movements')
op.drop_index('idx_movements_reference', table_name='stock_movements')
op.drop_table('stock_movements')
op.drop_index(op.f('ix_stock_alerts_tenant_id'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_stock_id'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_ingredient_id'), table_name='stock_alerts')
op.drop_index(op.f('ix_stock_alerts_alert_type'), table_name='stock_alerts')
op.drop_index('idx_alerts_unresolved', table_name='stock_alerts')
op.drop_index('idx_alerts_type_severity', table_name='stock_alerts')
op.drop_index('idx_alerts_tenant_active', table_name='stock_alerts')
op.drop_index('idx_alerts_ingredient', table_name='stock_alerts')
op.drop_table('stock_alerts')
op.drop_index(op.f('ix_food_safety_alerts_tenant_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_stock_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_status'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_source_entity_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_severity'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_ingredient_id'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_first_occurred_at'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_alert_type'), table_name='food_safety_alerts')
op.drop_index(op.f('ix_food_safety_alerts_alert_code'), table_name='food_safety_alerts')
op.drop_table('food_safety_alerts')
op.drop_index(op.f('ix_stock_transformation_reference'), table_name='stock')
op.drop_index(op.f('ix_stock_tenant_id'), table_name='stock')
op.drop_index(op.f('ix_stock_supplier_id'), table_name='stock')
op.drop_index(op.f('ix_stock_production_stage'), table_name='stock')
op.drop_index(op.f('ix_stock_lot_number'), table_name='stock')
op.drop_index(op.f('ix_stock_is_expired'), table_name='stock')
op.drop_index(op.f('ix_stock_ingredient_id'), table_name='stock')
op.drop_index(op.f('ix_stock_expiration_date'), table_name='stock')
op.drop_index(op.f('ix_stock_batch_number'), table_name='stock')
op.drop_index('idx_stock_transformation', table_name='stock')
op.drop_index('idx_stock_tenant_ingredient', table_name='stock')
op.drop_index('idx_stock_quality', table_name='stock')
op.drop_index('idx_stock_production_stage', table_name='stock')
op.drop_index('idx_stock_low_levels', table_name='stock')
op.drop_index('idx_stock_final_expiration', table_name='stock')
op.drop_index('idx_stock_expiration', table_name='stock')
op.drop_index('idx_stock_batch', table_name='stock')
op.drop_table('stock')
op.drop_index(op.f('ix_product_transformations_transformation_reference'), table_name='product_transformations')
op.drop_index(op.f('ix_product_transformations_tenant_id'), table_name='product_transformations')
op.drop_index('idx_transformations_tenant_date', table_name='product_transformations')
op.drop_index('idx_transformations_target', table_name='product_transformations')
op.drop_index('idx_transformations_stages', table_name='product_transformations')
op.drop_index('idx_transformations_source', table_name='product_transformations')
op.drop_index('idx_transformations_reference', table_name='product_transformations')
op.drop_table('product_transformations')
op.drop_index(op.f('ix_food_safety_compliance_tenant_id'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_standard'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_next_audit_date'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_ingredient_id'), table_name='food_safety_compliance')
op.drop_index(op.f('ix_food_safety_compliance_expiration_date'), table_name='food_safety_compliance')
op.drop_table('food_safety_compliance')
op.drop_index(op.f('ix_temperature_logs_tenant_id'), table_name='temperature_logs')
op.drop_index(op.f('ix_temperature_logs_storage_location'), table_name='temperature_logs')
op.drop_index(op.f('ix_temperature_logs_recorded_at'), table_name='temperature_logs')
op.drop_table('temperature_logs')
op.drop_index(op.f('ix_ingredients_tenant_id'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_sku'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_product_type'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_product_category'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_name'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_ingredient_category'), table_name='ingredients')
op.drop_index(op.f('ix_ingredients_barcode'), table_name='ingredients')
op.drop_index('idx_ingredients_tenant_sku', table_name='ingredients')
op.drop_index('idx_ingredients_tenant_name', table_name='ingredients')
op.drop_index('idx_ingredients_stock_levels', table_name='ingredients')
op.drop_index('idx_ingredients_product_type', table_name='ingredients')
op.drop_index('idx_ingredients_product_category', table_name='ingredients')
op.drop_index('idx_ingredients_ingredient_category', table_name='ingredients')
op.drop_index('idx_ingredients_barcode', table_name='ingredients')
op.drop_table('ingredients')
# ### end Alembic commands ###