"""Work cycle (Sprint/Zeitbox) API — AP2.0f.""" from __future__ import annotations from datetime import date from typing import Literal, Optional from capabilities import require_capability from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from services import work_cycle as work_cycle_service from tenant_context import TenantContext initiative_router = APIRouter( prefix="/api/initiatives/{initiative_id}/work-cycles", tags=["work-cycles"], ) class WorkCycleCreateRequest(BaseModel): title: str = Field(min_length=1, max_length=255) goal_description: str = "" status: Literal["planned", "active"] = "planned" target_date: Optional[date] = None sort_order: int = 0 @initiative_router.get("") def list_work_cycles( initiative_id: str, ctx: TenantContext = Depends(require_capability("kairo.milestone.read")), ): return work_cycle_service.list_work_cycles_for_initiative( tenant_id=ctx.tenant_id, initiative_id=initiative_id ) @initiative_router.get("/active") def get_active_work_cycle( initiative_id: str, ctx: TenantContext = Depends(require_capability("kairo.milestone.read")), ): item = work_cycle_service.get_active_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id ) if not item: return None item["open_action_count"] = work_cycle_service.count_actions_in_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id, work_cycle_id=item["id"], ) return item @initiative_router.post("", status_code=201) def create_work_cycle( initiative_id: str, body: WorkCycleCreateRequest, ctx: TenantContext = Depends(require_capability("kairo.milestone.manage")), ): try: item = work_cycle_service.create_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id, title=body.title, goal_description=body.goal_description, status=body.status, target_date=body.target_date, sort_order=body.sort_order, user_id=ctx.user_id, ) if body.status == "active": item = work_cycle_service.activate_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id, work_cycle_id=item["id"], user_id=ctx.user_id, ) return item except ValueError as exc: detail = str(exc) if detail == "Initiative nicht gefunden": raise HTTPException(status_code=404, detail=detail) from exc raise HTTPException(status_code=400, detail=detail) from exc @initiative_router.post("/{work_cycle_id}/activate") def activate_work_cycle( initiative_id: str, work_cycle_id: str, ctx: TenantContext = Depends(require_capability("kairo.milestone.manage")), ): try: return work_cycle_service.activate_work_cycle( tenant_id=ctx.tenant_id, initiative_id=initiative_id, work_cycle_id=work_cycle_id, user_id=ctx.user_id, ) except ValueError as exc: detail = str(exc) if detail in ("Initiative nicht gefunden", "Zeitbox nicht gefunden"): raise HTTPException(status_code=404, detail=detail) from exc raise HTTPException(status_code=400, detail=detail) from exc