from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, model_validator from govoplan_poll.backend.schemas import PollResultSummaryResponse SchedulingStatus = Literal["draft", "collecting", "closed", "decided", "handed_off", "cancelled", "archived"] SchedulingParticipantType = Literal["internal", "external", "resource"] SchedulingParticipantStatus = Literal["draft", "invited", "responded", "declined", "removed"] SchedulingResultVisibility = Literal["organizer", "after_response", "after_close", "public"] class SchedulingCalendarPreferences(BaseModel): model_config = ConfigDict(extra="forbid") enabled: bool = False calendar_id: str | None = Field(default=None, max_length=36) freebusy_enabled: bool = False tentative_holds_enabled: bool = False create_event_on_decision: bool = False class SchedulingCandidateSlotInput(BaseModel): model_config = ConfigDict(extra="forbid") label: str | None = Field(default=None, max_length=500) description: str | None = None start_at: datetime end_at: datetime timezone: str | None = Field(default=None, max_length=100) location: str | None = Field(default=None, max_length=500) metadata: dict[str, Any] = Field(default_factory=dict) @model_validator(mode="after") def validate_range(self) -> "SchedulingCandidateSlotInput": if self.end_at <= self.start_at: raise ValueError("end_at must be after start_at") return self class SchedulingParticipantInput(BaseModel): model_config = ConfigDict(extra="forbid") respondent_id: str | None = Field(default=None, max_length=255) display_name: str | None = Field(default=None, max_length=500) email: str | None = Field(default=None, max_length=320) participant_type: SchedulingParticipantType = "external" required: bool = True metadata: dict[str, Any] = Field(default_factory=dict) class SchedulingRequestCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") title: str = Field(min_length=1, max_length=500) description: str | None = None location: str | None = Field(default=None, max_length=500) timezone: str = Field(default="UTC", max_length=100) status: Literal["draft", "collecting"] = "draft" deadline_at: datetime | None = None allow_external_participants: bool = True allow_participant_updates: bool = True result_visibility: SchedulingResultVisibility = "after_close" calendar: SchedulingCalendarPreferences = Field(default_factory=SchedulingCalendarPreferences) slots: list[SchedulingCandidateSlotInput] = Field(default_factory=list, min_length=1) participants: list[SchedulingParticipantInput] = Field(default_factory=list) create_participant_invitations: bool = True metadata: dict[str, Any] = Field(default_factory=dict) class SchedulingRequestUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") title: str | None = Field(default=None, min_length=1, max_length=500) description: str | None = None location: str | None = Field(default=None, max_length=500) deadline_at: datetime | None = None allow_external_participants: bool | None = None allow_participant_updates: bool | None = None result_visibility: SchedulingResultVisibility | None = None calendar: SchedulingCalendarPreferences | None = None metadata: dict[str, Any] | None = None class SchedulingCandidateSlotResponse(BaseModel): id: str poll_option_id: str | None = None label: str description: str | None = None start_at: datetime end_at: datetime timezone: str location: str | None = None position: int freebusy_checked_at: datetime | None = None freebusy_status: str | None = None freebusy_conflicts: list[dict[str, Any]] = Field(default_factory=list) tentative_hold_event_id: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class SchedulingParticipantResponse(BaseModel): id: str respondent_id: str | None = None display_name: str | None = None email: str | None = None participant_type: str required: bool status: str poll_invitation_id: str | None = None invitation_token: str | None = None last_invited_at: datetime | None = None responded_at: datetime | None = None metadata: dict[str, Any] = Field(default_factory=dict) class SchedulingRequestResponse(BaseModel): id: str tenant_id: str title: str description: str | None = None location: str | None = None timezone: str status: str poll_id: str | None = None selected_slot_id: str | None = None organizer_user_id: str | None = None deadline_at: datetime | None = None allow_external_participants: bool allow_participant_updates: bool result_visibility: str calendar_integration_enabled: bool calendar_id: str | None = None calendar_freebusy_enabled: bool calendar_hold_enabled: bool create_calendar_event_on_decision: bool calendar_event_id: str | None = None handed_off_at: datetime | None = None cancelled_at: datetime | None = None created_at: datetime updated_at: datetime metadata: dict[str, Any] = Field(default_factory=dict) slots: list[SchedulingCandidateSlotResponse] = Field(default_factory=list) participants: list[SchedulingParticipantResponse] = Field(default_factory=list) class SchedulingRequestListResponse(BaseModel): requests: list[SchedulingRequestResponse] = Field(default_factory=list) class SchedulingStatusResponse(BaseModel): request: SchedulingRequestResponse class SchedulingDecisionRequest(BaseModel): model_config = ConfigDict(extra="forbid") slot_id: str | None = None poll_option_id: str | None = None handoff_to_calendar: bool | None = None class SchedulingSummaryResponse(BaseModel): request: SchedulingRequestResponse poll_summary: PollResultSummaryResponse class SchedulingCalendarActionResponse(BaseModel): request: SchedulingRequestResponse created_event_ids: list[str] = Field(default_factory=list) updated_slot_ids: list[str] = Field(default_factory=list) warnings: list[str] = Field(default_factory=list) class SchedulingNotificationResponse(BaseModel): id: str request_id: str participant_id: str | None = None event_kind: str channel: str recipient: str | None = None status: str payload: dict[str, Any] = Field(default_factory=dict) error: str | None = None sent_at: datetime | None = None created_at: datetime updated_at: datetime metadata: dict[str, Any] = Field(default_factory=dict) class SchedulingNotificationListResponse(BaseModel): notifications: list[SchedulingNotificationResponse] = Field(default_factory=list) class SchedulingNotificationCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") event_kind: Literal["invitation", "reminder", "decision", "cancellation"] channel: str = Field(default="mail", max_length=40) metadata: dict[str, Any] = Field(default_factory=dict)