#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Seed script to create the PILOT2025 coupon for the pilot customer program. This coupon provides 3 months (90 days) free trial extension for the first 20 customers. This script runs as a Kubernetes job inside the tenant-service container. Usage: python /app/services/tenant/scripts/seed_pilot_coupon.py Environment Variables Required: TENANT_DATABASE_URL - PostgreSQL connection string for tenant database LOG_LEVEL - Logging level (default: INFO) """ import asyncio import sys import os from datetime import datetime, timedelta, timezone from pathlib import Path import uuid # Add app to path sys.path.insert(0, str(Path(__file__).parent.parent)) from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker from sqlalchemy import select import structlog from app.models.coupon import CouponModel # Configure logging structlog.configure( processors=[ structlog.stdlib.add_log_level, structlog.processors.TimeStamper(fmt="iso"), structlog.dev.ConsoleRenderer() ] ) logger = structlog.get_logger() async def seed_pilot_coupon(db: AsyncSession): """Create or update the PILOT2025 coupon""" coupon_code = "PILOT2025" logger.info("=" * 80) logger.info("đŸŽ« Seeding PILOT2025 Coupon") logger.info("=" * 80) # Check if coupon already exists result = await db.execute( select(CouponModel).where(CouponModel.code == coupon_code) ) existing_coupon = result.scalars().first() if existing_coupon: logger.info( "Coupon already exists", code=coupon_code, current_redemptions=existing_coupon.current_redemptions, max_redemptions=existing_coupon.max_redemptions, active=existing_coupon.active, valid_from=existing_coupon.valid_from, valid_until=existing_coupon.valid_until ) return existing_coupon # Create new coupon now = datetime.now(timezone.utc) valid_until = now + timedelta(days=180) # Valid for 6 months coupon = CouponModel( id=uuid.uuid4(), code=coupon_code, discount_type="trial_extension", discount_value=90, # 90 days = 3 months max_redemptions=20, # First 20 pilot customers current_redemptions=0, valid_from=now, valid_until=valid_until, active=True, created_at=now, extra_data={ "program": "pilot_launch_2025", "description": "Programa piloto - 3 meses gratis para los primeros 20 clientes", "terms": "VĂĄlido para nuevos registros Ășnicamente. Un cupĂłn por cliente." } ) db.add(coupon) await db.commit() await db.refresh(coupon) logger.info("=" * 80) logger.info( "✅ Successfully created coupon", code=coupon_code, type="Trial Extension", value="90 days (3 months)", max_redemptions=20, valid_from=coupon.valid_from, valid_until=coupon.valid_until, id=str(coupon.id) ) logger.info("=" * 80) return coupon async def main(): """Main execution function""" logger.info("Pilot Coupon Seeding Script Starting") logger.info("Log Level: %s", os.getenv("LOG_LEVEL", "INFO")) # Get database URL from environment database_url = os.getenv("TENANT_DATABASE_URL") or os.getenv("DATABASE_URL") if not database_url: logger.error("❌ TENANT_DATABASE_URL or DATABASE_URL environment variable must be set") return 1 # Convert to async URL if needed if database_url.startswith("postgresql://"): database_url = database_url.replace("postgresql://", "postgresql+asyncpg://", 1) logger.info("Connecting to tenant database") # Create engine and session engine = create_async_engine( database_url, echo=False, pool_pre_ping=True, pool_size=5, max_overflow=10 ) async_session = sessionmaker( engine, class_=AsyncSession, expire_on_commit=False ) try: async with async_session() as session: await seed_pilot_coupon(session) logger.info("") logger.info("🎉 Success! PILOT2025 coupon is ready.") logger.info("") logger.info("Coupon Details:") logger.info(" Code: PILOT2025") logger.info(" Type: Trial Extension") logger.info(" Value: 90 days (3 months)") logger.info(" Max Redemptions: 20") logger.info("") return 0 except Exception as e: logger.error("=" * 80) logger.error("❌ Pilot Coupon Seeding Failed") logger.error("=" * 80) logger.error("Error: %s", str(e)) logger.error("", exc_info=True) return 1 finally: await engine.dispose() if __name__ == "__main__": exit_code = asyncio.run(main()) sys.exit(exit_code)