GET /api/v1/agent/calendar/slots — Список слотов доступности агента
Назначение
Отдаёт агенту (или консультанту ПИН) его собственные слоты доступности за диапазон дат — во всех статусах,
включая занятые и истёкшие: агент видит, какая доступность опубликована, что уже забронировано и что пора
архивировать. Документ — дом модели AgentSlotModel; выборка занятых слотов ведёт к записи через
ActiveAppointmentId.
| Поле | Значение |
|---|
| Сервис/контекст | calendar |
| API area | agent |
| Feature group | slots |
| Статус | draft |
| Документ | docs/06-services/calendar/api/agent/list-slots.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (агент агентства / консультант ПИН — владелец слотов) |
| Auth policy / Permissions | calendar.slots.manage |
| Scope (RBAC) | организация — компания актора (агентство / ПИН) |
| Record-level | AgentUserId = ActorUserId из токена (sub), не из query — актор видит только собственные слоты (fail-closed); свободные слоты других агентов доступны через анонимную витрину ../public/list-available-slots.md |
| Tenant isolation | TenantId из токена (tenant_id), ADR-0052 |
| Audit | read; Location может содержать адрес — в логи не пишется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/agent/calendar/slots |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | токен (sub) | Guid | Да | Required; не принимается из query | record-level фильтр AgentUserId |
TenantId | токен (tenant_id) | Guid | Да | Required | Tenant isolation |
From | query | DateTimeOffset | Да | UTC; начало диапазона выборки | — |
To | query | DateTimeOffset | Да | UTC; To > From; To − From ≤ 92 дня (квартальное окно) | — |
Status | query | SlotStatus? | Нет | enum-строка Available/Booked/Expired/Archived (availability-slot.md) | — |
Kind | query | EventKind? | Нет | только Showing / Consultation (подмножество EventKind) | — |
BatchId | query | Guid? | Нет | слоты одной публикации (publish-slots.md) | — |
Page / PageSize | query | int | Нет | Page ≥ 1; PageSize 1..500, default 200 | — |
Все фильтры комбинируются по «И». Без фильтра Status возвращаются слоты во всех статусах — скрытие
Expired/Archived — забота клиента. Слот попадает в выборку, если его интервал пересекается с диапазоном:
StartAt < To и EndAt > From.
Модель ответа — страница AgentSlotModel
Ответ — страница с полями Items, Page, PageSize, TotalCount. Сортировка — StartAt по возрастанию,
затем Id (стабильный порядок). Поля — проекция availability-slot.md
поле-в-поле плюс вычисляемый ActiveAppointmentId.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | entity | — | Идентификатор слота. |
Kind | EventKind | Да | entity | — | Вид встречи (Showing / Consultation). |
Status | SlotStatus | Да | entity | — | Текущий статус слота. |
StartAt | DateTimeOffset | Да | entity | — | Начало, UTC. |
EndAt | DateTimeOffset | Да | entity | — | Конец, UTC. |
TimeZone | string | Да | entity | — | IANA-таймзона батча. |
BatchId | Guid | Да | entity | — | Публикация, породившая слот; ключ массовой архивации (archive-slot.md). |
AutoConfirm | bool | Да | entity | — | Записи на слот подтверждаются автоматически. |
CancellationCutoffMinutes | int | Да | entity | — | Cutoff отмены/переноса записи инвестором (INV-APT-3). |
MinLeadTimeMinutes | int | Да | entity | — | Минимальный lead time бронирования (INV-SLT-5). |
ProjectId | Guid? | Нет | entity | — | Проект застройщика (chessboard, логическая ссылка). |
PropertyObjectId | Guid? | Нет | entity | — | Объект недвижимости (chessboard, логическая ссылка). |
Location | string? | Нет | entity | в логи не пишется | Место встречи (свободный текст). |
ActiveAppointmentId | Guid? | Нет | computed | — | Активная запись (Pending/Confirmed) на слоте; заполнен только у Booked-слота (INV-APT-1) — переход к карточке get-appointment.md. |
CreatedAt | DateTimeOffset | Да | entity | — | Публикация слота. |
UpdatedAt | DateTimeOffset | Да | entity | — | Последнее изменение. |
Version | int | Да | entity | — | Версия строки (оптимистическая блокировка). |
Алгоритм
- Контекст и доступ. Актор — владелец слотов; требуется
calendar.slots.manage (иначе
CAL_SLOT_ACCESS_DENIED). AgentUserId и TenantId берутся из токена, не из запроса.
- Проверка входа.
From/To заданы, To > From, диапазон ≤ 92 дней; enum-значения Status/Kind
(если заданы) допустимы (Kind — только Showing/Consultation); paging в границах — иначе
VALIDATION_FAILED.
- Что читаем. Страница слотов актора по пересечению интервала с диапазоном и фильтрам
Status/Kind/BatchId (по «И»); сортировка StartAt возр., затем Id. Ведущий индекс —
ix_availability_slots_agent_start (AgentUserId, StartAt). Для Booked-слотов страницы
подтягивается идентификатор активной записи (ActiveAppointmentId).
- Результат. Страница
AgentSlotModel → HTTP 200. Пустая выборка — 200 с Items = [], не
404. Данные — из source of truth calendar, актуальны на момент чтения.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | From/To отсутствуют; To ≤ From; диапазон > 92 дней; enum Status/Kind вне значений; paging вне границ | «Некорректные параметры выборки» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
CAL_SLOT_ACCESS_DENIED | 403 | нет permission calendar.slots.manage | «Недостаточно прав» | no retry |
Acceptance Criteria
- AC-1 (happy path). Given у агента слоты во всех статусах в диапазоне месяца. When
GET с From/To
этого месяца. Then 200, страница AgentSlotModel, сортировка по StartAt возр.; слоты вне диапазона
отсутствуют.
- AC-2 (занятый слот). Given
Booked-слот с активной записью. When выборка. Then у слота заполнен
ActiveAppointmentId; у Available/Expired/Archived — null.
- AC-3 (фильтр по батчу). Given две публикации с разными
BatchId. When GET с BatchId первой.
Then возвращаются только слоты первой публикации.
- AC-4 (record-level). Given в tenant есть слоты другого агента. When выборка. Then они не попадают в
результат; переданные в query идентификаторы владельца игнорируются (ключи — из токена).
- AC-5 (диапазон обязателен). Given запрос без
From или To. Then 400 VALIDATION_FAILED.
- AC-6 (пустая выборка). Given в диапазоне слотов нет. Then
200, Items = [], TotalCount = 0.
Совместимость и наблюдаемость
- Новые поля
AgentSlotModel — additive nullable (без breaking change); удаление/переименование/смена
типа — breaking. Неизвестное enum-значение в query → VALIDATION_FAILED; новые значения SlotStatus в
ответе клиент обязан переживать (forward-compatible отображение «прочее»).
- Logs:
TenantId, AgentUserId, диапазон, применённые фильтры, TotalCount (без Location); metrics:
calendar_api_request_duration_seconds{route}; traces — сквозной traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы