"""HTTP routes for day assertions — restructure step 3b. Replaces the write side of the legacy `/api/planned-days` (hand-pin/ad-hoc) and `/api/planned-day-exclusions` endpoints (both deleted this step) with one endpoint expressing all three: a pin (`day_type` concrete), an ad-hoc day (`wish_id` omitted), and an exclusion (`day_type='none'`). Routers stay thin: validate via Pydantic, call the service, map domain errors to HTTP. """ from datetime import date from uuid import UUID from fastapi import APIRouter, HTTPException, status from app.dependencies import ( DayAssertionServiceDep, WorkspaceIdDep, WorkspaceScopeDep, ) from app.models.day_assertion import DayAssertionCreate, DayAssertionResponse from app.utils.errors import ValidationError router = APIRouter(prefix="/api/day-assertions", tags=["day-assertions"]) @router.post("", response_model=DayAssertionResponse, status_code=status.HTTP_201_CREATED) async def assert_day( payload: DayAssertionCreate, service: DayAssertionServiceDep, scope: WorkspaceScopeDep, ) -> DayAssertionResponse: scope.check_workspace(payload.workspace_id) await scope.check_child(payload.child_id) await scope.check_member(payload.parent_personal_data_uuid) try: entity = await service.assert_day( workspace_id=payload.workspace_id, child_id=payload.child_id, parent_personal_data_uuid=payload.parent_personal_data_uuid, day=payload.date, day_type=payload.day_type, is_dubbeldag=payload.is_dubbeldag, wish_id=payload.wish_id, ) except ValidationError as exc: if exc.message_key: raise HTTPException( status.HTTP_422_UNPROCESSABLE_CONTENT, detail={"message_key": exc.message_key, "data": exc.data}, ) from exc raise HTTPException(status.HTTP_422_UNPROCESSABLE_CONTENT, str(exc)) from exc return DayAssertionResponse.from_entity(entity) @router.get("/workspace/{workspace_id}", response_model=list[DayAssertionResponse]) async def list_day_assertions( workspace_id: WorkspaceIdDep, service: DayAssertionServiceDep ) -> list[DayAssertionResponse]: entities = await service.list_for_workspace(workspace_id) return [DayAssertionResponse.from_entity(e) for e in entities] @router.delete("", status_code=status.HTTP_204_NO_CONTENT) async def clear_day_assertion( service: DayAssertionServiceDep, scope: WorkspaceScopeDep, workspace_id: UUID, child_id: UUID, parent_personal_data_uuid: UUID, date: date, ) -> None: """Retract the assertion at this (workspace, child, parent, date), if any — idempotent, matching `DayAssertionService.clear`. By natural key rather than by id: every caller (un-pinning a hand-added day, deleting an ad-hoc band, restoring an exclusion) already knows the key from context and would otherwise have to look the assertion up first just to delete it. """ scope.check_workspace(workspace_id) await scope.check_child(child_id) await scope.check_member(parent_personal_data_uuid) await service.clear(workspace_id, child_id, parent_personal_data_uuid, date)