diff --git a/app/api/routers/calendar.py b/app/api/routers/calendar.py index c7d8eb4..3f0b0f1 100644 --- a/app/api/routers/calendar.py +++ b/app/api/routers/calendar.py @@ -7,6 +7,7 @@ BE-CAL-API-003: Calendar slot edit endpoints (real + virtual). BE-CAL-API-004: Calendar slot cancel endpoints (real + virtual). BE-CAL-API-005: Plan schedule / plan list endpoints. BE-CAL-API-006: Plan edit / plan cancel endpoints. +BE-CAL-API-007: Date-list endpoint. """ from datetime import date as date_type @@ -22,6 +23,7 @@ from app.models.models import User from app.schemas.calendar import ( CalendarDayResponse, CalendarSlotItem, + DateListResponse, MinimumWorkloadConfig, MinimumWorkloadResponse, MinimumWorkloadUpdate, @@ -841,6 +843,49 @@ def cancel_plan( ) +# --------------------------------------------------------------------------- +# Date list (BE-CAL-API-007) +# --------------------------------------------------------------------------- + +# Statuses considered inactive — slots with these statuses are excluded +# from the date-list result because they no longer occupy calendar time. +_DATE_LIST_EXCLUDED_STATUSES = {SlotStatus.SKIPPED.value, SlotStatus.ABORTED.value} + + +@router.get( + "/dates", + response_model=DateListResponse, + summary="List future dates that have materialized slots", +) +def list_dates( + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user), +): + """Return a sorted list of future dates that have at least one + materialized (real) slot. + + - Only dates **today or later** are included. + - Only **active** slots are counted (skipped / aborted are excluded). + - Pure plan-generated (virtual) dates that have not been materialized + are **not** included. + """ + today = date_type.today() + + rows = ( + db.query(TimeSlot.date) + .filter( + TimeSlot.user_id == current_user.id, + TimeSlot.date >= today, + TimeSlot.status.notin_(list(_DATE_LIST_EXCLUDED_STATUSES)), + ) + .group_by(TimeSlot.date) + .order_by(TimeSlot.date.asc()) + .all() + ) + + return DateListResponse(dates=[r[0] for r in rows]) + + # --------------------------------------------------------------------------- # MinimumWorkload # --------------------------------------------------------------------------- diff --git a/app/schemas/calendar.py b/app/schemas/calendar.py index 751ac2a..7bd166b 100644 --- a/app/schemas/calendar.py +++ b/app/schemas/calendar.py @@ -391,3 +391,19 @@ class SchedulePlanCancelResponse(BaseModel): default_factory=list, description="IDs of past materialized slots that were NOT affected", ) + + +# --------------------------------------------------------------------------- +# Calendar date-list (BE-CAL-API-007) +# --------------------------------------------------------------------------- + +class DateListResponse(BaseModel): + """Response for the date-list endpoint. + + Returns only dates that have at least one materialized (real) future + slot. Pure plan-generated (virtual) dates are excluded. + """ + dates: list[date] = Field( + default_factory=list, + description="Sorted list of future dates with materialized slots", + )