""" Goal Progress Router - Progress Tracking for Goals Endpoints for logging and managing goal progress: - Get progress history - Create manual progress entries - Delete progress entries Part of v9h Goal System. """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional from datetime import date from db import get_db, get_cursor, r2d from auth import require_auth router = APIRouter(prefix="/api/goals", tags=["goal-progress"]) # ============================================================================ # Pydantic Models # ============================================================================ class GoalProgressCreate(BaseModel): """Log progress for a goal""" date: date value: float note: Optional[str] = None class GoalProgressUpdate(BaseModel): """Update progress entry""" value: Optional[float] = None note: Optional[str] = None # ============================================================================ # Endpoints # ============================================================================ @router.get("/{goal_id}/progress") def get_goal_progress(goal_id: str, session: dict = Depends(require_auth)): """Get progress history for a goal""" pid = session['profile_id'] with get_db() as conn: cur = get_cursor(conn) # Verify ownership cur.execute( "SELECT id FROM goals WHERE id = %s AND profile_id = %s", (goal_id, pid) ) if not cur.fetchone(): raise HTTPException(status_code=404, detail="Ziel nicht gefunden") # Get progress entries cur.execute(""" SELECT id, date, value, note, source, created_at FROM goal_progress_log WHERE goal_id = %s ORDER BY date DESC """, (goal_id,)) entries = cur.fetchall() return [r2d(e) for e in entries] @router.post("/{goal_id}/progress") def create_goal_progress(goal_id: str, data: GoalProgressCreate, session: dict = Depends(require_auth)): """Log new progress for a goal""" pid = session['profile_id'] with get_db() as conn: cur = get_cursor(conn) # Verify ownership and check if manual entry is allowed cur.execute(""" SELECT g.id, g.unit, gt.source_table FROM goals g LEFT JOIN goal_type_definitions gt ON g.goal_type = gt.type_key WHERE g.id = %s AND g.profile_id = %s """, (goal_id, pid)) goal = cur.fetchone() if not goal: raise HTTPException(status_code=404, detail="Ziel nicht gefunden") # Prevent manual entries for goals with automatic data sources if goal['source_table']: raise HTTPException( status_code=400, detail=f"Manuelle Einträge nicht erlaubt für automatisch erfasste Ziele. " f"Bitte nutze die entsprechende Erfassungsseite (z.B. Gewicht, Aktivität)." ) # Insert progress entry try: cur.execute(""" INSERT INTO goal_progress_log (goal_id, profile_id, date, value, note, source) VALUES (%s, %s, %s, %s, %s, 'manual') RETURNING id """, (goal_id, pid, data.date, data.value, data.note)) progress_id = cur.fetchone()['id'] # Trigger will auto-update goals.current_value return { "id": progress_id, "message": f"Fortschritt erfasst: {data.value} {goal['unit']}" } except Exception as e: if "unique_progress_per_day" in str(e): raise HTTPException( status_code=400, detail=f"Für {data.date} existiert bereits ein Eintrag. Bitte bearbeite den existierenden Eintrag." ) raise HTTPException(status_code=500, detail=f"Fehler beim Speichern: {str(e)}") @router.delete("/{goal_id}/progress/{progress_id}") def delete_goal_progress(goal_id: str, progress_id: str, session: dict = Depends(require_auth)): """Delete progress entry""" pid = session['profile_id'] with get_db() as conn: cur = get_cursor(conn) # Verify ownership cur.execute( "SELECT id FROM goals WHERE id = %s AND profile_id = %s", (goal_id, pid) ) if not cur.fetchone(): raise HTTPException(status_code=404, detail="Ziel nicht gefunden") # Delete progress entry cur.execute( "DELETE FROM goal_progress_log WHERE id = %s AND goal_id = %s AND profile_id = %s", (progress_id, goal_id, pid) ) if cur.rowcount == 0: raise HTTPException(status_code=404, detail="Progress-Eintrag nicht gefunden") # After deletion, recalculate current_value from remaining entries cur.execute(""" UPDATE goals SET current_value = ( SELECT value FROM goal_progress_log WHERE goal_id = %s ORDER BY date DESC LIMIT 1 ) WHERE id = %s """, (goal_id, goal_id)) return {"message": "Progress-Eintrag gelöscht"}