"""add tenant_settings Revision ID: 20251022_0000 Revises: 20251017_0000 Create Date: 2025-10-22 """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql from uuid import uuid4 # revision identifiers, used by Alembic. revision = '20251022_0000' down_revision = '20251017_0000' branch_labels = None depends_on = None def get_default_settings(): """Get default settings for all categories""" return { "procurement_settings": { "auto_approve_enabled": True, "auto_approve_threshold_eur": 500.0, "auto_approve_min_supplier_score": 0.80, "require_approval_new_suppliers": True, "require_approval_critical_items": True, "procurement_lead_time_days": 3, "demand_forecast_days": 14, "safety_stock_percentage": 20.0, "po_approval_reminder_hours": 24, "po_critical_escalation_hours": 12 }, "inventory_settings": { "low_stock_threshold": 10, "reorder_point": 20, "reorder_quantity": 50, "expiring_soon_days": 7, "expiration_warning_days": 3, "quality_score_threshold": 8.0, "temperature_monitoring_enabled": True, "refrigeration_temp_min": 1.0, "refrigeration_temp_max": 4.0, "freezer_temp_min": -20.0, "freezer_temp_max": -15.0, "room_temp_min": 18.0, "room_temp_max": 25.0, "temp_deviation_alert_minutes": 15, "critical_temp_deviation_minutes": 5 }, "production_settings": { "planning_horizon_days": 7, "minimum_batch_size": 1.0, "maximum_batch_size": 100.0, "production_buffer_percentage": 10.0, "working_hours_per_day": 12, "max_overtime_hours": 4, "capacity_utilization_target": 0.85, "capacity_warning_threshold": 0.95, "quality_check_enabled": True, "minimum_yield_percentage": 85.0, "quality_score_threshold": 8.0, "schedule_optimization_enabled": True, "prep_time_buffer_minutes": 30, "cleanup_time_buffer_minutes": 15, "labor_cost_per_hour_eur": 15.0, "overhead_cost_percentage": 20.0 }, "supplier_settings": { "default_payment_terms_days": 30, "default_delivery_days": 3, "excellent_delivery_rate": 95.0, "good_delivery_rate": 90.0, "excellent_quality_rate": 98.0, "good_quality_rate": 95.0, "critical_delivery_delay_hours": 24, "critical_quality_rejection_rate": 10.0, "high_cost_variance_percentage": 15.0 }, "pos_settings": { "sync_interval_minutes": 5, "auto_sync_products": True, "auto_sync_transactions": True }, "order_settings": { "max_discount_percentage": 50.0, "default_delivery_window_hours": 48, "dynamic_pricing_enabled": False, "discount_enabled": True, "delivery_tracking_enabled": True } } def upgrade(): """Create tenant_settings table and seed existing tenants""" # Create tenant_settings table op.create_table( 'tenant_settings', sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True, default=uuid4), sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False), sa.Column('procurement_settings', postgresql.JSON(), nullable=False), sa.Column('inventory_settings', postgresql.JSON(), nullable=False), sa.Column('production_settings', postgresql.JSON(), nullable=False), sa.Column('supplier_settings', postgresql.JSON(), nullable=False), sa.Column('pos_settings', postgresql.JSON(), nullable=False), sa.Column('order_settings', postgresql.JSON(), nullable=False), 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.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ondelete='CASCADE'), sa.UniqueConstraint('tenant_id', name='uq_tenant_settings_tenant_id') ) # Create indexes op.create_index('ix_tenant_settings_tenant_id', 'tenant_settings', ['tenant_id']) # Seed existing tenants with default settings connection = op.get_bind() # Get all existing tenant IDs result = connection.execute(sa.text("SELECT id FROM tenants")) tenant_ids = [row[0] for row in result] # Insert default settings for each existing tenant defaults = get_default_settings() for tenant_id in tenant_ids: connection.execute( sa.text(""" INSERT INTO tenant_settings ( id, tenant_id, procurement_settings, inventory_settings, production_settings, supplier_settings, pos_settings, order_settings ) VALUES ( :id, :tenant_id, :procurement_settings::jsonb, :inventory_settings::jsonb, :production_settings::jsonb, :supplier_settings::jsonb, :pos_settings::jsonb, :order_settings::jsonb ) """), { "id": str(uuid4()), "tenant_id": tenant_id, "procurement_settings": str(defaults["procurement_settings"]).replace("'", '"').replace("True", "true").replace("False", "false"), "inventory_settings": str(defaults["inventory_settings"]).replace("'", '"').replace("True", "true").replace("False", "false"), "production_settings": str(defaults["production_settings"]).replace("'", '"').replace("True", "true").replace("False", "false"), "supplier_settings": str(defaults["supplier_settings"]).replace("'", '"').replace("True", "true").replace("False", "false"), "pos_settings": str(defaults["pos_settings"]).replace("'", '"').replace("True", "true").replace("False", "false"), "order_settings": str(defaults["order_settings"]).replace("'", '"').replace("True", "true").replace("False", "false") } ) def downgrade(): """Drop tenant_settings table""" op.drop_index('ix_tenant_settings_tenant_id', table_name='tenant_settings') op.drop_table('tenant_settings')