"""API schemas (Pydantic) for LeaveWish. Schemas validate request bodies and shape responses. Separate from domain entities so the wire format can evolve independently. Business-rule validation (e.g. the parent_mode / parent_personal_data_uuid pairing) lives in the service, not here — these schemas only enforce shape and range. """ from datetime import datetime from uuid import UUID from pydantic import BaseModel, Field from app.entities.leave_wish import LeaveWish class LeaveWishCreate(BaseModel): workspace_id: UUID child_id: UUID | None = None name: str = Field(min_length=1, max_length=255) # i18n message key when `name` came from a preset (e.g. # "wishEditor.presetSommar") — see app/entities/leave_wish.py. Stored # alongside the translated `name` so the frontend can re-translate it; # not validated against a key catalogue here (that's a frontend concern). name_message_key: str | None = Field(default=None, max_length=255) window_start_month: int = Field(ge=1, le=12) window_start_day: int = Field(ge=1, le=31) window_end_month: int = Field(ge=1, le=12) window_end_day: int = Field(ge=1, le=31) target_weeks: int = Field(ge=1, le=52) # A calendar-drawn period is not a whole number of weeks. None (default) # falls back to `target_weeks * 7` — see `LeaveWish.target_span_days()`. target_days: int | None = Field(default=None, ge=1) recurrence: str = "once" # 'yearly' | 'once' first_year: int = Field(ge=2000, le=2200) last_year: int | None = Field(default=None, ge=2000, le=2200) priority: int = Field(default=1, ge=1) parent_mode: str = "either" # 'either' | 'both' | 'specific' parent_personal_data_uuid: UUID | None = None # PLANNING_ENGINE_SPEC.md §10.1 — 'fixed': window_start_*/window_end_* are the # exact placement (default). 'flexible': the same fields describe the # bounding range the allocator searches within for the best-costing window. window_mode: str = "fixed" # 'fixed' | 'flexible' class LeaveWishUpdate(BaseModel): """Partial update — all fields optional; only provided fields are changed.""" child_id: UUID | None = None name: str | None = Field(None, min_length=1, max_length=255) # Present + null in the payload means "clear it"; absent means "leave # unchanged" (see `model_fields_set` handling in the router). The service # also clears this automatically whenever `name` actually changes, so a # caller can never forget to and leave a stale key pointing at old text. name_message_key: str | None = Field(default=None, max_length=255) window_start_month: int | None = Field(None, ge=1, le=12) window_start_day: int | None = Field(None, ge=1, le=31) window_end_month: int | None = Field(None, ge=1, le=12) window_end_day: int | None = Field(None, ge=1, le=31) target_weeks: int | None = Field(None, ge=1, le=52) # Present + null means "clear it" (fall back to target_weeks); absent # means "leave unchanged" — same convention as the other nullable fields # on this model (see `_clear_*` handling in `LeaveWishService.update`). target_days: int | None = Field(default=None, ge=1) recurrence: str | None = None first_year: int | None = Field(None, ge=2000, le=2200) last_year: int | None = Field(None, ge=2000, le=2200) priority: int | None = Field(None, ge=1) parent_mode: str | None = None parent_personal_data_uuid: UUID | None = None window_mode: str | None = None # 'fixed' | 'flexible' class LeaveWishResponse(BaseModel): id: UUID workspace_id: UUID child_id: UUID | None name: str name_message_key: str | None window_start_month: int window_start_day: int window_end_month: int window_end_day: int target_weeks: int target_days: int | None recurrence: str first_year: int last_year: int | None priority: int parent_mode: str parent_personal_data_uuid: UUID | None window_mode: str created_at: datetime | None updated_at: datetime | None @classmethod def from_entity(cls, entity: LeaveWish) -> "LeaveWishResponse": return cls( id=entity.id, workspace_id=entity.workspace_id, child_id=entity.child_id, name=entity.name, name_message_key=entity.name_message_key, window_start_month=entity.window_start_month, window_start_day=entity.window_start_day, window_end_month=entity.window_end_month, window_end_day=entity.window_end_day, target_weeks=entity.target_weeks, target_days=entity.target_days, recurrence=entity.recurrence, first_year=entity.first_year, last_year=entity.last_year, priority=entity.priority, parent_mode=entity.parent_mode, parent_personal_data_uuid=entity.parent_personal_data_uuid, window_mode=entity.window_mode, created_at=entity.created_at, updated_at=entity.updated_at, )