feat: persist calendar free windows with Week Ahead (Closes #1258)
This commit is contained in:
1 parent
15739f7370
commit
fc93620118
10 files changed
+178
-7
No files matched your search
+7
-1
@@ -727,11 +727,17 @@ class TomorrowPromotion(BaseModel):
|
||||
today_revision: int = Field(ge=0)
|
||||
|
||||
|
||||
class WeekFreeWindow(BaseModel):
|
||||
start_time: str = Field(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$")
|
||||
end_time: str = Field(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$")
|
||||
|
||||
|
||||
class WeekPlanDay(BaseModel):
|
||||
plan_date: str = Field(min_length=10, max_length=10)
|
||||
ids: list[str] = Field(max_length=5)
|
||||
capacity_minutes: int | None = Field(default=None, ge=15, le=1440)
|
||||
estimates: dict[str, int] = Field(default_factory=dict, max_length=5)
|
||||
free_windows: list[WeekFreeWindow] | None = Field(default=None, max_length=16)
|
||||
|
||||
|
||||
class WeekPlanUpdate(BaseModel):
|
||||
@@ -2829,7 +2835,7 @@ async def replace_week_plan(payload: WeekPlanUpdate):
|
||||
return await asyncio.to_thread(
|
||||
_today_store().replace_week, login,
|
||||
base_revision=payload.base_revision,
|
||||
days=[day.model_dump() for day in payload.days],
|
||||
days=[day.model_dump(exclude_none=True) for day in payload.days],
|
||||
timezone=payload.timezone,
|
||||
availability_defaults=payload.availability_defaults,
|
||||
)
|
||||
|
||||
@@ -551,6 +551,33 @@ class TodayStore:
|
||||
ids=day.get("ids", []), capacity_minutes=day.get("capacity_minutes"),
|
||||
estimates=day.get("estimates", {}), plan_date=plan_date, timezone=timezone,
|
||||
)
|
||||
if "free_windows" in day:
|
||||
windows = day["free_windows"]
|
||||
if not isinstance(windows, list) or len(windows) > 16:
|
||||
raise ValueError("free windows must be a bounded list")
|
||||
normalized_windows = []
|
||||
previous_end = -1
|
||||
for window in windows:
|
||||
if not isinstance(window, dict) or set(window) != {"start_time", "end_time"}:
|
||||
raise ValueError("free windows must contain only start and end times")
|
||||
clock_values = []
|
||||
for key in ("start_time", "end_time"):
|
||||
value = window[key]
|
||||
if (
|
||||
not isinstance(value, str) or len(value) != 5 or value[2] != ":"
|
||||
or not value[:2].isdigit() or not value[3:].isdigit()
|
||||
or int(value[:2]) > 23 or int(value[3:]) > 59
|
||||
):
|
||||
raise ValueError("free windows must use HH:mm times")
|
||||
clock_values.append(int(value[:2]) * 60 + int(value[3:]))
|
||||
start, end = clock_values
|
||||
if start < previous_end or end <= start:
|
||||
raise ValueError("free windows must be ordered and non-overlapping")
|
||||
normalized_windows.append({
|
||||
"start_time": window["start_time"], "end_time": window["end_time"],
|
||||
})
|
||||
previous_end = end
|
||||
plan["free_windows"] = normalized_windows
|
||||
duplicate_ids = seen_ids.intersection(plan["ids"])
|
||||
if duplicate_ids:
|
||||
raise ValueError("work must be assigned to only one Week Ahead date")
|
||||
|
||||
Reference in new issue
Block a user