#!/usr/bin/env python3 """ 🌍 Seed script for Global Region Registry (system.region_config). Populates the system.region_config table with ALL 27 EU member states, plus United Kingdom (GB), Switzerland (CH), and a DEFAULT/Eurozone fallback. Total: 30 regions with accurate VAT rates, currencies, locales, and timezones. Usage: docker compose exec sf_api python3 /app/scripts/seed_regions.py """ import asyncio import logging import sys import os # Ensure we can import from the project sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from app.core.config import settings from app.models.core_logic import RegionConfig logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) # ────────────────────────────────────────────────────────────────────────────── # 🌍 COMPREHENSIVE EU + EEA REGION REGISTRY # ────────────────────────────────────────────────────────────────────────────── # All 27 EU member states + GB + CH + DEFAULT fallback = 30 entries # VAT rates verified against official EU VAT rates (2025-2026) # ────────────────────────────────────────────────────────────────────────────── DEFAULT_REGIONS = [ # ── EU MEMBER STATES (27) ────────────────────────────────────────────── { "country_code": "AT", "name": "Austria", "currency": "EUR", "default_vat_rate": 20.0, "locale_code": "de-AT", "timezone": "Europe/Vienna", "is_active": True, }, { "country_code": "BE", "name": "Belgium", "currency": "EUR", "default_vat_rate": 21.0, "locale_code": "nl-BE", "timezone": "Europe/Brussels", "is_active": True, }, { "country_code": "BG", "name": "Bulgaria", "currency": "BGN", "default_vat_rate": 20.0, "locale_code": "bg-BG", "timezone": "Europe/Sofia", "is_active": True, }, { "country_code": "HR", "name": "Croatia", "currency": "EUR", "default_vat_rate": 25.0, "locale_code": "hr-HR", "timezone": "Europe/Zagreb", "is_active": True, }, { "country_code": "CY", "name": "Cyprus", "currency": "EUR", "default_vat_rate": 19.0, "locale_code": "el-CY", "timezone": "Asia/Nicosia", "is_active": True, }, { "country_code": "CZ", "name": "Czech Republic", "currency": "CZK", "default_vat_rate": 21.0, "locale_code": "cs-CZ", "timezone": "Europe/Prague", "is_active": True, }, { "country_code": "DK", "name": "Denmark", "currency": "DKK", "default_vat_rate": 25.0, "locale_code": "da-DK", "timezone": "Europe/Copenhagen", "is_active": True, }, { "country_code": "EE", "name": "Estonia", "currency": "EUR", "default_vat_rate": 22.0, "locale_code": "et-EE", "timezone": "Europe/Tallinn", "is_active": True, }, { "country_code": "FI", "name": "Finland", "currency": "EUR", "default_vat_rate": 25.5, "locale_code": "fi-FI", "timezone": "Europe/Helsinki", "is_active": True, }, { "country_code": "FR", "name": "France", "currency": "EUR", "default_vat_rate": 20.0, "locale_code": "fr-FR", "timezone": "Europe/Paris", "is_active": True, }, { "country_code": "DE", "name": "Germany", "currency": "EUR", "default_vat_rate": 19.0, "locale_code": "de-DE", "timezone": "Europe/Berlin", "is_active": True, }, { "country_code": "GR", "name": "Greece", "currency": "EUR", "default_vat_rate": 24.0, "locale_code": "el-GR", "timezone": "Europe/Athens", "is_active": True, }, { "country_code": "HU", "name": "Hungary", "currency": "HUF", "default_vat_rate": 27.0, "locale_code": "hu-HU", "timezone": "Europe/Budapest", "is_active": True, }, { "country_code": "IE", "name": "Ireland", "currency": "EUR", "default_vat_rate": 23.0, "locale_code": "en-IE", "timezone": "Europe/Dublin", "is_active": True, }, { "country_code": "IT", "name": "Italy", "currency": "EUR", "default_vat_rate": 22.0, "locale_code": "it-IT", "timezone": "Europe/Rome", "is_active": True, }, { "country_code": "LV", "name": "Latvia", "currency": "EUR", "default_vat_rate": 21.0, "locale_code": "lv-LV", "timezone": "Europe/Riga", "is_active": True, }, { "country_code": "LT", "name": "Lithuania", "currency": "EUR", "default_vat_rate": 21.0, "locale_code": "lt-LT", "timezone": "Europe/Vilnius", "is_active": True, }, { "country_code": "LU", "name": "Luxembourg", "currency": "EUR", "default_vat_rate": 17.0, "locale_code": "lb-LU", "timezone": "Europe/Luxembourg", "is_active": True, }, { "country_code": "MT", "name": "Malta", "currency": "EUR", "default_vat_rate": 18.0, "locale_code": "mt-MT", "timezone": "Europe/Malta", "is_active": True, }, { "country_code": "NL", "name": "Netherlands", "currency": "EUR", "default_vat_rate": 21.0, "locale_code": "nl-NL", "timezone": "Europe/Amsterdam", "is_active": True, }, { "country_code": "PL", "name": "Poland", "currency": "PLN", "default_vat_rate": 23.0, "locale_code": "pl-PL", "timezone": "Europe/Warsaw", "is_active": True, }, { "country_code": "PT", "name": "Portugal", "currency": "EUR", "default_vat_rate": 23.0, "locale_code": "pt-PT", "timezone": "Europe/Lisbon", "is_active": True, }, { "country_code": "RO", "name": "Romania", "currency": "RON", "default_vat_rate": 19.0, "locale_code": "ro-RO", "timezone": "Europe/Bucharest", "is_active": True, }, { "country_code": "SK", "name": "Slovakia", "currency": "EUR", "default_vat_rate": 23.0, "locale_code": "sk-SK", "timezone": "Europe/Bratislava", "is_active": True, }, { "country_code": "SI", "name": "Slovenia", "currency": "EUR", "default_vat_rate": 22.0, "locale_code": "sl-SI", "timezone": "Europe/Ljubljana", "is_active": True, }, { "country_code": "ES", "name": "Spain", "currency": "EUR", "default_vat_rate": 21.0, "locale_code": "es-ES", "timezone": "Europe/Madrid", "is_active": True, }, { "country_code": "SE", "name": "Sweden", "currency": "SEK", "default_vat_rate": 25.0, "locale_code": "sv-SE", "timezone": "Europe/Stockholm", "is_active": True, }, # ── NON-EU EEA / ASSOCIATED ──────────────────────────────────────────── { "country_code": "GB", "name": "United Kingdom", "currency": "GBP", "default_vat_rate": 20.0, "locale_code": "en-GB", "timezone": "Europe/London", "is_active": True, }, { "country_code": "CH", "name": "Switzerland", "currency": "CHF", "default_vat_rate": 8.1, "locale_code": "de-CH", "timezone": "Europe/Zurich", "is_active": True, }, # ── FALLBACK / DEFAULT ───────────────────────────────────────────────── { "country_code": "DEFAULT", "name": "Eurozone (Default)", "currency": "EUR", "default_vat_rate": 0.0, "locale_code": "en-EU", "timezone": "Europe/Brussels", "is_active": True, }, ] async def seed_regions(): """ Seed the system.region_config table with all EU regions + GB + CH + DEFAULT. Uses UPSERT logic: if a region with the same country_code already exists, its fields are UPDATED (name, currency, VAT rate, locale, timezone, is_active) rather than skipped. This ensures the script is idempotent and can be re-run to update VAT rates or other configuration changes. """ engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI), echo=False) async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as session: async with session.begin(): for region_data in DEFAULT_REGIONS: country_code = region_data["country_code"] # Check if region already exists stmt = select(RegionConfig).where( RegionConfig.country_code == country_code ) result = await session.execute(stmt) existing = result.scalar_one_or_none() if existing: # UPSERT: update existing record with new values existing.name = region_data["name"] existing.currency = region_data["currency"] existing.default_vat_rate = region_data["default_vat_rate"] existing.locale_code = region_data["locale_code"] existing.timezone = region_data["timezone"] existing.is_active = region_data["is_active"] logger.info( f"🔄 Updated region: {country_code} - {region_data['name']} " f"(VAT: {region_data['default_vat_rate']}%, " f"Currency: {region_data['currency']})" ) else: # INSERT: create new region region = RegionConfig(**region_data) session.add(region) logger.info( f"✅ Created region: {country_code} - {region_data['name']} " f"(VAT: {region_data['default_vat_rate']}%, " f"Currency: {region_data['currency']})" ) await session.commit() # Count total regions count_stmt = select(RegionConfig) count_result = await session.execute(count_stmt) total = len(count_result.scalars().all()) logger.info(f"🎉 Region seeding completed! Total regions in database: {total}") await engine.dispose() if __name__ == "__main__": asyncio.run(seed_regions())