0e9b20ec37
Three strands of work that grew together across the same settings screen and
API client, so they land as one change.
Per-year preschool planning days ("studiedagar"): new table, repository
(SQLite + Postgres), service, router and migration 007, plus the hooks and
Settings UI to plan and record them per child and year. Allocation now
reserves the recorded figure for a year where one exists and only falls back
to the child-level estimate otherwise, marking that fallback as assumed.
Partner invitations: workspaces issue and list invitations, registration
accepts an invite token to join the inviter's workspace instead of creating a
personal one, and a /join/:token page plus the settings invite panel drive it.
Vacation forfeiture: tips and the dashboard now distinguish days a parent
genuinely loses from days that merely age, so the warning stops firing on
balances that were never at risk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""API schemas (Pydantic) for PreschoolPlanningDays.
|
|
|
|
Schemas are the API boundary — they validate request bodies and shape
|
|
responses, kept separate from the domain entity so the wire format can
|
|
evolve independently.
|
|
|
|
`PreschoolPlanningDaysResponse.default_for` builds a response for a (child,
|
|
year) with no persisted row, using `Child.preschool_planning_days_per_year`
|
|
as the planned figure and `taken_days=None` ("not recorded"). `is_default`
|
|
tells the caller (frontend, or later the allocation engine) whether the
|
|
figures are a real record or just the child's fallback default.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.entities.child import Child
|
|
from app.entities.preschool_planning_days import PreschoolPlanningDays
|
|
|
|
|
|
class PreschoolPlanningDaysPlannedUpdate(BaseModel):
|
|
planned_days: int = Field(ge=0, le=30)
|
|
|
|
|
|
class PreschoolPlanningDaysTakenUpdate(BaseModel):
|
|
taken_days: int = Field(ge=0, le=30)
|
|
|
|
|
|
class PreschoolPlanningDaysResponse(BaseModel):
|
|
id: UUID | None
|
|
child_id: UUID
|
|
year: int
|
|
planned_days: int
|
|
taken_days: int | None
|
|
is_default: bool
|
|
created_at: datetime | None
|
|
updated_at: datetime | None
|
|
|
|
@classmethod
|
|
def from_entity(cls, entity: PreschoolPlanningDays) -> "PreschoolPlanningDaysResponse":
|
|
return cls(
|
|
id=entity.id,
|
|
child_id=entity.child_id,
|
|
year=entity.year,
|
|
planned_days=entity.planned_days,
|
|
taken_days=entity.taken_days,
|
|
is_default=False,
|
|
created_at=entity.created_at,
|
|
updated_at=entity.updated_at,
|
|
)
|
|
|
|
@classmethod
|
|
def default_for(cls, child: Child, year: int) -> "PreschoolPlanningDaysResponse":
|
|
"""A (child, year) with no persisted row: fall back to the child's default."""
|
|
return cls(
|
|
id=None,
|
|
child_id=child.id,
|
|
year=year,
|
|
planned_days=child.preschool_planning_days_per_year,
|
|
taken_days=None,
|
|
is_default=True,
|
|
created_at=None,
|
|
updated_at=None,
|
|
)
|