"""HTTP routes for planned leave days (calendar planning) — READ ONLY. Restructure step 3b: `planned_leave_day` is purely derived output now. The write endpoints that used to live here (`POST`/`PUT`/`DELETE`) are gone — every write goes through `POST /api/plan/sync(-with-wish)` (the allocator) or `POST`/`DELETE /api/day-assertions` (a hand-pin, an ad-hoc day, or an exclusion). Routers stay thin: validate via Pydantic, call the service, map domain errors to HTTP. """ from uuid import UUID from fastapi import APIRouter, HTTPException, Query, status from app.dependencies import PlanningServiceDep, WorkspaceIdDep, WorkspaceScopeDep from app.entities.planned_leave import PlannedLeaveDay from app.models.planned_leave import PlannedLeaveDayResponse from app.services.planning_service import PlanningService from app.utils.errors import NotFoundError router = APIRouter(prefix="/api/planned-days", tags=["planned-days"]) async def _to_responses( service: PlanningService, days: list[PlannedLeaveDay] ) -> list[PlannedLeaveDayResponse]: """Annotate a batch of rows with the live `is_pinned` signal in one shot (one `pinned_keys` read per workspace, not one per row) — see `PlanningService.pinned_keys`. """ workspace_ids = {d.workspace_id for d in days} pinned: set[tuple[UUID, UUID, object]] = set() for ws_id in workspace_ids: pinned |= await service.pinned_keys(ws_id) return [ PlannedLeaveDayResponse.from_entity( d, is_pinned=(d.parent_personal_data_uuid, d.child_id, d.date) in pinned ) for d in days ] @router.get("", response_model=list[PlannedLeaveDayResponse]) async def list_planned_days( service: PlanningServiceDep, scope: WorkspaceScopeDep, child_id: UUID = Query(...), ) -> list[PlannedLeaveDayResponse]: await scope.check_child(child_id) days = await service.list_by_child(child_id) return await _to_responses(service, days) @router.get("/{planned_leave_day_id}", response_model=PlannedLeaveDayResponse) async def get_planned_day( planned_leave_day_id: UUID, service: PlanningServiceDep, scope: WorkspaceScopeDep, ) -> PlannedLeaveDayResponse: try: day = await service.get(planned_leave_day_id, workspace_id=scope.workspace_id) except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc is_pinned = (day.parent_personal_data_uuid, day.child_id, day.date) in ( await service.pinned_keys(day.workspace_id) ) return PlannedLeaveDayResponse.from_entity(day, is_pinned=is_pinned) @router.get("/workspace/{workspace_id}", response_model=list[PlannedLeaveDayResponse]) async def list_planned_days_by_workspace( workspace_id: WorkspaceIdDep, service: PlanningServiceDep, ) -> list[PlannedLeaveDayResponse]: days = await service.list_by_workspace(workspace_id) return await _to_responses(service, days)