import logging from fastapi import APIRouter, Depends from sqlalchemy import func, select from sqlalchemy.orm import Session from app.core.rbac import get_user_permission_names, require_permission from app.db.database import get_db from app.models.rbac import Role from app.models.audit import AuditLog from app.models.user import User from app.repositories.customer_repository import CustomerRepository from app.repositories.inventory_repository import InventoryRepository from app.repositories.repair_repository import RepairRepository from app.repositories.repair_estimate_repository import RepairEstimateRepository from app.repositories.user_repository import UserRepository from app.schemas.dashboard import DashboardSummary, EmptyWidget, MetricCard, SystemStatusItem from app.services.system_settings_service import SystemSettingsService logger = logging.getLogger(__name__) router = APIRouter( prefix="/dashboard", tags=["Dashboard"], ) @router.get("/summary", response_model=DashboardSummary) def get_dashboard_summary( db: Session = Depends(get_db), current_user: User = Depends(require_permission("dashboard.read")), ): permissions = get_user_permission_names(current_user) customers: list[MetricCard] = [] latest_customers = [] users: list[MetricCard] = [] roles: list[MetricCard] = [] repairs: list[MetricCard] = [] inventory: list[MetricCard] = [] system_status: list[SystemStatusItem] = [] if "customers.read" in permissions: customers = [ MetricCard(label="Kunden", value=CustomerRepository.count_all(db)), MetricCard(label="Aktive Kunden", value=CustomerRepository.count_active(db)), MetricCard(label="Neue Kunden", value=CustomerRepository.count_recent(db)), ] latest_customers = CustomerRepository.get_latest(db) if "users.read" in permissions: all_users = UserRepository.get_all(db) users = [ MetricCard(label="Benutzer", value=len(all_users)), MetricCard(label="Aktive Benutzer", value=len([user for user in all_users if user.is_active])), ] if "roles.read" in permissions: roles = [ MetricCard( label="Rollen", value=db.scalar(select(func.count(Role.id))) or 0, ) ] if "repairs.read" in permissions: repairs = [ MetricCard(label="Neue Reparaturen", value=RepairRepository.count_by_status(db, "new")), MetricCard(label="In Diagnose", value=RepairRepository.count_by_status(db, "diagnosis")), MetricCard(label="Wartet auf Kunde", value=RepairRepository.count_by_status(db, "waiting_for_customer")), MetricCard(label="In Reparatur", value=RepairRepository.count_by_status(db, "repair")), MetricCard(label="Endprüfung", value=RepairRepository.count_by_status(db, "final_test")), MetricCard(label="Abgeschlossen", value=RepairRepository.count_by_status(db, "completed")), MetricCard(label="Statusmails heute", value=RepairRepository.count_status_mails_sent_today(db)), MetricCard(label="Fehlgeschlagene Mails", value=RepairRepository.count_failed_status_mails(db)), MetricCard(label="Offen ohne Kundenmail", value=RepairRepository.count_open_repairs_without_customer_email(db)), MetricCard(label="Reparaturdokumente", value=RepairRepository.count_documents(db)), ] if "repair_estimates.read" in permissions: repairs.extend([ MetricCard(label="Offene KVs", value=RepairEstimateRepository.count_open(db)), MetricCard(label="Warten auf Freigabe", value=RepairEstimateRepository.count_waiting(db)), MetricCard(label="KVs freigegeben heute", value=RepairEstimateRepository.count_approved_today(db)), MetricCard(label="KVs abgelehnt", value=RepairEstimateRepository.count_declined(db)), ]) if "inventory.read" in permissions: inventory = [ MetricCard(label="Aktive Ersatzteile", value=InventoryRepository.count_active_items(db)), MetricCard(label="Niedriger Bestand", value=InventoryRepository.count_low_stock_items(db)), MetricCard(label="Lagerwert Einkauf", value=InventoryRepository.total_stock_value_cents(db)), MetricCard(label="Reservierte Artikel", value=InventoryRepository.count_reserved_items(db)), MetricCard(label="Reservierter Warenwert", value=InventoryRepository.reserved_stock_value_cents(db)), MetricCard(label="Artikel unter Mindestbestand", value=InventoryRepository.count_low_stock_items(db)), MetricCard(label="Letzte Bewegungen", value=len(InventoryRepository.latest_movements(db, limit=5))), ] if "system_settings.manage" in permissions: smtp_config = SystemSettingsService.get_smtp_runtime_config(db) public_links = SystemSettingsService.get_public_links_settings(db) latest_testmail = db.scalar( select(AuditLog) .where(AuditLog.action.in_(["system_settings.smtp.test_sent", "system_settings.smtp.test_failed"])) .order_by(AuditLog.created_at.desc(), AuditLog.id.desc()) .limit(1) ) latest_failed_status_mail = RepairRepository.latest_failed_status_mail(db) system_status = [ SystemStatusItem( label="SMTP-Konfiguration", value={ "database": "Admin-Konfiguration", "environment": "Umgebung", "missing": "Nicht konfiguriert", }[smtp_config.source], status="ok" if smtp_config.is_configured else "warning", ), SystemStatusItem( label="Public Status Base URL", value=public_links.repair_status_base_url or "Nicht konfiguriert", status="ok" if public_links.repair_status_base_url else "warning", ), SystemStatusItem( label="Letzte SMTP-Testmail", value=latest_testmail.created_at.isoformat() if latest_testmail else "Noch keine Testmail", status="info" if latest_testmail else "warning", ), SystemStatusItem( label="Letzte fehlgeschlagene Statusmail", value=latest_failed_status_mail.created_at.isoformat() if latest_failed_status_mail else "Keine Fehler", status="warning" if latest_failed_status_mail else "ok", ), ] logger.info("dashboard.summary", extra={"actor_user_id": current_user.id}) return DashboardSummary( customers=customers, latest_customers=latest_customers, users=users, roles=roles, repairs=repairs, inventory=inventory, system_status=system_status, activities=EmptyWidget( title="Letzte Aktivitäten", message="Noch keine Aktivitäten vorhanden.", ), tasks=EmptyWidget( title="Offene Aufgaben", message="Noch keine Aufgaben vorhanden.", ), tickets=EmptyWidget( title="Offene Tickets", message="Noch keine Tickets vorhanden.", ), projects=EmptyWidget( title="Projekte", message="Noch keine Projektdaten vorhanden.", ), )