BE-CAL-API-003: implement Calendar edit API for real and virtual slots

- Add TimeSlotEdit schema (partial update, all fields optional)
- Add TimeSlotEditResponse schema
- Add PATCH /calendar/slots/{slot_id} for editing real slots
- Add PATCH /calendar/slots/virtual/{virtual_id} for editing virtual slots
  - Triggers materialization before applying edits
  - Detaches from plan after edit
- Both endpoints enforce past-slot immutability, overlap detection, plan
  detachment, and workload warnings
This commit is contained in:
zhi
2026-03-31 10:46:09 +00:00
parent c75ded02c8
commit f7f9ba3aa7
2 changed files with 244 additions and 2 deletions

View File

@@ -3,6 +3,7 @@
BE-CAL-004: MinimumWorkload read/write schemas.
BE-CAL-API-001: TimeSlot create / response schemas.
BE-CAL-API-002: Calendar day-view query schemas.
BE-CAL-API-003: TimeSlot edit schemas.
"""
from __future__ import annotations
@@ -155,6 +156,49 @@ class TimeSlotCreateResponse(BaseModel):
warnings: list[WorkloadWarningItem] = Field(default_factory=list)
# ---------------------------------------------------------------------------
# TimeSlot edit (BE-CAL-API-003)
# ---------------------------------------------------------------------------
class TimeSlotEdit(BaseModel):
"""Request body for editing a calendar slot.
All fields are optional — only provided fields are updated.
The caller must supply either ``slot_id`` (for real slots) or
``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")
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")
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]:
if v is not None and v.hour > 23:
raise ValueError("scheduled_at hour must be between 00 and 23")
return v
@model_validator(mode="after")
def _at_least_one_field(self) -> "TimeSlotEdit":
"""Ensure at least one editable field is provided."""
if all(
getattr(self, f) is None
for f in ("slot_type", "scheduled_at", "estimated_duration",
"event_type", "event_data", "priority")
):
raise ValueError("At least one field must be provided for edit")
return self
class TimeSlotEditResponse(BaseModel):
"""Response after editing a slot — includes the updated slot and any warnings."""
slot: TimeSlotResponse
warnings: list[WorkloadWarningItem] = Field(default_factory=list)
# ---------------------------------------------------------------------------
# Calendar day-view query (BE-CAL-API-002)
# ---------------------------------------------------------------------------