"""Idempotenter Seed der Stammdaten (Kategoriebaum, Benachrichtigungsregeln).""" import logging from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.db.seed_data import SEED_TREE, SeedCategory from app.models import Category, NotificationRule from app.models.enums import EntryKind, NotificationChannel, NotificationType logger = logging.getLogger(__name__) # Standardregeln: Vorlaufzeiten gemäß Fachspezifikation, zunächst inaktiv per SMTP. DEFAULT_RULES: tuple[tuple[NotificationType, int], ...] = ( (NotificationType.DUE_SOON, 3), (NotificationType.NOTICE_DEADLINE, 30), (NotificationType.BUDGET_EXCEEDED, 0), (NotificationType.CONTRACT_RENEWAL, 30), ) async def seed_categories(session: AsyncSession) -> int: """Legt den Standardbaum an. Bereits vorhandene Kategorien bleiben unverändert.""" created = 0 for kind, nodes in SEED_TREE: for order, node in enumerate(nodes): parent, was_created = await _upsert(session, node, kind, None, order) created += int(was_created) for child_order, child in enumerate(node.children): _, child_created = await _upsert(session, child, kind, parent.id, child_order) created += int(child_created) return created async def _upsert( session: AsyncSession, node: SeedCategory, kind: EntryKind, parent_id: int | None, sort_order: int, ) -> tuple[Category, bool]: """Sucht die Kategorie über (parent_id, name) und legt sie bei Bedarf an.""" stmt = select(Category).where(Category.name == node.name) stmt = stmt.where( Category.parent_id.is_(None) if parent_id is None else Category.parent_id == parent_id ) existing = (await session.execute(stmt)).scalar_one_or_none() if existing is not None: return existing, False category = Category( parent_id=parent_id, name=node.name, kind=kind, color=node.color, icon=node.icon, is_fixed_cost=node.is_fixed_cost, sort_order=sort_order, ) session.add(category) await session.flush() return category, True async def seed_notification_rules(session: AsyncSession) -> int: """Legt je Ereignistyp eine inaktive SMTP-Standardregel an.""" created = 0 for rule_type, lead_days in DEFAULT_RULES: stmt = select(NotificationRule).where( NotificationRule.type == rule_type, NotificationRule.channel == NotificationChannel.SMTP, ) if (await session.execute(stmt)).scalar_one_or_none() is not None: continue session.add( NotificationRule( type=rule_type, lead_days=lead_days, channel=NotificationChannel.SMTP, is_active=False, ) ) created += 1 await session.flush() return created async def seed_all(session: AsyncSession) -> dict[str, int]: """Führt alle Seeds aus und liefert die Anzahl neu angelegter Datensätze.""" result = { "categories": await seed_categories(session), "notification_rules": await seed_notification_rules(session), } await session.commit() logger.info("Seed abgeschlossen: %s", result) return result