Kairo-Jinkendo/backend/routers/work_cycles.py
Lars f6aba94864
Some checks failed
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 2m56s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Has been cancelled
AP2.2d: Sprint-Planung, Abschluss und Product-Fallback.
Backlog committet in gewaehlten Sprint, Sprint complete-Endpoint, continuous_product-Ansicht ohne aktiven Sprint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 19:21:49 +02:00

126 lines
4.0 KiB
Python

"""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}/complete")
def complete_work_cycle(
initiative_id: str,
work_cycle_id: str,
ctx: TenantContext = Depends(require_capability("kairo.milestone.manage")),
):
try:
return work_cycle_service.complete_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", "Sprint nicht gefunden oder bereits abgeschlossen"):
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