# TODO — Fix `GET /api/planned-days` 405 (planned-days router is incomplete) **Status:** open · **Area:** backend · **Owner:** _unassigned_ · **Filed:** 2026-06-14 ## Symptom Loading the Dashboard and adding a day in the Calendar both log: ``` "GET /api/planned-days?child_id=3b9b6dad-... HTTP/1.1" 405 Method Not Allowed ``` Adding a day works (`POST /api/planned-days` → 201), but the follow-up refresh (`GET /api/planned-days?child_id=…`) is rejected, so the calendar/dashboard never show planned days. ## Root cause `backend/app/routers/planned_days.py` defines `POST ""` but **no `GET ""`**. FastAPI returns *405* (not 404) because the path exists for another verb. The list-by-child endpoint was never finished — the `GET /range` handler (lines ~65–78) is a dead stub (`pass` body, `= None` defaults on its `service`/`_current_user` deps) and `/range` also collides with `GET /{planned_leave_day_id}`. Same router has a second gap: `api.updatePlannedDay` issues `PUT /api/planned-days/{id}` (used by Calendar edit, `frontend/src/pages/Calendar.tsx:99`), but there is **no PUT handler** and no `PlanningService.update`. Editing an existing day will 405 once adding works. ## Fix In `backend/app/services/planning_service.py`: 1. Add `list_by_child`: ```python async def list_by_child(self, child_id: UUID) -> list[PlannedLeaveDay]: return await self._repo.list(child_id=child_id) ``` (The SQLite `list()` already filters on `child_id` alone.) 2. Add `update(self, planned_leave_day_id: UUID, day_type: str | None, is_dubbeldag: bool | None) -> PlannedLeaveDay` — load via `get_by_id` (raise `NotFoundError` if missing), apply provided fields, call `repo.update`. In `backend/app/routers/planned_days.py`: 3. Add the list route **before** `GET /{planned_leave_day_id}`: ```python @router.get("", response_model=list[PlannedLeaveDayResponse]) async def list_planned_days( service: PlanningServiceDep, _current_user: CurrentUserDep, child_id: UUID = Query(...), ) -> list[PlannedLeaveDayResponse]: return [_to_response(d) for d in await service.list_by_child(child_id)] ``` 4. Delete the dead `GET /range` stub (lines ~65–78). 5. Add the `PUT /{planned_leave_day_id}` handler using the existing `PlannedLeaveDayUpdate` model → `service.update(...)` → `_to_response`. > Route ordering matters: declare `""`, `/workspace/...`, then `/{planned_leave_day_id}` > last, so the UUID catch-all doesn't swallow the others. ## Verification From `backend/`: ```bash uv run ruff check . && uv run ruff format --check . && uv run pytest ``` Add tests: - `GET /api/planned-days?child_id=…` → 200 with the seeded rows for that child. - `PUT /api/planned-days/{id}` → 200, fields updated; unknown id → 404. Manual: add a vacation day in the Calendar → `POST 201` then `GET 200` and the list refreshes; edit an existing day → `PUT` succeeds. After code changes: `graphify update .` ## Related The 405 only blocks the *planned-days* list. The dashboard's empty **metrics / parental-leave breakdown** is a separate snake_case↔camelCase issue — see `DASHBOARD_EMPTY_DATA_TODO.md`.