BE-CAL-API-001: Implement single slot creation API
- Add TimeSlotCreate, TimeSlotResponse, TimeSlotCreateResponse schemas - Add SlotConflictItem, SlotTypeEnum, EventTypeEnum, SlotStatusEnum to schemas - Add POST /calendar/slots endpoint with overlap detection and workload warnings - Add _slot_to_response helper for ORM -> schema conversion
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
"""Calendar-related Pydantic schemas.
|
||||
|
||||
BE-CAL-004: MinimumWorkload read/write schemas.
|
||||
BE-CAL-API-001: TimeSlot create / response schemas.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from typing import Optional
|
||||
from datetime import date, time, datetime
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field, model_validator, field_validator
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -61,3 +64,91 @@ class WorkloadWarningItem(BaseModel):
|
||||
minimum_minutes: int = Field(..., ge=0, description="Configured minimum threshold")
|
||||
shortfall_minutes: int = Field(..., ge=0, description="How many minutes below threshold")
|
||||
message: str = Field(..., description="Human-readable warning")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TimeSlot enums (mirror DB enums for schema layer)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class SlotTypeEnum(str, Enum):
|
||||
WORK = "work"
|
||||
ON_CALL = "on_call"
|
||||
ENTERTAINMENT = "entertainment"
|
||||
SYSTEM = "system"
|
||||
|
||||
|
||||
class EventTypeEnum(str, Enum):
|
||||
JOB = "job"
|
||||
ENTERTAINMENT = "entertainment"
|
||||
SYSTEM_EVENT = "system_event"
|
||||
|
||||
|
||||
class SlotStatusEnum(str, Enum):
|
||||
NOT_STARTED = "not_started"
|
||||
ONGOING = "ongoing"
|
||||
DEFERRED = "deferred"
|
||||
SKIPPED = "skipped"
|
||||
PAUSED = "paused"
|
||||
FINISHED = "finished"
|
||||
ABORTED = "aborted"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TimeSlot create / response (BE-CAL-API-001)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TimeSlotCreate(BaseModel):
|
||||
"""Request body for creating a single calendar slot."""
|
||||
date: Optional[date] = Field(None, description="Target date (defaults to today)")
|
||||
slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system")
|
||||
scheduled_at: time = Field(..., description="Planned start time HH:MM (00:00-23:00)")
|
||||
estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)")
|
||||
event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event")
|
||||
event_data: Optional[dict[str, Any]] = Field(None, description="Event details JSON")
|
||||
priority: int = Field(0, ge=0, le=99, description="Priority 0-99")
|
||||
|
||||
@field_validator("scheduled_at")
|
||||
@classmethod
|
||||
def _validate_scheduled_at(cls, v: time) -> time:
|
||||
if v.hour > 23:
|
||||
raise ValueError("scheduled_at hour must be between 00 and 23")
|
||||
return v
|
||||
|
||||
|
||||
class SlotConflictItem(BaseModel):
|
||||
"""Describes a single overlap conflict."""
|
||||
conflicting_slot_id: Optional[int] = None
|
||||
conflicting_virtual_id: Optional[str] = None
|
||||
scheduled_at: str
|
||||
estimated_duration: int
|
||||
slot_type: str
|
||||
message: str
|
||||
|
||||
|
||||
class TimeSlotResponse(BaseModel):
|
||||
"""Response for a single TimeSlot."""
|
||||
id: int
|
||||
user_id: int
|
||||
date: date
|
||||
slot_type: str
|
||||
estimated_duration: int
|
||||
scheduled_at: str # HH:MM:SS ISO format
|
||||
started_at: Optional[str] = None
|
||||
attended: bool
|
||||
actual_duration: Optional[int] = None
|
||||
event_type: Optional[str] = None
|
||||
event_data: Optional[dict[str, Any]] = None
|
||||
priority: int
|
||||
status: str
|
||||
plan_id: Optional[int] = None
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TimeSlotCreateResponse(BaseModel):
|
||||
"""Response after creating a slot — includes the slot and any warnings."""
|
||||
slot: TimeSlotResponse
|
||||
warnings: list[WorkloadWarningItem] = Field(default_factory=list)
|
||||
|
||||
Reference in New Issue
Block a user