"""HTTP routes for settings and tips. Covers: - Break periods (CRUD) — uses models/break_period.py - Tips endpoint """ from datetime import datetime from uuid import UUID, uuid4 from fastapi import APIRouter, HTTPException, status from app.dependencies import ( BreakPeriodRepositoryDep, ChildRepositoryDep, ChildServiceDep, CurrentUserDep, ParentalLeaveRepositoryDep, PersonalDataRepositoryDep, PlannedLeaveRepositoryDep, VacationDaysRepositoryDep, WorkspaceRepositoryDep, ) from app.entities.break_period import BreakPeriod from app.models.break_period import ( BreakPeriodCreate, BreakPeriodResponse, BreakPeriodUpdate, ) from app.models.child import ChildCreate, ChildResponse, ChildUpdate from app.models.personal_data import PersonalDataResponse, PersonalDataUpdate from app.services.tips_service import TipsService from app.utils.errors import NotFoundError, ValidationError router = APIRouter(tags=["settings"]) # --------------------------------------------------------------------------- # Tips # --------------------------------------------------------------------------- tips_router = APIRouter(prefix="/api/tips", tags=["tips"]) @tips_router.get("/{workspace_id}", response_model=list[dict]) async def get_tips( workspace_id: UUID, leave_repo: ParentalLeaveRepositoryDep, planned_repo: PlannedLeaveRepositoryDep, child_repo: ChildRepositoryDep, pd_repo: PersonalDataRepositoryDep, vacation_repo: VacationDaysRepositoryDep, break_repo: BreakPeriodRepositoryDep, ws_repo: WorkspaceRepositoryDep, _current_user: CurrentUserDep, ) -> list[dict]: service = TipsService(leave_repo, planned_repo, child_repo, pd_repo, vacation_repo, break_repo, ws_repo) tips = await service.get_tips(workspace_id) return [ {"id": t.id, "severity": t.severity, "message_key": t.message_key, "data": t.data} for t in tips ] # --------------------------------------------------------------------------- # Children # --------------------------------------------------------------------------- child_router = APIRouter(prefix="/api/children", tags=["children"]) def _child_to_response(child) -> ChildResponse: return ChildResponse.from_entity(child) @child_router.post("", response_model=ChildResponse, status_code=status.HTTP_201_CREATED) async def create_child( payload: ChildCreate, service: ChildServiceDep, _current_user: CurrentUserDep, ) -> ChildResponse: try: child = await service.create( workspace_id=payload.workspace_id, name=payload.name, birth_date=payload.birth_date, preschool_start_date=payload.preschool_start_date, ) return _child_to_response(child) except ValidationError as exc: raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, str(exc)) from exc @child_router.get("/{child_id}", response_model=ChildResponse) async def get_child( child_id: UUID, service: ChildServiceDep, _current_user: CurrentUserDep, ) -> ChildResponse: try: child = await service.get(child_id) return _child_to_response(child) except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc @child_router.get("/workspace/{workspace_id}", response_model=list[ChildResponse]) async def list_children( workspace_id: UUID, service: ChildServiceDep, _current_user: CurrentUserDep, ) -> list[ChildResponse]: children = await service.list_by_workspace(workspace_id) return [_child_to_response(c) for c in children] # Settings endpoint for updating child settings_child_router = APIRouter(prefix="/api/settings", tags=["settings"]) @settings_child_router.put("/child/{child_id}", response_model=ChildResponse) async def update_child( child_id: UUID, payload: ChildUpdate, service: ChildServiceDep, _current_user: CurrentUserDep, ) -> ChildResponse: try: child = await service.update( child_id=child_id, name=payload.name, birth_date=payload.birth_date, preschool_start_date=payload.preschool_start_date, ) return _child_to_response(child) except ValidationError as exc: raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, str(exc)) from exc except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc @settings_child_router.delete("/{child_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_child( child_id: UUID, service: ChildServiceDep, _current_user: CurrentUserDep, ) -> None: try: await service.delete(child_id) except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc @settings_child_router.put("/parent/{personal_data_uuid}", response_model=PersonalDataResponse) async def update_parent_settings( personal_data_uuid: UUID, payload: PersonalDataUpdate, repo: PersonalDataRepositoryDep, _current_user: CurrentUserDep, ) -> PersonalDataResponse: try: entity = await repo.get_by_id(personal_data_uuid) if entity is None: raise HTTPException( status.HTTP_404_NOT_FOUND, f"PersonalData {personal_data_uuid} not found" ) # Apply partial updates if payload.salary_type is not None: entity.salary_type = payload.salary_type if payload.employment_status is not None: entity.employment_status = payload.employment_status updated = await repo.update(personal_data_uuid, entity) return PersonalDataResponse.from_entity(updated) except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc # --------------------------------------------------------------------------- # Break periods # --------------------------------------------------------------------------- break_router = APIRouter(prefix="/api/break-periods", tags=["break-periods"]) def _to_response(bp: BreakPeriod) -> BreakPeriodResponse: return BreakPeriodResponse.from_entity(bp) @break_router.get("/{workspace_id}", response_model=list[BreakPeriodResponse]) async def list_break_periods( workspace_id: UUID, repo: BreakPeriodRepositoryDep, _current_user: CurrentUserDep, ) -> list[BreakPeriodResponse]: periods = await repo.get_by_workspace(workspace_id) return [_to_response(bp) for bp in periods] @break_router.post("", response_model=BreakPeriodResponse, status_code=status.HTTP_201_CREATED) async def create_break_period( payload: BreakPeriodCreate, repo: BreakPeriodRepositoryDep, _current_user: CurrentUserDep, ) -> BreakPeriodResponse: bp = BreakPeriod( id=uuid4(), workspace_id=payload.workspace_id, name=payload.name, start_date=payload.start_date, end_date=payload.end_date, target_weeks=payload.target_weeks, created_at=datetime.now(), updated_at=datetime.now(), ) created = await repo.create(bp) return _to_response(created) @break_router.put("/{break_period_id}", response_model=BreakPeriodResponse) async def update_break_period( break_period_id: UUID, payload: BreakPeriodUpdate, repo: BreakPeriodRepositoryDep, _current_user: CurrentUserDep, ) -> BreakPeriodResponse: existing = await repo.get_by_id(break_period_id) if existing is None: raise HTTPException(status.HTTP_404_NOT_FOUND, f"BreakPeriod {break_period_id} not found") # BreakPeriodUpdate in models/break_period.py has all fields required (not Optional). # Apply all fields unconditionally. existing.name = payload.name existing.start_date = payload.start_date existing.end_date = payload.end_date existing.target_weeks = payload.target_weeks updated = await repo.update(break_period_id, existing) return _to_response(updated) @break_router.delete("/{break_period_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_break_period( break_period_id: UUID, repo: BreakPeriodRepositoryDep, _current_user: CurrentUserDep, ) -> None: try: await repo.delete(break_period_id) except NotFoundError as exc: raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc