"""Sparziele.""" from fastapi import APIRouter, Query, status from sqlalchemy import select from sqlalchemy.exc import IntegrityError from app.api.deps import DbSession from app.core.errors import ConflictError from app.models import SavingsGoal from app.schemas.budget import SavingsGoalCreate, SavingsGoalOut, SavingsGoalUpdate from app.schemas.common import ErrorResponse, MessageResponse from app.services.crud import apply_updates, get_or_404 router = APIRouter(prefix="/savings-goals", tags=["savings-goals"]) NOT_FOUND = {status.HTTP_404_NOT_FOUND: {"model": ErrorResponse}} @router.get("", response_model=list[SavingsGoalOut], summary="Sparziele auflisten") async def list_goals( session: DbSession, include_archived: bool = Query(default=False), ) -> list[SavingsGoal]: stmt = select(SavingsGoal).order_by(SavingsGoal.target_date.nulls_last(), SavingsGoal.name) if not include_archived: stmt = stmt.where(SavingsGoal.is_archived.is_(False)) return list((await session.execute(stmt)).scalars().all()) @router.post( "", response_model=SavingsGoalOut, status_code=status.HTTP_201_CREATED, summary="Sparziel anlegen", ) async def create_goal(payload: SavingsGoalCreate, session: DbSession) -> SavingsGoal: goal = SavingsGoal(**payload.model_dump()) session.add(goal) try: await session.commit() except IntegrityError as exc: await session.rollback() raise ConflictError(f"Das Sparziel '{payload.name}' existiert bereits.") from exc await session.refresh(goal) return goal @router.get( "/{goal_id}", response_model=SavingsGoalOut, responses=NOT_FOUND, summary="Sparziel lesen" ) async def read_goal(goal_id: int, session: DbSession) -> SavingsGoal: return await get_or_404(session, SavingsGoal, goal_id) @router.patch( "/{goal_id}", response_model=SavingsGoalOut, responses=NOT_FOUND, summary="Sparziel ändern" ) async def update_goal(goal_id: int, payload: SavingsGoalUpdate, session: DbSession) -> SavingsGoal: goal = await get_or_404(session, SavingsGoal, goal_id) apply_updates(goal, payload) try: await session.commit() except IntegrityError as exc: await session.rollback() raise ConflictError("Ein Sparziel mit diesem Namen existiert bereits.") from exc await session.refresh(goal) return goal @router.delete( "/{goal_id}", response_model=MessageResponse, responses=NOT_FOUND, summary="Sparziel löschen", ) async def delete_goal(goal_id: int, session: DbSession) -> MessageResponse: goal = await get_or_404(session, SavingsGoal, goal_id) await session.delete(goal) await session.commit() return MessageResponse(detail="Sparziel gelöscht.")