""" FastAPI routes for Audit Sessions & Sign-off functionality. Sprint 3 Phase 3: Auditor-Verbesserungen Endpoints: - /audit/sessions: Manage audit sessions - /audit/checklist: Audit checklist with sign-off Phase 1 Step 4 refactor: handlers are thin and delegate to ``AuditSessionService`` / ``AuditSignOffService``. Domain errors raised by the services are translated to HTTPException via ``translate_domain_errors``. """ import logging from typing import Any, List, Optional from fastapi import APIRouter, Depends, Query from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors from compliance.schemas.audit_session import ( AuditChecklistResponse, AuditSessionDetailResponse, AuditSessionResponse, AuditSessionSummary, CreateAuditSessionRequest, SignOffRequest, SignOffResponse, ) from compliance.services.audit_session_service import AuditSessionService from compliance.services.audit_signoff_service import AuditSignOffService logger = logging.getLogger(__name__) router = APIRouter(prefix="/audit", tags=["compliance-audit"]) # ---------------------------------------------------------------------- # Dependency-injection factories # ---------------------------------------------------------------------- def get_audit_session_service(db: Session = Depends(get_db)) -> AuditSessionService: return AuditSessionService(db) def get_audit_signoff_service(db: Session = Depends(get_db)) -> AuditSignOffService: return AuditSignOffService(db) # ============================================================================ # Audit Sessions # ============================================================================ @router.post("/sessions", response_model=AuditSessionResponse) async def create_audit_session( request: CreateAuditSessionRequest, service: AuditSessionService = Depends(get_audit_session_service), ) -> AuditSessionResponse: """Create a new audit session for structured compliance reviews.""" with translate_domain_errors(): return service.create(request) @router.get("/sessions", response_model=List[AuditSessionSummary]) async def list_audit_sessions( status: Optional[str] = None, service: AuditSessionService = Depends(get_audit_session_service), ) -> List[AuditSessionSummary]: """List all audit sessions, optionally filtered by status.""" with translate_domain_errors(): return service.list(status) @router.get("/sessions/{session_id}", response_model=AuditSessionDetailResponse) async def get_audit_session( session_id: str, service: AuditSessionService = Depends(get_audit_session_service), ) -> AuditSessionDetailResponse: """Get detailed information about a specific audit session.""" with translate_domain_errors(): return service.get(session_id) @router.put("/sessions/{session_id}/start") async def start_audit_session( session_id: str, service: AuditSessionService = Depends(get_audit_session_service), ) -> dict[str, Any]: """Start an audit session (draft -> in_progress).""" with translate_domain_errors(): return service.start(session_id) @router.put("/sessions/{session_id}/complete") async def complete_audit_session( session_id: str, service: AuditSessionService = Depends(get_audit_session_service), ) -> dict[str, Any]: """Complete an audit session (in_progress -> completed).""" with translate_domain_errors(): return service.complete(session_id) @router.put("/sessions/{session_id}/archive") async def archive_audit_session( session_id: str, service: AuditSessionService = Depends(get_audit_session_service), ) -> dict[str, Any]: """Archive a completed audit session.""" with translate_domain_errors(): return service.archive(session_id) @router.delete("/sessions/{session_id}") async def delete_audit_session( session_id: str, service: AuditSessionService = Depends(get_audit_session_service), ) -> dict[str, Any]: """Delete a draft or archived audit session and all its sign-offs.""" with translate_domain_errors(): return service.delete(session_id) # ============================================================================ # Audit Checklist & Sign-off # ============================================================================ @router.get("/checklist/{session_id}", response_model=AuditChecklistResponse) async def get_audit_checklist( session_id: str, page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), status_filter: Optional[str] = None, regulation_filter: Optional[str] = None, search: Optional[str] = None, service: AuditSignOffService = Depends(get_audit_signoff_service), ) -> AuditChecklistResponse: """Get the paginated audit checklist for a session.""" with translate_domain_errors(): return service.get_checklist( session_id=session_id, page=page, page_size=page_size, status_filter=status_filter, regulation_filter=regulation_filter, search=search, ) @router.put( "/checklist/{session_id}/items/{requirement_id}/sign-off", response_model=SignOffResponse, ) async def sign_off_item( session_id: str, requirement_id: str, request: SignOffRequest, service: AuditSignOffService = Depends(get_audit_signoff_service), ) -> SignOffResponse: """Sign off on a specific requirement in an audit session.""" with translate_domain_errors(): return service.sign_off(session_id, requirement_id, request) @router.get( "/checklist/{session_id}/items/{requirement_id}", response_model=SignOffResponse, ) async def get_sign_off( session_id: str, requirement_id: str, service: AuditSignOffService = Depends(get_audit_signoff_service), ) -> SignOffResponse: """Get the current sign-off status for a specific requirement.""" with translate_domain_errors(): return service.get_sign_off(session_id, requirement_id) # ============================================================================ # PDF Report Generation # ============================================================================ @router.get("/sessions/{session_id}/report/pdf") async def generate_audit_pdf_report( session_id: str, language: str = Query("de", pattern="^(de|en)$"), include_signatures: bool = Query(True), service: AuditSessionService = Depends(get_audit_session_service), ) -> StreamingResponse: """Generate a PDF report for an audit session.""" with translate_domain_errors(): return service.generate_pdf( session_id=session_id, language=language, include_signatures=include_signatures, )