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

GET /api/v1/projects/{id}/lots — Ассортимент проекта/отеля

Назначение

Список объектов конкретного проекта с ценами и статусами — страница /hotels/[id]/assortment и блок «список всех объектов» полной карточки проекта: фильтры по этажу, комнатности, цене, типу объекта и доступности; переход в карточку объекта. Часть полной карточки проекта → гейт GATE-FEED (токен или реф-cookie — README.md); гостю список недоступен (статусы объектов и шахматка скрыты). Источник — витрина catalog-property-object-card по ProjectId.

Границы метода

Ассортимент ограничен одним проектом (сотни–тысячи строк) — offset-пагинация вместо keyset; шахматка (матрица этажей × секций) — контекст chessboard, не этот метод.

Metadata

ПолеЗначение
Сервис/контекстcatalog
API areapublic
Feature groupcards
Статусdraft

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

ПроверкаЗначение
Actor typeUser или RefGuest (GATE-FEED); Guest → 401
Auth policy / Permissionsчтение — без permission; agentCommissioncatalog.commissions.view (MASK-COMMISSION)
Scopeплатформа
Tenant isolationне применяется (платформенная витрина)
Auditnone; фильтры ассортимента — фронт пишет в user-activity

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/projects/{id}/lots
Success status200
Idempotencyне применяется (safe GET)
CorrelationOpenTelemetry trace (traceparent)

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ProjectIdroute {id}GuidДа!= Guid.Empty; проект существует и Published
ActorClassidentity middlewareUser/RefGuestДаGATE-FEED: Guest → 401README.md
BuildingIdqueryGuid?Нетвалидный Guid (фильтр по корпусу)
Floorquery (floorMin/floorMaxFloor.Min/Floor.Max)NullableRange<short>Нетзаданные границы >= -5; well-formed Floor.Min <= Floor.Max; null-граница = открытый интервал
Roomsquery (csv)IReadOnlyList<short>?Неткаждый 0..20; максимум 10 значений
Pricequery (priceMin/priceMaxPrice.Min/Price.Max)NullableRange<decimal>Нетзаданные границы > 0; well-formed Price.Min <= Price.Max; null-граница = открытый интервал
TypeNamequerystring?Нет1..64 (значение из get-catalog-filters.md)
OnlyAvailablequeryboolНетdefault false; true → только PropertyObjectStatus = Free
SortqueryAssortmentSortНетenum Price (default) / Floor / AreaTotal / RoomsCount; иное → ValidationError
DescqueryboolНетdefault false (asc)
PagequeryintНет>= 1, default 1
PageSizequeryintНет1..100, default 30

Enum AssortmentSort (локальный для ассортимента; ключ + Id тем же направлением)

ЗначениеКлюч сортировки
Price (default)Price
FloorFloor
AreaTotalAreaTotal
RoomsCountRoomsCount

Модель ответа GetProjectPropertyObjectsQueryResult

ПолеТипОбязательностьИсточникMaskingОписание
itemsIReadOnlyList<AssortmentPropertyObjectModel>Дапроекция объектов витринысм. itemСтраница объектов проекта.
page / pageSizeintДаechoТекущая страница / размер.
totalCountlongДасчётчик по фильтруВсего объектов под фильтром.
appliedFiltersAppliedAssortmentFiltersModelДаecho запросаEcho для URL-состояния (шаринг).

AssortmentPropertyObjectModel

Состав = PropertyObjectListItemModel (get-property-objects.mdдом модели, дельта ниже). Актор прошёл гейт → MASK-PRICE/MASK-STATUS не применяются; MASK-COMMISSION действует.

Поле (дельта к PropertyObjectListItemModel)ТипОбязательностьИсточникMaskingОписание
floorshortДаCatalogPropertyObjectCard.FloorЭтаж — колонка списка и фильтр.
floorsTotalshortДаFloorsTotalЭтажность корпуса.
areaLivingdecimal?НетAreaLivingЖилая площадь.

AppliedAssortmentFiltersModel

ПолеТипОбязательностьИсточникMaskingОписание
buildingIdGuid?Нетecho
floorNullableRange<short>НетechoПрименённый диапазон этажей (сериализуется в query как floorMin/floorMax).
roomsIReadOnlyList<short>?Нетecho
priceNullableRange<decimal>НетechoПрименённый ценовой диапазон (сериализуется в query как priceMin/priceMax).
typeNamestring?Нетecho
onlyAvailableboolДаecho
sortstring (enum AssortmentSort)Даecho (после default-ов)
descboolДаecho (после default-ов)

Алгоритм

  1. Контекст и доступ. Актор по токену (User; невалидный → 401) либо по реф-cookie referralUrl (RefGuest); иначе 401 (GATE-FEED — часть полной карточки проекта). Права User-а — catalog.commissions.view (для комиссии).
  2. Проверка входа. Диапазоны/enum по таблице входа, well-formed диапазоны Floor и Price, Rooms.Count <= 10, Page >= 1, PageSize ∈ 1..100 — иначе ValidationError (400). Карточка проекта должна существовать и быть опубликованной (Status = Published); иначе CATALOG_PROJECT_NOT_FOUND (404) — ассортимент скрытого проекта не раскрывается. BuildingId, не принадлежащий проекту, даёт пустую выдачу (не ошибка).
  3. Что читаем. Сначала карточка проекта (guard существования), затем страница объектов проекта: отбор Status = Published (INV-1 property-object-card) с фильтрами BuildingId, Floor, Rooms, Price, TypeName, OnlyAvailable (→ PropertyObjectStatus = Free); сортировка — AssortmentSort
    • Id; вместе со страницей — общий счётчик под фильтром.
  4. Сборка ответа. Маппинг — дом PropertyObjectListItemModel (get-property-objects.md) + дельта floor/floorsTotal/areaLiving; agentCommission — MASK-COMMISSION.
  5. Результат. GetProjectPropertyObjectsQueryResult → 200; appliedFilters — URL-состояние. Консистентность — eventual (статусы объектов — лаг консюмера pin.chessboard.property-object-upserted.v1 < 1 мин, SLO ../README.md); решающая проверка статуса при бронировании — crm.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Guest либо невалидный токен (GATE-FEED)«Требуется вход или реферальная ссылка»после входа
ValidationError400Нарушение ограничений входаСписок ошибок полейno retry
CATALOG_PROJECT_NOT_FOUND404Проекта нет либо Status ∈ {Hidden, Delisted}«Проект не найден»no retry
Пустая выдача — не ошибка

Жёсткие фильтры или чужой BuildingId дают 200 с items = [].

Acceptance Criteria

  • Happy path. Дано опубликованный проект с объектами недвижимости на этажах 2–10. Когда User запрашивает с floorMin=2&rooms=1,2&onlyAvailable=true. Тогда 200, items — только Published объекты с Floor >= 2, комнатностью 1/2 и PropertyObjectStatus = Free; totalCount — под фильтром; appliedFilters эхо для шаринга URL.
  • Гейт (гость). Дано Guest (нет токена и реф-cookie). Когда запрашивает ассортимент. Тогда 401 (список объектов и статусы гостю не раскрываются — часть полной карточки проекта).
  • Скрытый проект. Дано проект в Hidden/Delisted. Когда запрашивают ассортимент. Тогда CATALOG_PROJECT_NOT_FOUND (404) — ассортимент скрытого проекта не раскрывается.
  • Чужой BuildingId. Дано BuildingId из другого проекта. Когда фильтруют. Тогда 200 с items = [] (жёсткий фильтр без совпадений — не ошибка).
  • Комиссия по праву. Дано User без catalog.commissions.view. Когда возвращаются объекты. Тогда agentCommission = null (MASK-COMMISSION); с правом — заполнена.
  • Свежесть статуса. Дано объект только что забронирован в crm. Когда ассортимент читается в окне лага (< 1 мин, событие pin.chessboard.property-object-upserted.v1). Тогда возможен показ устаревшего Free; решающую проверку статуса при бронировании выполняет crm в источнике.
  • Невалидная сортировка. Дано sort=Popularity (нет в AssortmentSort). Когда приходит запрос. Тогда ValidationError (400).

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

  • AssortmentPropertyObjectModel эволюционирует вместе с домом PropertyObjectListItemModel (get-property-objects.md); новые optional-поля — additive.
  • Logs: projectId, фильтры, размер выдачи; metrics — catalog_assortment_requests_total{result}, latency histogram; traces/dashboard — общие правила area (README.md).

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

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

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