"""Business logic for per-year preschool planning ("studiedagar") days. The service owns the "no row yet" fallback: `child.preschool_planning_days_per_year` seeds `planned_days` the first time a year is touched (either a planned-days override or a taken-days record), so a caller never has to know whether a row already exists. NOTE for the allocation engine (`app/services/allocation_service.py`, `_preschool_reserve_days`): this service/table is not wired into it yet. That currently still multiplies `child.preschool_planning_days_per_year` by the number of years in the horizon. A follow-up should make it consume `PreschoolPlanningDaysRepository.list(child_id=...)` per year instead, falling back to the child default for years with no row (see `PreschoolPlanningDaysResponse.default_for`). """ from datetime import datetime from uuid import UUID, uuid4 from app.entities.child import Child from app.entities.preschool_planning_days import PreschoolPlanningDays from app.repositories.preschool_planning_days_repository import ( PreschoolPlanningDaysRepository, ) from app.utils.errors import NotFoundError, ValidationError class PreschoolPlanningDaysService: def __init__(self, repo: PreschoolPlanningDaysRepository) -> None: self._repo = repo async def upsert_planned( self, child: Child, year: int, planned_days: int ) -> PreschoolPlanningDays: """Create or update the planned-days figure for (child, year). Preserves any already-recorded `taken_days`. """ if planned_days < 0: raise ValidationError("Planned days cannot be negative") existing = await self._repo.get_by_child_year(child.id, year) if existing is not None: existing.planned_days = planned_days return await self._repo.update(existing.id, existing) entity = PreschoolPlanningDays( id=uuid4(), child_id=child.id, year=year, planned_days=planned_days, taken_days=None, created_at=datetime.now(), updated_at=datetime.now(), ) return await self._repo.create(entity) async def record_taken(self, child: Child, year: int, taken_days: int) -> PreschoolPlanningDays: """Record how many planning days were actually taken for (child, year). Seeds `planned_days` from `child.preschool_planning_days_per_year` if no row exists yet. """ if taken_days < 0: raise ValidationError("Taken days cannot be negative") existing = await self._repo.get_by_child_year(child.id, year) if existing is not None: existing.taken_days = taken_days return await self._repo.update(existing.id, existing) entity = PreschoolPlanningDays( id=uuid4(), child_id=child.id, year=year, planned_days=child.preschool_planning_days_per_year, taken_days=taken_days, created_at=datetime.now(), updated_at=datetime.now(), ) return await self._repo.create(entity) async def get_by_child_year(self, child_id: UUID, year: int) -> PreschoolPlanningDays | None: return await self._repo.get_by_child_year(child_id, year) async def get(self, entry_id: UUID, child_id: UUID | None = None) -> PreschoolPlanningDays: entity = await self._repo.get_by_id(entry_id) if entity is None or (child_id is not None and entity.child_id != child_id): raise NotFoundError(f"PreschoolPlanningDays {entry_id} not found") return entity async def list_for_child(self, child_id: UUID) -> list[PreschoolPlanningDays]: return await self._repo.list(child_id=child_id)