Initial commit – TimeMaster Zeiterfassung & HR-Tool
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>
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
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"})
|
||||
Reference in New Issue
Block a user