BE-CAL-API-007: implement date-list API endpoint

- Add GET /calendar/dates endpoint that returns sorted future dates
  with at least one materialized (real) slot
- Excludes skipped/aborted slots and pure plan-generated virtual dates
- Add DateListResponse schema
This commit is contained in:
zhi
2026-03-31 20:46:34 +00:00
parent 78d836c71e
commit 22a0097a5d
2 changed files with 61 additions and 0 deletions

View File

@@ -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
# ---------------------------------------------------------------------------

View File

@@ -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",
)