from __future__ import annotations from datetime import time from typing import Sequence from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from models.calendars import CalendarSlot class SlotError(ValueError): pass def _b(v): if isinstance(v, bool): return v s = str(v).lower() return s in {"1","true","t","yes","y","on"} async def list_slots(sess: AsyncSession, calendar_id: int) -> Sequence[CalendarSlot]: res = await sess.execute( select(CalendarSlot) .where(CalendarSlot.calendar_id == calendar_id, CalendarSlot.deleted_at.is_(None)) .order_by(CalendarSlot.time_start.asc(), CalendarSlot.id.asc()) ) return res.scalars().all() async def create_slot(sess: AsyncSession, calendar_id: int, *, name: str, description: str | None, days: dict, time_start: time, time_end: time, cost: float | None): if not name: raise SlotError("name is required") if not time_start or not time_end or time_end <= time_start: raise SlotError("time range invalid") slot = CalendarSlot( calendar_id=calendar_id, name=name, description=(description or None), mon=_b(days.get("mon")), tue=_b(days.get("tue")), wed=_b(days.get("wed")), thu=_b(days.get("thu")), fri=_b(days.get("fri")), sat=_b(days.get("sat")), sun=_b(days.get("sun")), time_start=time_start, time_end=time_end, cost=cost, ) sess.add(slot) await sess.flush() return slot async def update_slot( sess: AsyncSession, slot_id: int, *, name: str | None = None, description: str | None = None, days: dict | None = None, time_start: time | None = None, time_end: time | None = None, cost: float | None = None, flexible: bool | None = None, # NEW ): slot = await sess.get(CalendarSlot, slot_id) if not slot or slot.deleted_at is not None: raise SlotError("slot not found") if name is not None: slot.name = name if description is not None: slot.description = description or None if days is not None: slot.mon = _b(days.get("mon", slot.mon)) slot.tue = _b(days.get("tue", slot.tue)) slot.wed = _b(days.get("wed", slot.wed)) slot.thu = _b(days.get("thu", slot.thu)) slot.fri = _b(days.get("fri", slot.fri)) slot.sat = _b(days.get("sat", slot.sat)) slot.sun = _b(days.get("sun", slot.sun)) if time_start is not None: slot.time_start = time_start if time_end is not None: slot.time_end = time_end if (time_start or time_end) and slot.time_end <= slot.time_start: raise SlotError("time range invalid") if cost is not None: slot.cost = cost # NEW: update flexible flag only if explicitly provided if flexible is not None: slot.flexible = flexible await sess.flush() return slot async def soft_delete_slot(sess: AsyncSession, slot_id: int): slot = await sess.get(CalendarSlot, slot_id) if not slot or slot.deleted_at is not None: return from datetime import datetime, timezone slot.deleted_at = datetime.now(timezone.utc) await sess.flush() async def get_slot(sess: AsyncSession, slot_id: int) -> CalendarSlot | None: return await sess.get(CalendarSlot, slot_id) async def update_slot_description( sess: AsyncSession, slot_id: int, description: str | None, ) -> CalendarSlot: slot = await sess.get(CalendarSlot, slot_id) if not slot: raise SlotError("slot not found") slot.description = description or None await sess.flush() return slot