205 lines
7.0 KiB
Python
205 lines
7.0 KiB
Python
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)
|