#!/usr/bin/env python3 """ Clone Demo Tenant Data - Database Level Clones all data from base template tenant to a virtual demo tenant across all databases """ import asyncio import sys import os from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy import select import uuid import structlog # Add app to path for imports sys.path.insert(0, '/app') logger = structlog.get_logger() # Base template tenant IDs DEMO_TENANT_SAN_PABLO = "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6" DEMO_TENANT_LA_ESPIGA = "b2c3d4e5-f6a7-48b9-c0d1-e2f3a4b5c6d7" async def clone_inventory_data(base_tenant_id: str, virtual_tenant_id: str): """Clone inventory database tables using ORM""" database_url = os.getenv("INVENTORY_DATABASE_URL") if not database_url: logger.warning("INVENTORY_DATABASE_URL not set, skipping inventory data") return 0 engine = create_async_engine(database_url, echo=False) session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) total_cloned = 0 try: from app.models.inventory import Ingredient async with session_factory() as session: # Clone ingredients result = await session.execute( select(Ingredient).where(Ingredient.tenant_id == uuid.UUID(base_tenant_id)) ) base_ingredients = result.scalars().all() logger.info(f"Found {len(base_ingredients)} ingredients to clone") for ing in base_ingredients: new_ing = Ingredient( id=uuid.uuid4(), tenant_id=uuid.UUID(virtual_tenant_id), name=ing.name, sku=ing.sku, barcode=ing.barcode, product_type=ing.product_type, ingredient_category=ing.ingredient_category, product_category=ing.product_category, subcategory=ing.subcategory, description=ing.description, brand=ing.brand, unit_of_measure=ing.unit_of_measure, package_size=ing.package_size, average_cost=ing.average_cost, last_purchase_price=ing.last_purchase_price, standard_cost=ing.standard_cost, low_stock_threshold=ing.low_stock_threshold, reorder_point=ing.reorder_point, reorder_quantity=ing.reorder_quantity, max_stock_level=ing.max_stock_level, shelf_life_days=ing.shelf_life_days, is_perishable=ing.is_perishable, is_active=ing.is_active, allergen_info=ing.allergen_info ) session.add(new_ing) total_cloned += 1 await session.commit() logger.info(f"Cloned {total_cloned} ingredients") except Exception as e: logger.error(f"Failed to clone inventory data: {str(e)}", exc_info=True) raise finally: await engine.dispose() return total_cloned async def clone_sales_data(base_tenant_id: str, virtual_tenant_id: str): """Clone sales database tables""" database_url = os.getenv("SALES_DATABASE_URL") if not database_url: logger.warning("SALES_DATABASE_URL not set, skipping sales data") return 0 # Sales cloning not implemented yet logger.info("Sales data cloning not yet implemented") return 0 async def clone_orders_data(base_tenant_id: str, virtual_tenant_id: str): """Clone orders database tables""" database_url = os.getenv("ORDERS_DATABASE_URL") if not database_url: logger.warning("ORDERS_DATABASE_URL not set, skipping orders data") return 0 # Orders cloning not implemented yet logger.info("Orders data cloning not yet implemented") return 0 async def create_virtual_tenant(virtual_tenant_id: str, demo_account_type: str): """Create the virtual tenant record in tenant database""" database_url = os.getenv("TENANT_DATABASE_URL") if not database_url: logger.warning("TENANT_DATABASE_URL not set, skipping tenant creation") return engine = create_async_engine(database_url, echo=False) session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) try: # Import after adding to path from services.tenant.app.models.tenants import Tenant async with session_factory() as session: # Check if tenant already exists result = await session.execute( select(Tenant).where(Tenant.id == uuid.UUID(virtual_tenant_id)) ) existing = result.scalars().first() if existing: logger.info(f"Virtual tenant {virtual_tenant_id} already exists") return # Create virtual tenant tenant = Tenant( id=uuid.UUID(virtual_tenant_id), name=f"Demo Session Tenant", is_demo=True, is_demo_template=False, business_model=demo_account_type ) session.add(tenant) await session.commit() logger.info(f"Created virtual tenant {virtual_tenant_id}") except ImportError: # Tenant model not available, skip logger.warning("Could not import Tenant model, skipping virtual tenant creation") except Exception as e: logger.error(f"Failed to create virtual tenant: {str(e)}", exc_info=True) finally: await engine.dispose() async def clone_demo_tenant(virtual_tenant_id: str, demo_account_type: str = "individual_bakery"): """ Main function to clone all demo data for a virtual tenant Args: virtual_tenant_id: The UUID of the virtual tenant to create demo_account_type: Type of demo account (individual_bakery or central_baker) """ base_tenant_id = DEMO_TENANT_SAN_PABLO if demo_account_type == "individual_bakery" else DEMO_TENANT_LA_ESPIGA logger.info( "Starting demo tenant cloning", virtual_tenant=virtual_tenant_id, base_tenant=base_tenant_id, demo_type=demo_account_type ) try: # Create virtual tenant record await create_virtual_tenant(virtual_tenant_id, demo_account_type) # Clone data from each database stats = { "inventory": await clone_inventory_data(base_tenant_id, virtual_tenant_id), "sales": await clone_sales_data(base_tenant_id, virtual_tenant_id), "orders": await clone_orders_data(base_tenant_id, virtual_tenant_id), } total_records = sum(stats.values()) logger.info( "Demo tenant cloning completed successfully", virtual_tenant=virtual_tenant_id, total_records=total_records, stats=stats ) # Print summary for job logs print(f"✅ Cloning completed: {total_records} total records") print(f" - Inventory: {stats['inventory']} records") print(f" - Sales: {stats['sales']} records") print(f" - Orders: {stats['orders']} records") return True except Exception as e: logger.error( "Demo tenant cloning failed", virtual_tenant=virtual_tenant_id, error=str(e), exc_info=True ) print(f"❌ Cloning failed: {str(e)}") return False if __name__ == "__main__": # Get virtual tenant ID from environment or CLI argument virtual_tenant_id = os.getenv("VIRTUAL_TENANT_ID") or (sys.argv[1] if len(sys.argv) > 1 else None) demo_type = os.getenv("DEMO_ACCOUNT_TYPE", "individual_bakery") if not virtual_tenant_id: print("Usage: python clone_demo_tenant.py ") print(" or: VIRTUAL_TENANT_ID= python clone_demo_tenant.py") sys.exit(1) # Validate UUID try: uuid.UUID(virtual_tenant_id) except ValueError: print(f"Error: Invalid UUID format: {virtual_tenant_id}") sys.exit(1) result = asyncio.run(clone_demo_tenant(virtual_tenant_id, demo_type)) sys.exit(0 if result else 1)