"""Action (Maßnahme) API — AP0.5 / AP0.7.""" from __future__ import annotations from datetime import datetime from typing import Literal, Optional from capabilities import require_capability from data_layer import actions as dl_actions from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from services import actions as action_service from services import tasks as task_service from tenant_context import TenantContext router = APIRouter(prefix="/api/actions", tags=["actions"]) class ActionUpdateRequest(BaseModel): title: Optional[str] = Field(default=None, min_length=1, max_length=255) description: Optional[str] = None status: Optional[ Literal["open", "ready", "in_progress", "blocked", "review_required", "done", "discarded"] ] = None priority: Optional[Literal["low", "normal", "high"]] = None due_at: Optional[str] = None clear_due_at: bool = False project_id: Optional[str] = None clear_project: bool = False class TaskCreateRequest(BaseModel): title: str = Field(min_length=1, max_length=255) description: str = "" status: Literal["open", "in_progress", "done", "discarded"] = "open" roadmap_item_id: Optional[str] = None sort_order: int = 0 due_at: Optional[str] = None class ActionAssignmentsRequest(BaseModel): actor_ids: list[str] = Field(default_factory=list) @router.get("/me/open") def list_my_open_actions( ctx: TenantContext = Depends(require_capability("kairo.action.read")), ): try: return dl_actions.get_all_my_open_actions(ctx) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/{action_id}/tasks") def list_action_tasks( action_id: str, ctx: TenantContext = Depends(require_capability("kairo.action.read")), ): try: return task_service.list_tasks_for_action( tenant_id=ctx.tenant_id, action_id=action_id ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.post("/{action_id}/tasks", status_code=201) def create_action_task( action_id: str, body: TaskCreateRequest, ctx: TenantContext = Depends(require_capability("kairo.action.manage")), ): due_at = None if body.due_at: try: due_at = datetime.fromisoformat(body.due_at.replace("Z", "+00:00")) except ValueError as exc: raise HTTPException(status_code=400, detail="Ungültiges due_at") from exc try: return task_service.create_task( tenant_id=ctx.tenant_id, action_id=action_id, user_id=ctx.user_id, title=body.title, description=body.description, status=body.status, roadmap_item_id=body.roadmap_item_id, sort_order=body.sort_order, due_at=due_at, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/{action_id}") def get_action( action_id: str, ctx: TenantContext = Depends(require_capability("kairo.action.read")), ): item = action_service.get_action(tenant_id=ctx.tenant_id, action_id=action_id) if not item: raise HTTPException(status_code=404, detail="Maßnahme nicht gefunden") return item @router.patch("/{action_id}") def update_action( action_id: str, body: ActionUpdateRequest, ctx: TenantContext = Depends(require_capability("kairo.action.manage")), ): due_at = None if body.due_at is not None: try: due_at = datetime.fromisoformat(body.due_at.replace("Z", "+00:00")) except ValueError as exc: raise HTTPException(status_code=400, detail="Ungültiges due_at") from exc try: item = action_service.update_action( tenant_id=ctx.tenant_id, action_id=action_id, user_id=ctx.user_id, title=body.title, description=body.description, status=body.status, priority=body.priority, due_at=due_at, clear_due_at=body.clear_due_at, project_id=body.project_id, clear_project=body.clear_project, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc if not item: raise HTTPException(status_code=404, detail="Maßnahme nicht gefunden") return item @router.put("/{action_id}/assignments") def set_action_assignments( action_id: str, body: ActionAssignmentsRequest, ctx: TenantContext = Depends(require_capability("kairo.action.manage")), ): try: item = action_service.set_action_assignments( tenant_id=ctx.tenant_id, action_id=action_id, actor_ids=body.actor_ids, user_id=ctx.user_id, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc if not item: raise HTTPException(status_code=404, detail="Maßnahme nicht gefunden") return item @router.delete("/{action_id}", status_code=204) def delete_action( action_id: str, ctx: TenantContext = Depends(require_capability("kairo.action.manage")), ): if not action_service.delete_action( tenant_id=ctx.tenant_id, action_id=action_id, user_id=ctx.user_id, ): raise HTTPException(status_code=404, detail="Maßnahme nicht gefunden")