dak.c2s/backend/app/api/reports.py
CCS Admin d6fb04d5a7 feat: coding queue, reports API, Excel sync
- Add coding_service.py with queue retrieval, single + batch coding updates
- Add report schemas (DashboardKPIs, WeeklyDataPoint, ReportMeta)
- Add coding API router with /queue, PUT /{case_id}, POST /batch endpoints
- Add reports API router with /dashboard, /weekly, /generate, /download, /list
- Add excel_sync.py for bidirectional Abrechnung DB<->XLSX sync
- Register coding and reports routers in main.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:03:17 +00:00

184 lines
5.7 KiB
Python

"""Reports API — dashboard KPIs, weekly data, report generation, and download."""
import logging
import os
from datetime import date
from io import BytesIO
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from app.core.dependencies import get_current_user, require_admin
from app.database import get_db
from app.models.report import WeeklyReport
from app.models.user import User
from app.schemas.report import (
DashboardResponse,
ReportListResponse,
ReportMeta,
)
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/dashboard", response_model=DashboardResponse)
def dashboard(
jahr: int | None = Query(None),
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""Return dashboard KPIs and weekly time-series for the given year.
Defaults to the current ISO year if *jahr* is not provided.
Accessible to both admin and dak_mitarbeiter users.
"""
if not jahr:
from app.utils.kw_utils import date_to_jahr
jahr = date_to_jahr(date.today())
try:
from app.services.report_service import (
calculate_dashboard_kpis,
calculate_sheet1_data,
)
kpis = calculate_dashboard_kpis(db, jahr)
sheet1 = calculate_sheet1_data(db, jahr)
return DashboardResponse(kpis=kpis, weekly=sheet1.get("weekly", []))
except ImportError:
# report_service not yet implemented (parallel task)
raise HTTPException(501, "Report service not yet available")
@router.get("/weekly/{jahr}/{kw}")
def weekly_report(
jahr: int,
kw: int,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""Return aggregated data for a single calendar week.
Accessible to both admin and dak_mitarbeiter users.
"""
try:
from app.services.report_service import calculate_sheet1_data
data = calculate_sheet1_data(db, jahr)
weekly = [w for w in data.get("weekly", []) if w.get("kw") == kw]
return weekly[0] if weekly else {"kw": kw, "erstberatungen": 0}
except ImportError:
raise HTTPException(501, "Report service not yet available")
@router.post("/generate", response_model=ReportMeta)
def generate_report(
jahr: int | None = Query(None),
kw: int | None = Query(None),
db: Session = Depends(get_db),
user: User = Depends(require_admin),
):
"""Generate a full Berichtswesen Excel report and persist it to disk + DB.
Admin only. Defaults to the current ISO year/week if not specified.
Depends on report_service, excel_export, and vorjahr_service (parallel tasks).
"""
if not jahr:
from app.utils.kw_utils import date_to_jahr, date_to_kw
today = date.today()
jahr = date_to_jahr(today)
kw = kw or date_to_kw(today)
if not kw:
from app.utils.kw_utils import date_to_kw
kw = date_to_kw(date.today())
try:
from app.services.excel_export import generate_berichtswesen_xlsx
from app.services.report_service import generate_full_report
from app.services.vorjahr_service import get_vorjahr_summary
report_data = generate_full_report(db, jahr, kw)
vorjahr = get_vorjahr_summary(db, jahr)
xlsx_bytes = generate_berichtswesen_xlsx(report_data, jahr, vorjahr)
# Persist Excel file to disk
reports_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"reports",
)
os.makedirs(reports_dir, exist_ok=True)
filename = f"Berichtswesen_{jahr}_KW{kw:02d}.xlsx"
filepath = os.path.join(reports_dir, filename)
with open(filepath, "wb") as f:
f.write(xlsx_bytes)
# Save report metadata to DB
report = WeeklyReport(
jahr=jahr,
kw=kw,
report_date=date.today(),
report_data=report_data,
generated_by=user.id,
)
report.report_file_path = filepath
db.add(report)
db.commit()
db.refresh(report)
return ReportMeta.model_validate(report)
except ImportError as exc:
raise HTTPException(501, f"Required service not yet available: {exc}")
@router.get("/download/{report_id}")
def download_report(
report_id: int,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""Download a previously generated Berichtswesen Excel file.
Accessible to both admin and dak_mitarbeiter users.
"""
report = db.query(WeeklyReport).filter(WeeklyReport.id == report_id).first()
if not report or not report.report_file_path:
raise HTTPException(404, "Report not found")
if not os.path.exists(report.report_file_path):
raise HTTPException(404, "Report file not found on disk")
with open(report.report_file_path, "rb") as f:
content = f.read()
filename = os.path.basename(report.report_file_path)
return StreamingResponse(
BytesIO(content),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
@router.get("/list", response_model=ReportListResponse)
def list_reports(
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
):
"""List all generated reports, newest first.
Accessible to both admin and dak_mitarbeiter users.
"""
reports = (
db.query(WeeklyReport).order_by(WeeklyReport.generated_at.desc()).all()
)
return ReportListResponse(
items=[ReportMeta.model_validate(r) for r in reports],
total=len(reports),
)