1fedd683e0
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer), Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst. Migrations 0001–0023 deployed auf 192.168.1.137 + .164. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
211 lines
9.1 KiB
Python
211 lines
9.1 KiB
Python
from datetime import date, timedelta
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from fastapi.responses import Response
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import CurrentUser, require_role
|
|
from app.models.user import User, UserRole
|
|
from app.schemas.report import (
|
|
AbsenceReport,
|
|
CompanyDashboard,
|
|
EmployeeDashboard,
|
|
OvertimeReport,
|
|
OvertimeReportDetailed,
|
|
TeamDashboard,
|
|
TimeReport,
|
|
)
|
|
from app.services.report_service import report_service
|
|
|
|
router = APIRouter(tags=["Dashboard & Reports"])
|
|
|
|
_manager_roles = (UserRole.MANAGER, UserRole.HR, UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
|
|
_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
|
|
|
|
|
|
# ── Dashboard ──────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/dashboard/me", response_model=EmployeeDashboard)
|
|
async def my_dashboard(
|
|
current_user: CurrentUser,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Mitarbeiter-Dashboard: eigene Stunden, Urlaub, Status heute."""
|
|
return await report_service.employee_dashboard(current_user, db)
|
|
|
|
|
|
@router.get("/dashboard/team", response_model=TeamDashboard)
|
|
async def team_dashboard(
|
|
current_user: User = require_role(*_manager_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Team-Dashboard: Anwesenheit, ausstehende Genehmigungen."""
|
|
return await report_service.team_dashboard(current_user, db)
|
|
|
|
|
|
@router.get("/dashboard/company", response_model=CompanyDashboard)
|
|
async def company_dashboard(
|
|
current_user: User = require_role(*_admin_roles),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Unternehmens-Dashboard: Gesamtübersicht, Überstunden, kommende Abwesenheiten."""
|
|
return await report_service.company_dashboard(current_user, db)
|
|
|
|
|
|
# ── Reports ────────────────────────────────────────────────────────────────────
|
|
|
|
def _default_date_from() -> date:
|
|
today = date.today()
|
|
return today.replace(day=1)
|
|
|
|
|
|
def _default_date_to() -> date:
|
|
return date.today()
|
|
|
|
|
|
@router.get("/reports/time", response_model=TimeReport)
|
|
async def time_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Zeiterfassungsbericht (JSON). EMPLOYEE sieht nur eigene Einträge."""
|
|
return await report_service.time_report(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
|
|
|
|
@router.get("/reports/absences", response_model=AbsenceReport)
|
|
async def absence_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Abwesenheitsbericht (JSON). EMPLOYEE sieht nur eigene Abwesenheiten."""
|
|
return await report_service.absence_report(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
|
|
|
|
@router.get("/reports/overtime", response_model=OvertimeReport)
|
|
async def overtime_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Überstundenbericht (JSON). EMPLOYEE sieht nur eigene Daten."""
|
|
return await report_service.overtime_report(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
|
|
|
|
@router.get("/reports/overtime/detail", response_model=OvertimeReportDetailed)
|
|
async def overtime_report_detail(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Erweiterter Überstundenbericht mit Wochen- und Tagesaufschlüsselung."""
|
|
return await report_service.overtime_report_detail(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
|
|
|
|
# ── Export ─────────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/reports/time/export")
|
|
async def export_time_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
format: str = Query("csv", pattern="^(csv|xlsx|pdf)$"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Zeiterfassungsbericht als CSV, XLSX oder PDF herunterladen."""
|
|
report = await report_service.time_report(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
filename = f"zeiterfassung_{date_from}_{date_to}"
|
|
|
|
if format == "pdf":
|
|
content = report_service.time_report_to_pdf(report)
|
|
return Response(content=content, media_type="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.pdf"})
|
|
if format == "xlsx":
|
|
content = report_service.to_xlsx(report_service._time_rows_to_dicts(report.rows), sheet_name="Zeiterfassung")
|
|
return Response(content=content,
|
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.xlsx"})
|
|
content = report_service.to_csv(report_service._time_rows_to_dicts(report.rows))
|
|
return Response(content=content, media_type="text/csv; charset=utf-8",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.csv"})
|
|
|
|
|
|
@router.get("/reports/absences/export")
|
|
async def export_absence_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
format: str = Query("csv", pattern="^(csv|xlsx|pdf)$"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Abwesenheitsbericht als CSV, XLSX oder PDF herunterladen."""
|
|
report = await report_service.absence_report(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
filename = f"abwesenheiten_{date_from}_{date_to}"
|
|
|
|
if format == "pdf":
|
|
content = report_service.absence_report_to_pdf(report)
|
|
return Response(content=content, media_type="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.pdf"})
|
|
if format == "xlsx":
|
|
content = report_service.to_xlsx(report_service._absence_rows_to_dicts(report.rows), sheet_name="Abwesenheiten")
|
|
return Response(content=content,
|
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.xlsx"})
|
|
content = report_service.to_csv(report_service._absence_rows_to_dicts(report.rows))
|
|
return Response(content=content, media_type="text/csv; charset=utf-8",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.csv"})
|
|
|
|
|
|
@router.get("/reports/overtime/export")
|
|
async def export_overtime_report(
|
|
current_user: CurrentUser,
|
|
date_from: date = Query(default_factory=_default_date_from),
|
|
date_to: date = Query(default_factory=_default_date_to),
|
|
user_id: UUID | None = Query(None),
|
|
format: str = Query("csv", pattern="^(csv|xlsx|pdf)$"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Überstundenbericht als CSV, XLSX oder PDF herunterladen (Detailansicht)."""
|
|
detail = await report_service.overtime_report_detail(
|
|
current_user.company_id, current_user, db, date_from, date_to, user_id
|
|
)
|
|
filename = f"ueberstunden_{date_from}_{date_to}"
|
|
|
|
if format == "pdf":
|
|
content = report_service.overtime_detail_to_pdf(detail)
|
|
return Response(content=content, media_type="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.pdf"})
|
|
if format == "xlsx":
|
|
content = report_service.to_xlsx(report_service._overtime_detail_to_dicts(detail), sheet_name="Überstunden")
|
|
return Response(content=content,
|
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.xlsx"})
|
|
content = report_service.to_csv(report_service._overtime_detail_to_dicts(detail))
|
|
return Response(content=content, media_type="text/csv; charset=utf-8",
|
|
headers={"Content-Disposition": f"attachment; filename={filename}.csv"})
|