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

GET /api/v1/public/property-objects — Список объектов недвижимости проекта/здания с фильтрами

Назначение

Табличный список объектов проекта или конкретного здания с фильтрами по этажу, комнатности, цене, площади, планировке, видам из окон и статусу — постраничная выдача с сортировкой. Обслуживает вывод полного списка объектов проекта с ценами и статусами, а также применение фильтров внутри проекта.

Анониму список доступен в ограниченном виде: цены, цена/м², статусы и таймеры скрыты, а фильтры/сортировки по скрытым полям отклоняются (нельзя восстановить цену бинарным поиском по фильтру). Зеркалируется в agents area (GET /api/v1/agents/property-objects) той же операцией; комиссия агента в списке — обогащение витрины catalog/rewards, не этот контекст.

Metadata

ПолеЗначение
Сервис/контекстchessboard
API areapublic (зеркало: agents)
Feature grouppropertyObjects
Статусdraft
OwnerPIN Platform Core
Документdocs/06-services/chessboard/api/public/property-objects/list-property-objects.md

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

ПроверкаЗначение
Actor typeAnonymous (ограниченная проекция, урезанные фильтры) / User — инвестор, агент, менеджер застройщика (полная)
Auth policy / Permissionsnone — public endpoint; masking и допустимость фильтров по признаку авторизации
Scope (RBAC)не применяется
Record-levelне применяется
Tenant isolationBypass tenant-фильтра по публичному срезу: PropertyObject.Status != Hidden, здание/проект/застройщик Published
Auditnone (read)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/public/property-objects
Success status200
Idempotency headerне применяется (GET)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ProjectIdqueryGuidДаRequired, != Guid.Empty; published-проект (шаг 3)
BuildingIdqueryGuid?Нетpublished-здание проекта (шаг 3); null — все здания проекта
Floorquery (floorMin/floorMax)NullableRange<short>Нетграницы -5..200; From <= To при обоих заданных
Roomsquery (repeat: rooms=1&rooms=2)IReadOnlyList<short>?Неткаждый 0..20 (0 = студия); ≤ 10 элементов, без дублей
Pricequery (priceMin/priceMax)NullableRange<decimal>Нетграницы >= 0; From <= To при обоих заданныхтолько авторизованным (иначе 403 — фильтр по скрытому полю)
AreaTotalquery (areaTotalMin/areaTotalMax)NullableRange<decimal>Нетграницы > 0; From <= To при обоих заданных
Statusesquery (repeat)IReadOnlyList<PropertyObjectStatus>?Нетзначения из Free/Reserved/Booked/Sold (Hidden отклоняется всегда — 400); ≤ 4, без дублейтолько авторизованным (иначе 403)
LayoutIdqueryGuid?Нетпланировка этого проекта (шаг 3)
LayoutGroupIdqueryGuid?Нетгруппа планировок этого проекта; взаимоисключим с LayoutId (оба заданы → 400)
WindowViewsquery (repeat)IReadOnlyList<WindowViewKind>?Нетзначения enum (reference-data.md); ≤ 7, без дублей; семантика — «хотя бы один из»
PagequeryintНет (default 1)>= 1
PageSizequeryintНет (default 20)1..100 (лента 20–30)
OrderByqueryPropertyObjectOrderField (string)Нет (default см. ниже)enum нижеPrice/PricePerMeterтолько авторизованным (иначе 403)
OrderDirectionqueryOrderDirection (string)Нет (default Asc)Asc / Desc
IsAuthenticatedidentity claimsboolДа (вычисляется)из контекста авторизацииопределяет masking и допустимость фильтров

Enum PropertyObjectOrderField (поля сортировки)

ЗначениеКолонкаDefaultДоступно анониму
Priceproperty_objects.price (index ix_property_objects_price)default для авторизованных (Asc)Нет (403)
PricePerMeterproperty_objects.price_per_meterНет (403)
TotalAreaproperty_objects.total_areaДа
Floorproperty_objects.floordefault для анонима (Asc)Да
Roomsproperty_objects.rooms (index ix_property_objects_rooms)Да
Numberproperty_objects.numberДа
CreatedAtproperty_objects.created_at (index ix_property_objects_created_at)Да

Тай-брейк любой сортировки — Number asc, Id asc (стабильная пагинация).

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

Постраничный контейнер: Items, Page, PageSize, TotalCount. Masking-колонка — правило для анонима.

ПолеТипОбязательностьИсточникMasking (аноним)Описание
ItemsIReadOnlyList<PropertyObjectListItemModel>Да (может быть пустым)страница выборкипострочно (ниже)Элементы страницы.
PageintДавходТекущая страница.
PageSizeintДавходРазмер страницы.
TotalCountlongДачисло объектов под фильтромВсего объектов недвижимости под фильтром.

Вложенная модель PropertyObjectListItemModel (проекция property-object.md)

ПолеТипОбязательностьИсточникMasking (аноним)Описание
IdGuidДаPropertyObject.IdПереход в карточку (get-property-object.md).
NumberstringДаPropertyObject.NumberНомер объекта.
FloorshortДаPropertyObject.FloorЭтаж.
Sectionshort?НетPropertyObject.SectionСекция.
RoomsshortДаPropertyObject.RoomsКомнатность.
TotalAreadecimalДаPropertyObject.TotalAreaОбщая площадь.
LivingAreadecimal?НетPropertyObject.LivingAreaЖилая площадь.
Pricedecimal?НетPropertyObject.PricenullЦена.
CurrencyIdshortДаPropertyObject.CurrencyIdСсылка на валюту справочника (RUB-only, ADR-0048); display-код/знак — публичный lookup handbook.
PricePerMeterdecimal?НетPropertyObject.PricePerMeternullЦена за м².
StatusPropertyObjectStatus (string)?НетPropertyObject.StatusnullСтатус продажи.
ReservedUntilDateTimeOffset?НетPropertyObject.ReservedUntilnullТаймер резерва.
BookedUntilDateTimeOffset?НетPropertyObject.BookedUntilnullТаймер брони.
WindowViewsIReadOnlyList<WindowViewKind> (string)Да (может быть пустым)PropertyObject.WindowViews.ViewsВиды из окон.
Capacityshort?НетPropertyObject.CapacityВместимость (отели).
LayoutPropertyObjectLayoutModel?Нетбатч-загрузка LayoutEntityПланировка объекта (id + название; ниже). null — без планировки.
BuildingBuildingRefModelДабатч-загрузка BuildingEntityЗдание (id, название, срок сдачи; ниже).
ProjectIdGuidДаPropertyObject.ProjectIdПроект (переход в карточку проекта).

Вложенная модель PropertyObjectLayoutModel (планировка объекта, батч LayoutEntity)

ПолеТипОбязательностьИсточникОписание
LayoutIdGuidДаLayout.IdИдентификатор планировки.
LayoutNamestringДаLayout.NameНазвание планировки («1К-А 32.5»); батч-загрузка, без N+1.

Вложенная модель BuildingRefModel (ссылка на здание, батч BuildingEntity)

ПолеТипОбязательностьИсточникОписание
BuildingIdGuidДаBuilding.IdИдентификатор здания.
BuildingNamestringДаBuilding.Name«Литер 3»; батч-загрузка, без N+1.
DeliveryYearshort?НетBuilding.DeliveryYearСрок сдачи (колонка списка).
DeliveryQuartershort?НетBuilding.DeliveryQuarterКвартал сдачи.

Предусловия и постусловия

Предусловия:

  • Вызов анонимом (ограниченная проекция, урезанные фильтры) или авторизованным пользователем (полная); признак авторизации определяет masking и допустимость фильтров.
  • Публичный срез: PropertyObject.Status != Hidden, здание/проект/застройщик Published (INV-DEV-6).
  • ProjectId задан; LayoutId и LayoutGroupId не заданы одновременно.

Постусловия (read — состояние не меняется):

  • Возвращена страница объектов с точным TotalCount под фильтром на момент запроса. Для анонима в каждом элементе Price/PricePerMeter/Status/ReservedUntil/BookedUntil = null; фильтры/сортировки по скрытым полям отклонены (403), чтобы цену/статус нельзя было восстановить перебором.
  • Пустая выдача (в т.ч. по несуществующему LayoutId/пустой группе) — валидный 200 c Items = [].
  • Freshness: прямое чтение source of truth; строки обогащаются зданиями и планировками страницы батч-загрузкой (без N+1).

Алгоритм

Постраничный список объектов проекта/здания по published-срезу: фильтры и сортировка, с masking цены/статуса и запретом фильтров по скрытым полям для анонима.

  1. Контекст и доступ. Вызывает аноним (ограниченная проекция, урезанные фильтры) или авторизованный пользователь (полная); признак авторизации определяет masking и допустимость фильтров. Чтение — по публичному срезу.
  2. Проверка входа.
    • Синтаксис/диапазоны: ProjectId задан; границы Floor/Price/AreaTotal согласованы (From <= To); массивы без дублей и в лимитах; Statuses не содержит Hidden; LayoutId и LayoutGroupId не заданы одновременно; Page/PageSize в границах. Нарушение → VALIDATION_ERROR.
    • Права на фильтры: при анониме и заданном Price/Statuses либо сортировке по Price/PricePerMeterCHESSBOARD_FILTER_REQUIRES_AUTH (фильтр/сортировка по скрытым полям позволили бы восстановить цену/статус).
    • Существование и публичность — шаг 3.
  3. Что читаем.
    • Проект по id со статусом Published и не удалённый; не найден → CHESSBOARD_PROJECT_NOT_FOUND.
    • Застройщик проекта; статус не Published (INV-DEV-6) → CHESSBOARD_PROJECT_NOT_FOUND.
    • Область зданий: заданный BuildingId — published-здание этого проекта (иначе → CHESSBOARD_BUILDING_NOT_FOUND); иначе — все published-здания проекта (объекты hidden-зданий в выдачу не попадают).
    • Если задан LayoutGroupId — планировки этой группы в проекте; пустой список → пустая выдача (валидное «ничего не найдено»). Заданный LayoutId отдельно не проверяется (несуществующий id даёт пустую выдачу, без утечки).
    • Основная выборка объектов проекта в области published-зданий (не удалённых) с применением фильтров: этаж, комнатность, цена, площадь, планировка/группа планировок, виды из окон («хотя бы один из»), статусы (только для авторизованного; иначе Status != Hidden). Сортировка — по заданному полю и направлению, default Price asc (авторизованный) / Floor asc (аноним), тай-брейк Number asc, Id asc; страница с TotalCount (индексы ix_property_objects_project_status, ix_property_objects_building_floor, ix_property_objects_rooms, ix_property_objects_price).
    • Обогащение страницы (без N+1): здания и планировки объектов страницы одной батч-загрузкой каждая.
  4. Сборка ответа и masking. Строка — по таблице PropertyObjectListItemModel; Layout — id и название планировки объекта (при наличии), Building — id, название, срок сдачи. При анониме в каждом элементе: Price = PricePerMeter = null, Status = null, ReservedUntil = BookedUntil = null.
  5. Результат. Страница объектов, статус 200; ошибки — 400/403/404. Применённые фильтры как действия пользователя трекает клиент → user-activity.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_ERROR400Нарушение правил таблицы входных данных (диапазоны, дубли, Hidden в Statuses, LayoutId+LayoutGroupId вместе)«Некорректные параметры запроса»no retry
CHESSBOARD_FILTER_REQUIRES_AUTH403Аноним использует Price (диапазон)/Statuses или OrderBy in (Price, PricePerMeter)«Фильтр доступен после входа»no retry (войти)
CHESSBOARD_PROJECT_NOT_FOUND404Проект не публичен/не существует/застройщик скрыт«Проект не найден»no retry
CHESSBOARD_BUILDING_NOT_FOUND404BuildingId не является published-зданием проекта«Здание не найдено»no retry
INTERNAL_ERROR500Инфраструктурный сбойgenericretry (backoff)

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

  • Новые фильтры/поля ответа — additive; ужесточение валидации или изменение default-сортировки — breaking (фиксируется в changelog контекста).
  • Enum-строки — клиентский fallback на unknown обязателен.
  • Logs: projectId, buildingId, применённые фильтры (имена, не значения цен), actor type, TotalCount; metrics: chessboard_public_property_objects_list_requests_total{authenticated}, latency histogram (SLO p99 < 300 мс), chessboard_public_property_objects_list_page_size; traces: OTel span chessboard.list-public-property-objects.
  • Dashboard/runbook — chessboard-overview (README).

Acceptance Criteria

  • AC1 (полный список для авторизованного). Дано: авторизованный, published-проект. Когда: GET с фильтрами по цене/статусу. Тогда: 200, отфильтрованная страница с ценами/статусами, default-сортировка Price asc, тай-брейк Number asc, Id asc.
  • AC2 (masking + default-сортировка анонима). Дано: аноним. Когда: GET без фильтров по цене/статусу. Тогда: 200, Price/PricePerMeter/Status/таймеры = null, default-сортировка Floor asc.
  • AC3 (запрет фильтров по скрытым полям). Дано: аноним. Когда: задан Price/Statuses или OrderBy in (Price, PricePerMeter). Тогда: CHESSBOARD_FILTER_REQUIRES_AUTH (403).
  • AC4 (Hidden в фильтре статусов). Дано: любой актор. Когда: Statuses содержит Hidden. Тогда: VALIDATION_ERROR (400) — всегда.
  • AC5 (взаимоисключающие фильтры планировки). Дано: заданы и LayoutId, и LayoutGroupId. Когда: GET. Тогда: VALIDATION_ERROR (400).
  • AC6 (здание вне проекта). Дано: BuildingId не является published-зданием проекта. Когда: GET. Тогда: CHESSBOARD_BUILDING_NOT_FOUND (404).
  • AC7 (непубличный родитель). Дано: проект не Published или застройщик скрыт. Когда: GET. Тогда: CHESSBOARD_PROJECT_NOT_FOUND (404).
  • AC8 (виды из окон «хотя бы один из»). Дано: WindowViews = [Park, Sea]. Когда: GET. Тогда: в выдаче объекты, у которых присутствует хотя бы один из указанных видов.

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

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

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