"""RecurringElement API — AP0.9e.""" from __future__ import annotations from datetime import datetime from typing import Literal, Optional from capabilities import require_capability from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from services import recurring as recurring_service from tenant_context import TenantContext router = APIRouter(prefix="/api/recurring", tags=["recurring"]) class RecurringUpdateRequest(BaseModel): title: Optional[str] = Field(default=None, min_length=1, max_length=255) description: Optional[str] = None status: Optional[Literal["active", "paused", "ended"]] = None interval_days: Optional[int] = Field(default=None, gt=0) clear_interval_days: bool = False next_due_at: Optional[str] = None clear_next_due_at: bool = False class RecurringCompleteRequest(BaseModel): measurement_note: Optional[str] = Field(default=None, max_length=2000) def _parse_datetime(value: Optional[str]) -> Optional[datetime]: if value is None: return None try: return datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError as exc: raise HTTPException(status_code=400, detail="Ungültiges next_due_at") from exc @router.get("/{recurring_id}") def get_recurring_element( recurring_id: str, ctx: TenantContext = Depends(require_capability("kairo.recurring.read")), ): item = recurring_service.get_recurring_element( tenant_id=ctx.tenant_id, recurring_id=recurring_id ) if not item: raise HTTPException(status_code=404, detail="Recurring-Element nicht gefunden") return item @router.patch("/{recurring_id}") def update_recurring_element( recurring_id: str, body: RecurringUpdateRequest, ctx: TenantContext = Depends(require_capability("kairo.recurring.manage")), ): next_due_at = _parse_datetime(body.next_due_at) if body.next_due_at is not None else None try: item = recurring_service.update_recurring_element( tenant_id=ctx.tenant_id, recurring_id=recurring_id, user_id=ctx.user_id, title=body.title, description=body.description, status=body.status, interval_days=body.interval_days, clear_interval_days=body.clear_interval_days, next_due_at=next_due_at, clear_next_due_at=body.clear_next_due_at, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc if not item: raise HTTPException(status_code=404, detail="Recurring-Element nicht gefunden") return item @router.post("/{recurring_id}/complete") def complete_recurring_practice( recurring_id: str, body: RecurringCompleteRequest, ctx: TenantContext = Depends(require_capability("kairo.recurring.manage")), ): from services.cadence_instance import complete_open_instance try: result = complete_open_instance( tenant_id=ctx.tenant_id, recurring_element_id=recurring_id, user_id=ctx.user_id or None, measurement_note=body.measurement_note or "", ) except ValueError as exc: detail = str(exc) if detail == "Recurring-Element nicht gefunden": raise HTTPException(status_code=404, detail=detail) from exc raise HTTPException(status_code=400, detail=detail) from exc return result @router.delete("/{recurring_id}", status_code=204) def delete_recurring_element( recurring_id: str, ctx: TenantContext = Depends(require_capability("kairo.recurring.manage")), ): if not recurring_service.delete_recurring_element( tenant_id=ctx.tenant_id, recurring_id=recurring_id, user_id=ctx.user_id ): raise HTTPException(status_code=404, detail="Recurring-Element nicht gefunden")