#!/usr/bin/env python3 """ Seed script: fleet_finance dictionary tables (CostCategory & InsuranceProvider). Idempotent seeder that populates: 1. fleet_finance.cost_categories - Hierarchical cost categories (5 parents + children) 2. fleet_finance.insurance_providers - Base insurance providers (HU/EU) Usage: docker compose exec sf_api python -m backend.scripts.seed_finance_dictionaries """ import asyncio import logging from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import select, text from app.core.config import settings from app.models.fleet_finance.models import CostCategory, InsuranceProvider logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # ============================================================================= # Cost Categories - Hierarchical structure # ============================================================================= # Format: (code, name, description, children) # Children format: (code, name, description) COST_CATEGORY_TREE = [ { "code": "FINANCING", "name": "Finanszírozás", "description": "Jármű finanszírozásával kapcsolatos költségek", "children": [ ("FINANCING_LEASE", "Lízingdíj", "Lízingdíj fizetés"), ("FINANCING_LOAN", "Hiteltörlesztő", "Hiteltörlesztő részlet"), ], }, { "code": "INSURANCE", "name": "Biztosítás", "description": "Biztosítási díjak és kötvények", "children": [ ("INSURANCE_KGFB", "KGFB", "Kötelező gépjármű-felelősségbiztosítás"), ("INSURANCE_CASCO", "Casco", "Casco biztosítás"), ("INSURANCE_ASSISTANCE", "Assistance", "Assistance szolgáltatás"), ], }, { "code": "ADMIN_FEES", "name": "Hatósági díjak", "description": "Hatósági és adminisztratív költségek", "children": [ ("ADMIN_FEES_VEHICLE_TAX", "Gépjárműadó", "Gépjárműadó fizetés"), ("ADMIN_FEES_MOT", "Műszaki vizsga", "Műszaki vizsgadíj"), ("ADMIN_FEES_DOCS", "Okmányok", "Okmányokkal kapcsolatos díjak"), ], }, { "code": "OPERATION", "name": "Üzemeltetés", "description": "Jármű üzemeltetésével kapcsolatos költségek", "children": [ ("OPERATION_FUEL", "Üzemanyag", "Üzemanyag költség"), ("OPERATION_ELECTRIC", "Elektromos töltés", "Elektromos töltési költség"), ("OPERATION_TOLL", "Autópálya", "Autópálya használati díj"), ("OPERATION_PARKING", "Parkolás", "Parkolási díj"), ("OPERATION_WASH", "Mosás", "Járműmosás és ápolás"), ], }, { "code": "SERVICE", "name": "Szerviz", "description": "Szerviz és karbantartási költségek", "children": [ ("SERVICE_SCHEDULED", "Kötelező szerviz", "Kötelező időszakos szerviz"), ("SERVICE_REPAIR", "Eseti javítás", "Eseti javítási költség"), ("SERVICE_TIRES", "Gumiabroncs", "Gumiabroncs csere és tárolás"), ], }, { "code": "TIRE", "name": "Gumiabroncsok", "description": "Gumiabroncsokkal kapcsolatos költségek", "children": [ ("TIRE_SUMMER", "Nyári gumi", "Nyári gumiabroncs vásárlás"), ("TIRE_WINTER", "Téli gumi", "Téli gumiabroncs vásárlás"), ("TIRE_STORAGE", "Gumi tárolás", "Gumiabroncs tárolási díj"), ], }, { "code": "CLEANING", "name": "Ápolás & Kozmetika", "description": "Jármű ápolásával és kozmetikájával kapcsolatos költségek", "children": [ ("CLEANING_WASH", "Mosás", "Járműmosás"), ("CLEANING_DETAILING", "Részletes tisztítás", "Részletes autókozmetika"), ("CLEANING_INTERIOR", "Belső tisztítás", "Belső tér tisztítása"), ], }, { "code": "OTHER", "name": "Egyéb", "description": "Egyéb, máshova nem sorolható költségek", "children": [ ("OTHER_FINE", "Bírság", "Közlekedési bírság"), ("OTHER_ACCESSORY", "Tartozék", "Jármű tartozékok"), ("OTHER_MISC", "Egyéb", "Egyéb költségek"), ], }, ] # ============================================================================= # Insurance Providers # ============================================================================= INSURANCE_PROVIDERS = [ {"name": "Allianz Hungária Zrt.", "claim_phone": "+36-1-301-7100", "claim_url": "https://www.allianz.hu/karterites"}, {"name": "Generali Biztosító Zrt.", "claim_phone": "+36-1-452-3200", "claim_url": "https://www.generali.hu/karterites"}, {"name": "K&H Biztosító Zrt.", "claim_phone": "+36-1-335-3300", "claim_url": "https://www.kh.hu/biztositas/karterites"}, {"name": "Groupama Biztosító Zrt.", "claim_phone": "+36-1-477-4800", "claim_url": "https://www.groupama.hu/karterites"}, {"name": "Uniqa Biztosító Zrt.", "claim_phone": "+36-1-301-7300", "claim_url": "https://www.uniqa.hu/karterites"}, {"name": "Aegon Magyarország / Alfa Vienna Insurance Group", "claim_phone": "+36-1-477-4700", "claim_url": "https://www.alfabiztosito.hu/karterites"}, {"name": "Signal Biztosító Zrt.", "claim_phone": "+36-1-457-4400", "claim_url": "https://www.signal.hu/karterites"}, {"name": "Wáberer Hungária Biztosító Zrt.", "claim_phone": "+36-1-237-7000", "claim_url": "https://www.whb.hu/karterites"}, {"name": "Posta Biztosító Zrt.", "claim_phone": "+36-1-477-4900", "claim_url": "https://www.postabiztosito.hu/karterites"}, {"name": "Magyar Posta Életbiztosító Zrt.", "claim_phone": "+36-1-477-5000", "claim_url": "https://www.mpelb.hu/karterites"}, ] async def seed_cost_categories(db: AsyncSession) -> int: """Seed cost_categories table with hierarchical data. Returns count of inserted parents.""" inserted_count = 0 for parent_def in COST_CATEGORY_TREE: # Check if parent already exists by code existing_parent = await db.execute( select(CostCategory).where(CostCategory.code == parent_def["code"]) ) parent = existing_parent.scalar_one_or_none() if not parent: parent = CostCategory( code=parent_def["code"], name=parent_def["name"], description=parent_def["description"], is_system=True, visibility="both", min_tier="free", parent_id=None, ) db.add(parent) await db.flush() # Get the parent ID inserted_count += 1 logger.info(f" ✅ PARENT [{parent.code:20s}] {parent.name}") # Process children for child_code, child_name, child_desc in parent_def["children"]: existing_child = await db.execute( select(CostCategory).where(CostCategory.code == child_code) ) child = existing_child.scalar_one_or_none() if not child: child = CostCategory( code=child_code, name=child_name, description=child_desc, is_system=True, visibility="both", min_tier="free", parent_id=parent.id, ) db.add(child) await db.flush() logger.info(f" └─ CHILD [{child.code:20s}] {child.name}") else: logger.info(f" └─ EXISTS [{child.code:20s}] {child.name}") return inserted_count async def seed_insurance_providers(db: AsyncSession) -> int: """Seed insurance_providers table. Returns count of inserted providers.""" inserted_count = 0 for prov_def in INSURANCE_PROVIDERS: existing = await db.execute( select(InsuranceProvider).where(InsuranceProvider.name == prov_def["name"]) ) provider = existing.scalar_one_or_none() if not provider: provider = InsuranceProvider( name=prov_def["name"], country_code="HU", claim_phone=prov_def["claim_phone"], claim_url=prov_def["claim_url"], services_offered={"kgfb": True, "casco": True}, is_active=True, ) db.add(provider) inserted_count += 1 logger.info(f" ✅ {provider.name} (HU)") else: # Update existing provider's country_code if not set if not provider.country_code: provider.country_code = "HU" logger.info(f" UPDATED {provider.name} → country_code='HU'") else: logger.info(f" EXISTS {provider.name} (country_code='{provider.country_code}')") return inserted_count async def main(): """Main seeder entry point.""" engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI)) async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as db: try: logger.info("=" * 60) logger.info("📦 fleet_finance dictionary seeder") logger.info("=" * 60) # --- Cost Categories --- logger.info("\n📁 Cost Categories (hierarchical):") cat_count = await seed_cost_categories(db) await db.commit() logger.info(f" → {cat_count} new parent categories inserted") # --- Insurance Providers --- logger.info("\n🏢 Insurance Providers:") prov_count = await seed_insurance_providers(db) await db.commit() logger.info(f" → {prov_count} new providers inserted") # --- Summary --- total_cats = await db.execute( select(CostCategory).where(CostCategory.parent_id.is_(None)) ) parents = total_cats.scalars().all() logger.info("\n" + "=" * 60) logger.info("📊 SEED SUMMARY") logger.info("=" * 60) logger.info(f" CostCategory parents: {len(parents)}") for p in parents: child_count = await db.execute( select(CostCategory).where(CostCategory.parent_id == p.id) ) children = child_count.scalars().all() logger.info(f" ├─ {p.code:20s} ({p.name}) → {len(children)} children") total_providers = await db.execute( select(InsuranceProvider).where(InsuranceProvider.is_active.is_(True)) ) providers = total_providers.scalars().all() logger.info(f" InsuranceProviders (active): {len(providers)}") for p in providers: logger.info(f" ├─ {p.name}") logger.info("\n✅ Seeding completed successfully!") except Exception as e: await db.rollback() logger.error(f"❌ Seeding failed: {e}") raise finally: await engine.dispose() if __name__ == "__main__": asyncio.run(main())