128 lines
3.8 KiB
Python
128 lines
3.8 KiB
Python
"""
|
|
Startup seeder for tenant service.
|
|
Seeds initial data (like pilot coupons) on service startup.
|
|
All operations are idempotent - safe to run multiple times.
|
|
"""
|
|
|
|
import os
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Optional
|
|
|
|
import structlog
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.coupon import CouponModel
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
async def ensure_pilot_coupon(session: AsyncSession) -> Optional[CouponModel]:
|
|
"""
|
|
Ensure the PILOT2025 coupon exists in the database.
|
|
|
|
This coupon provides 3 months (90 days) free trial extension
|
|
for the first 20 pilot customers.
|
|
|
|
This function is idempotent - it will not create duplicates.
|
|
|
|
Args:
|
|
session: Database session
|
|
|
|
Returns:
|
|
The coupon model (existing or newly created), or None if disabled
|
|
"""
|
|
# Check if pilot mode is enabled via environment variable
|
|
pilot_mode_enabled = os.getenv("VITE_PILOT_MODE_ENABLED", "true").lower() == "true"
|
|
|
|
if not pilot_mode_enabled:
|
|
logger.info("Pilot mode is disabled, skipping coupon seeding")
|
|
return None
|
|
|
|
coupon_code = os.getenv("VITE_PILOT_COUPON_CODE", "PILOT2025")
|
|
trial_months = int(os.getenv("VITE_PILOT_TRIAL_MONTHS", "3"))
|
|
max_redemptions = int(os.getenv("PILOT_MAX_REDEMPTIONS", "20"))
|
|
|
|
# Check if coupon already exists
|
|
result = await session.execute(
|
|
select(CouponModel).where(CouponModel.code == coupon_code)
|
|
)
|
|
existing_coupon = result.scalars().first()
|
|
|
|
if existing_coupon:
|
|
logger.info(
|
|
"Pilot coupon already exists",
|
|
code=coupon_code,
|
|
current_redemptions=existing_coupon.current_redemptions,
|
|
max_redemptions=existing_coupon.max_redemptions,
|
|
active=existing_coupon.active
|
|
)
|
|
return existing_coupon
|
|
|
|
# Create new coupon
|
|
now = datetime.now(timezone.utc)
|
|
valid_until = now + timedelta(days=180) # Valid for 6 months
|
|
trial_days = trial_months * 30 # Approximate days
|
|
|
|
coupon = CouponModel(
|
|
id=uuid.uuid4(),
|
|
code=coupon_code,
|
|
discount_type="trial_extension",
|
|
discount_value=trial_days,
|
|
max_redemptions=max_redemptions,
|
|
current_redemptions=0,
|
|
valid_from=now,
|
|
valid_until=valid_until,
|
|
active=True,
|
|
created_at=now,
|
|
extra_data={
|
|
"program": "pilot_launch_2025",
|
|
"description": f"Programa piloto - {trial_months} meses gratis para los primeros {max_redemptions} clientes",
|
|
"terms": "Válido para nuevos registros únicamente. Un cupón por cliente."
|
|
}
|
|
)
|
|
|
|
session.add(coupon)
|
|
await session.commit()
|
|
await session.refresh(coupon)
|
|
|
|
logger.info(
|
|
"Pilot coupon created successfully",
|
|
code=coupon_code,
|
|
type="Trial Extension",
|
|
value=f"{trial_days} days ({trial_months} months)",
|
|
max_redemptions=max_redemptions,
|
|
valid_until=valid_until.isoformat(),
|
|
id=str(coupon.id)
|
|
)
|
|
|
|
return coupon
|
|
|
|
|
|
async def run_startup_seeders(database_manager) -> None:
|
|
"""
|
|
Run all startup seeders.
|
|
|
|
This function is called during service startup to ensure
|
|
required seed data exists in the database.
|
|
|
|
Args:
|
|
database_manager: The database manager instance
|
|
"""
|
|
logger.info("Running startup seeders...")
|
|
|
|
try:
|
|
async with database_manager.get_session() as session:
|
|
# Seed pilot coupon
|
|
await ensure_pilot_coupon(session)
|
|
|
|
logger.info("Startup seeders completed successfully")
|
|
|
|
except Exception as e:
|
|
# Log but don't fail startup - seed data is not critical
|
|
logger.warning(
|
|
"Startup seeder encountered an error (non-fatal)",
|
|
error=str(e)
|
|
)
|