HarborForge.Backend: dev-2026-03-29 -> main #13

Merged
hzhang merged 43 commits from dev-2026-03-29 into main 2026-04-05 22:08:15 +00:00
Showing only changes of commit 45ab4583de - Show all commits

View File

@@ -9,10 +9,10 @@ BE-CAL-API-004: TimeSlot cancel schemas.
from __future__ import annotations
from datetime import date, time, datetime
from datetime import date as dt_date, time as dt_time, datetime as dt_datetime
from enum import Enum
from pydantic import BaseModel, Field, model_validator, field_validator
from typing import Any, Optional
from typing import Optional
# ---------------------------------------------------------------------------
@@ -102,17 +102,17 @@ class SlotStatusEnum(str, Enum):
class TimeSlotCreate(BaseModel):
"""Request body for creating a single calendar slot."""
date: Optional[date] = Field(None, description="Target date (defaults to today)")
date: Optional[dt_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)")
scheduled_at: dt_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")
event_data: Optional[dict] = 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:
def _validate_scheduled_at(cls, v: dt_time) -> dt_time:
if v.hour > 23:
raise ValueError("scheduled_at hour must be between 00 and 23")
return v
@@ -132,7 +132,7 @@ class TimeSlotResponse(BaseModel):
"""Response for a single TimeSlot."""
id: int
user_id: int
date: date
date: dt_date
slot_type: str
estimated_duration: int
scheduled_at: str # HH:MM:SS ISO format
@@ -140,12 +140,12 @@ class TimeSlotResponse(BaseModel):
attended: bool
actual_duration: Optional[int] = None
event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None
event_data: Optional[dict] = None
priority: int
status: str
plan_id: Optional[int] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: Optional[dt_datetime] = None
updated_at: Optional[dt_datetime] = None
class Config:
from_attributes = True
@@ -169,15 +169,15 @@ class TimeSlotEdit(BaseModel):
``virtual_id`` (for plan-generated virtual slots) in the URL path.
"""
slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type")
scheduled_at: Optional[time] = Field(None, description="New start time HH:MM")
scheduled_at: Optional[dt_time] = Field(None, description="New start time HH:MM")
estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)")
event_type: Optional[EventTypeEnum] = Field(None, description="New event type")
event_data: Optional[dict[str, Any]] = Field(None, description="New event details JSON")
event_data: Optional[dict] = Field(None, description="New event details JSON")
priority: Optional[int] = Field(None, ge=0, le=99, description="New priority 0-99")
@field_validator("scheduled_at")
@classmethod
def _validate_scheduled_at(cls, v: Optional[time]) -> Optional[time]:
def _validate_scheduled_at(cls, v: Optional[dt_time]) -> Optional[dt_time]:
if v is not None and v.hour > 23:
raise ValueError("scheduled_at hour must be between 00 and 23")
return v
@@ -214,7 +214,7 @@ class CalendarSlotItem(BaseModel):
id: Optional[int] = Field(None, description="Real slot DB id (None for virtual)")
virtual_id: Optional[str] = Field(None, description="Virtual slot id (None for real)")
user_id: int
date: date
date: dt_date
slot_type: str
estimated_duration: int
scheduled_at: str # HH:MM:SS ISO format
@@ -222,12 +222,12 @@ class CalendarSlotItem(BaseModel):
attended: bool
actual_duration: Optional[int] = None
event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None
event_data: Optional[dict] = None
priority: int
status: str
plan_id: Optional[int] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: Optional[dt_datetime] = None
updated_at: Optional[dt_datetime] = None
class Config:
from_attributes = True
@@ -235,7 +235,7 @@ class CalendarSlotItem(BaseModel):
class CalendarDayResponse(BaseModel):
"""Response for a single-day calendar query."""
date: date
date: dt_date
user_id: int
slots: list[CalendarSlotItem] = Field(
default_factory=list,
@@ -290,16 +290,16 @@ class SchedulePlanCreate(BaseModel):
"""Request body for creating a recurring schedule plan."""
slot_type: SlotTypeEnum = Field(..., description="work | on_call | entertainment | system")
estimated_duration: int = Field(..., ge=1, le=50, description="Duration in minutes (1-50)")
at_time: time = Field(..., description="Daily scheduled time (HH:MM)")
at_time: dt_time = Field(..., description="Daily scheduled time (HH:MM)")
on_day: Optional[DayOfWeekEnum] = Field(None, description="Day of week (sun-sat)")
on_week: Optional[int] = Field(None, ge=1, le=4, description="Week of month (1-4)")
on_month: Optional[MonthOfYearEnum] = Field(None, description="Month (jan-dec)")
event_type: Optional[EventTypeEnum] = Field(None, description="job | entertainment | system_event")
event_data: Optional[dict[str, Any]] = Field(None, description="Event details JSON")
event_data: Optional[dict] = Field(None, description="Event details JSON")
@field_validator("at_time")
@classmethod
def _validate_at_time(cls, v: time) -> time:
def _validate_at_time(cls, v: dt_time) -> dt_time:
if v.hour > 23:
raise ValueError("at_time hour must be between 00 and 23")
return v
@@ -325,10 +325,10 @@ class SchedulePlanResponse(BaseModel):
on_week: Optional[int] = None
on_month: Optional[str] = None
event_type: Optional[str] = None
event_data: Optional[dict[str, Any]] = None
event_data: Optional[dict] = None
is_active: bool
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: Optional[dt_datetime] = None
updated_at: Optional[dt_datetime] = None
class Config:
from_attributes = True
@@ -352,19 +352,19 @@ class SchedulePlanEdit(BaseModel):
"""
slot_type: Optional[SlotTypeEnum] = Field(None, description="New slot type")
estimated_duration: Optional[int] = Field(None, ge=1, le=50, description="New duration in minutes (1-50)")
at_time: Optional[time] = Field(None, description="New daily time (HH:MM)")
at_time: Optional[dt_time] = Field(None, description="New daily time (HH:MM)")
on_day: Optional[DayOfWeekEnum] = Field(None, description="New day of week (sun-sat), use 'clear' param to remove")
on_week: Optional[int] = Field(None, ge=1, le=4, description="New week of month (1-4), use 'clear' param to remove")
on_month: Optional[MonthOfYearEnum] = Field(None, description="New month (jan-dec), use 'clear' param to remove")
event_type: Optional[EventTypeEnum] = Field(None, description="New event type")
event_data: Optional[dict[str, Any]] = Field(None, description="New event details JSON")
event_data: Optional[dict] = Field(None, description="New event details JSON")
clear_on_day: bool = Field(False, description="Clear on_day (set to NULL)")
clear_on_week: bool = Field(False, description="Clear on_week (set to NULL)")
clear_on_month: bool = Field(False, description="Clear on_month (set to NULL)")
@field_validator("at_time")
@classmethod
def _validate_at_time(cls, v: Optional[time]) -> Optional[time]:
def _validate_at_time(cls, v: Optional[dt_time]) -> Optional[dt_time]:
if v is not None and v.hour > 23:
raise ValueError("at_time hour must be between 00 and 23")
return v
@@ -403,7 +403,7 @@ class DateListResponse(BaseModel):
Returns only dates that have at least one materialized (real) future
slot. Pure plan-generated (virtual) dates are excluded.
"""
dates: list[date] = Field(
dates: list[dt_date] = Field(
default_factory=list,
description="Sorted list of future dates with materialized slots",
)