Olympus/backend/hermes/app/api/dashboard.py
2026-07-05 13:41:33 +02:00

191 lines
8.6 KiB
Python

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.lexware import LexwareSyncRecord
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)),
MetricCard(label="Heute zurückgenommene KV", value=RepairEstimateRepository.count_revoked_today(db)),
])
if "lexware.read" in permissions:
repairs.extend([
MetricCard(
label="Vorbereitete Rechnungen",
value=db.scalar(
select(func.count(LexwareSyncRecord.id))
.where(LexwareSyncRecord.lexware_resource_type == "invoice")
.where(LexwareSyncRecord.export_status == "prepared")
) or 0,
),
MetricCard(
label="An Buchhaltung übergeben",
value=db.scalar(
select(func.count(LexwareSyncRecord.id))
.where(LexwareSyncRecord.lexware_resource_type == "invoice")
.where(LexwareSyncRecord.export_status == "transferred")
) or 0,
),
MetricCard(
label="Noch nicht übertragen",
value=db.scalar(
select(func.count(LexwareSyncRecord.id))
.where(LexwareSyncRecord.lexware_resource_type == "invoice")
.where(LexwareSyncRecord.export_status == "prepared")
) or 0,
),
])
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.",
),
)