Перейти к основному содержимому

GET /api/public/calendar/slots — Витрина свободных слотов самозаписи

Назначение

Публичная витрина доступности: аноним или инвестор видит свободные слоты агента (или инвест-консультанта ПИН) для записи на показ или консультацию — по конкретному агенту, проекту застройщика или объекту недвижимости. Отдаются только бронируемые слоты (Available и не позже lead-time) с display-именем агента без контактов и прочих персональных данных. Выбранный слот бронируется операцией book-slot.md.

Metadata

ПолеЗначение
Сервис/контекстcalendar
API areapublic
Feature groupslots
Статусdraft
Документdocs/06-services/calendar/api/public/list-available-slots.md

Актор и доступ

ПроверкаЗначение
Actor typeAnonymous или User (Инвестор с JWT — контракт одинаков)
Auth policy / Permissionsnone (публичный метод); rate limit по IP
Scope (RBAC)не применяется (актор — вне tenant)
Tenant isolationне применяется: витрина читает слоты всех компаний (Available — публичное предложение времени, availability-slot.md)
Auditread: фильтры фиксируются в trace; PII в запросе и ответе нет

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/public/calendar/slots
Success status200
Idempotency headerнет (безопасное чтение)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

Хотя бы один из целевых фильтров AgentUserId / ProjectId / PropertyObjectId обязателен — витрина не отдаёт слоты «всей платформы» без цели. Все фильтры комбинируются по «И».

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
AgentUserIdqueryGuid?Условнослоты конкретного агента/консультанта
ProjectIdqueryGuid?Условнослоты показов по проекту застройщика (chessboard, логическая ссылка)
PropertyObjectIdqueryGuid?Условнослоты показов по конкретному объекту
KindqueryEventKind?Нетenum-строка Showing / Consultation (подмножество слотов, availability-slot.md); иное значение → VALIDATION_FAILED
FromqueryDateTimeOffset?НетUTC; default now; значения раньше now поднимаются до now (прошедшие слоты не отдаются никогда)
ToqueryDateTimeOffset?НетUTC; To > From; default From + 14 дней; To − From ≤ 31 день
Page / PageSizequeryintНетPage ≥ 1; PageSize 1..200, default 100

Модель ответа — страница PublicSlotModel

Ответ — страница с полями Items, Page, PageSize, TotalCount; сортировка — StartAt по возрастанию, затем Id (стабильный порядок). Дом модели — этот документ; поля — публичная проекция availability-slot.md без PII: данных клиентов в слоте нет, из данных агента — только display-имя.

ПолеТипОбязательностьИсточникMaskingОписание
SlotIdGuidДаentityИдентификатор слота — вход операции book-slot.md.
AgentUserIdGuidДаentityАгент/консультант — владелец слота.
AgentDisplayNamestring?Нетdisplay-реплика identity (UserDisplayReplicaEntity)без контактовОтображаемое имя агента («Имя Фамилия»); null, если реплика ещё не получила пользователя (клиент показывает обезличенный вариант). Телефон/email/прочие данные агента не отдаются.
KindEventKindДаentityВид встречи: Showing / Consultation.
StartAtDateTimeOffsetДаentityНачало слота, UTC.
EndAtDateTimeOffsetДаentityКонец слота, UTC.
TimeZonestringДаentityIANA-таймзона слота — локальное отображение времени на клиенте.
AutoConfirmboolДаentitytrue — запись подтверждается мгновенно; false — потребуется решение агента.
CancellationCutoffMinutesintДаentityПравило отмены: инвестор сможет отменить/перенести запись не позже чем за столько минут до начала (INV-APT-3).
BookableUntilDateTimeOffsetДаcomputed: StartAt − MinLeadTimeMinutesДо какого момента слот можно забронировать (INV-SLT-5).
ProjectIdGuid?НетentityПроект застройщика (логическая ссылка).
PropertyObjectIdGuid?НетentityОбъект недвижимости (логическая ссылка).
Locationstring?НетentityМесто встречи — свободный текст, заданный агентом для витрины.

Алгоритм

  1. Контекст и доступ. Публичный метод без аутентификации; rate limit по IP — превышение → CAL_RATE_LIMITED.
  2. Проверка входа. Задан хотя бы один из AgentUserId/ProjectId/PropertyObjectId; Kind (если задан) ∈ {Showing, Consultation}; To > From, окно ≤ 31 дня; paging в границах — иначе VALIDATION_FAILED. Существование агента/проекта/объекта синхронно не проверяется: неизвестный id даёт пустую выборку (fail-closed, без раскрытия существования).
  3. Что читаем. Страница слотов: Status = Available, StartAt в окне [From, To) и слот ещё бронируем — now ≤ StartAt − MinLeadTimeMinutes (per-slot значение, INV-SLT-5); наложение фильтров AgentUserId/ProjectId/PropertyObjectId/Kind по «И»; сортировка StartAt возр., затем Id. Ведущий индекс — ix_availability_slots_status_start (Status, StartAt). Display-имена агентов страницы дополняются из display-реплики identity одним заходом.
  4. Результат. Страница PublicSlotModel (Items, Page, PageSize, TotalCount) → HTTP 200; пустая выборка — 200 с Items = [], не 404. Freshness: витрина не резервирует слот — между просмотром и бронированием слот может занять другой клиент (hold'ов нет, ADR-0064 п.3); актуальная проверка выполняется в book-slot.md, проигравший получает SLOT_ALREADY_BOOKED.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400нет ни одного целевого фильтра; To ≤ From; окно > 31 дня; enum Kind вне значений; paging вне границ«Некорректные параметры выборки»no retry
CAL_RATE_LIMITED429превышен rate limit по IP«Слишком много запросов, повторите позже»retry после окна лимита

Совместимость и наблюдаемость

  • Новые поля PublicSlotModel — additive optional (без breaking change); удаление/переименование/смена типа — breaking. Неизвестное enum-значение Kind в query → VALIDATION_FAILED; новые значения EventKind в ответе публичный клиент обязан переживать (forward-compatible отображение «встреча»).
  • Logs: применённые фильтры, окно, TotalCount (PII в логах нет — её нет и в данных); metrics: calendar_api_request_duration_seconds{route}, calendar_public_slots_requests_total{result}; traces — сквозной traceparent. Latency p95 < 300 мс.

Acceptance Criteria

  • AC-1 (по агенту). Дано: у агента 5 слотов Available в ближайшие 2 недели. Когда: GET с AgentUserId без From/To. Тогда: 200, все 5 слотов, сортировка StartAt возр., у каждого — AgentDisplayName без контактов.
  • AC-2 (по проекту). Дано: слоты показов трёх агентов с одним ProjectId. Когда: GET с ProjectId и Kind = Showing. Тогда: слоты всех трёх агентов; консультационные слоты не возвращаются.
  • AC-3 (занятый скрыт). Дано: слот перешёл в Booked. Когда: повторный запрос витрины. Тогда: слот в выборке отсутствует.
  • AC-4 (lead time). Дано: слот с StartAt через 30 минут и MinLeadTimeMinutes = 60. Тогда: слот в выборке отсутствует (уже не бронируем), хотя статус ещё Available.
  • AC-5 (без цели). Дано: запрос без AgentUserId/ProjectId/PropertyObjectId. Тогда: отказ VALIDATION_FAILED (400).
  • AC-6 (широкое окно). Дано: To − From = 60 дней. Тогда: отказ VALIDATION_FAILED (400).
  • AC-7 (неизвестный агент). Дано: AgentUserId, которого нет. Тогда: 200, Items = [] (без раскрытия существования).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы

  • Сущность и статусы слота: availability-slot.md (INV-SLT-5 — lead time, витрина без PII); публикация слотов агентом: publish-slots.md.
  • Бронирование выбранного слота: book-slot.md; display-реплика пользователей — консюмер ../../consumers/identity-user-upserted.md.
  • README контекста; ADR-0064 п.3 — материализованные слоты без hold'ов.