GET /api/v1/developers/projects/{projectId}/property-objects — Список объектов недвижимости проекта
Назначение
Экран «Объекты» страницы проекта в кабинете застройщика: список объектов недвижимости проекта своей компании
с пагинацией, фильтрами (здание / этажи / секции / комнатность / статус / цена) и поиском по номеру, а также
сортировками (натуральный порядок шахматки / цена). Менеджер видит все свои объекты — включая Hidden и
(по флагу) soft-deleted, в отличие от публичного списка
public/property-objects/list-property-objects.md.
Дом полей — сущность Объект недвижимости.
| Поле | Значение |
|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | propertyObjects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/search-property-objects.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.property-objects.manage (../../identity/domain/permission.md) |
| Scope (RBAC) | организация / группа проектов / проект (projectId в scope) |
| Tenant isolation | TenantId = CompanyId (= DeveloperId) из JWT; выборка ограничивается tenant-ом актора; чужой проект → 404 |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/developers/projects/{projectId}/property-objects |
| Success status | 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ProjectId | route | Guid | Да | != Guid.Empty; существующий проект tenant-а | 404 при чужом/несуществующем |
Page | query | int | Нет (default 1) | >= 1 | — |
PageSize | query | int | Нет (default 50) | 1..200 | — |
BuildingId | query | Guid? | Нет | здание этого проекта; null — все здания проекта | 404 при чужом здании |
Floors | query (csv) | short[]? | Нет | каждый -5..200; ≤ 50 элементов, без дублей | — |
Sections | query (csv) | short[]? | Нет | каждый 1..50; ≤ 50 элементов, без дублей | — |
Rooms | query (csv) | short[]? | Нет | каждый 0..20 (0 = студия); ≤ 20 элементов, без дублей | — |
Statuses | query (csv) | PropertyObjectStatus[]? | Нет | значения enum Free/Reserved/Booked/Sold/Hidden (reference-data.md); пусто = все | — |
Price | query (Price.Min/Price.Max) | NullableRange<decimal>? | Нет | Price.Min >= 0, Price.Max >= 0, при обоих — Price.Min <= Price.Max (фильтр по PropertyObject.Price) | — |
Search | query | string? | Нет | <= 32 симв.; поиск по номеру lower(number) like lower(@Search%) | — |
SortBy | query | PropertyObjectSortBy (string) | Нет (default FloorSectionNumber) | enum ниже | — |
IncludeDeleted | query | bool | Нет (default false) | false → фильтр DeletedAt == null | — |
Enum PropertyObjectSortBy (поле сортировки)
| Значение | Колонки |
|---|
FloorSectionNumber (default) | floor asc, section asc nulls first, lower(number) asc |
PriceAsc | price asc |
PriceDesc | price desc |
Тай-брейк любой сортировки — Id asc (стабильная пагинация).
Модель ответа SearchDeveloperPropertyObjectsResult
Постраничный контейнер: Items, Page, PageSize, TotalCount (не сырой массив).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Items | IReadOnlyList<DeveloperPropertyObjectListItem> | Да (может быть пустым) | проекция PropertyObjectEntity | — | Страница объектов недвижимости. |
Page | int | Да | echo запроса | — | Текущая страница. |
PageSize | int | Да | echo запроса | — | Размер страницы. |
TotalCount | long | Да | подсчёт под фильтром | — | Всего записей под фильтром. |
Модель DeveloperPropertyObjectListItem (проекция property-object.md)
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | PropertyObject.Id | — | Id объекта недвижимости (переход в карточку). |
BuildingId | Guid | Да | PropertyObject.BuildingId | — | Здание (литер/корпус). |
Number | string | Да | PropertyObject.Number | — | Номер объекта. |
Floor | short | Да | PropertyObject.Floor | — | Этаж (ось Y шахматки). |
Section | short? | Нет | PropertyObject.Section | — | Секция/подъезд (ось X). |
Rooms | short | Да | PropertyObject.Rooms | — | Комнатность (0 = студия). |
TotalArea | decimal | Да | PropertyObject.TotalArea | — | Общая площадь, м². |
LivingArea | decimal? | Нет | PropertyObject.LivingArea | — | Жилая площадь, м². |
Price | decimal | Да | PropertyObject.Price | — | Текущая цена. |
CurrencyId | short | Да | PropertyObject.CurrencyId | — | Валюта — ссылка на справочник handbook (= Project.CurrencyId, INV-PRJ-6). |
PricePerMeter | decimal | Да | PropertyObject.PricePerMeter | — | Цена за м² (денормализация). |
Status | PropertyObjectStatus (string) | Да | PropertyObject.Status | — | Free/Reserved/Booked/Sold/Hidden (reference-data.md). |
ReservedUntil | DateTimeOffset? | Нет | PropertyObject.ReservedUntil | — | Таймер резерва (⇔ Status = Reserved). |
BookedUntil | DateTimeOffset? | Нет | PropertyObject.BookedUntil | — | Таймер брони (⇔ Status = Booked). |
LayoutId | Guid? | Нет | PropertyObject.LayoutId | — | Планировка (layout.md). |
WindowViews | IReadOnlyList<WindowViewKind> (string) | Да (может быть пустым) | PropertyObject.WindowViews.Views | — | Виды из окон (reference-data.md). |
SyncSource | ChessboardSyncSource | Да | PropertyObject.SyncSource | — | Pin/TalentChessboard (UI блокирует реплика-поля). |
Version | long | Да | PropertyObject.Version | — | Доменная версия (для последующих PUT, update-property-object.md). |
Предусловия
- Пользователь авторизован;
tenant_id = его компания-застройщик (= DeveloperId).
- У актора есть permission
chessboard.property-objects.manage в scope, покрывающем projectId.
- Проект-родитель существует и принадлежит tenant-у актора; при заданном
BuildingId — здание того же
проекта; иначе — 404.
Алгоритм
Операция read-only, доменных событий не публикует.
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.property-objects.manage в scope Organization; при сужении «группа проектов/проект» —
ProjectId должен входить в разрешённый набор (иначе проект недостижим → 404). Нет permission ни в одном
scope → FORBIDDEN (403); просроченный/отозванный доступ → UNAUTHORIZED (401) / FORBIDDEN (403).
- Проверка входа. Диапазоны
Page/PageSize, парсинг csv-enum-ов и Floors/Sections/Rooms
(диапазоны, дубли, лимиты), Price.Min <= Price.Max, длина Search — иначе ValidationError (400).
Проект-родитель существует в пределах tenant-а и в scope — иначе CHESSBOARD_PROJECT_NOT_FOUND (404). При
заданном BuildingId — здание принадлежит этому проекту, иначе CHESSBOARD_BUILDING_NOT_FOUND (404).
Допустим любой статус проекта.
- Выборка. Страница проекций собирается по фильтрам:
ProjectId + tenant-фильтр; при заданном
BuildingId — по зданию; Floors/Sections/Rooms/Statuses — совпадение по множествам; Price —
диапазон по Price; Search — префикс номера; DeletedAt == null при !IncludeDeleted. Сортировка —
по SortBy (таблица enum), тай-брейк Id asc. Считается TotalCount под тем же фильтром. Пустой
результат и Page за пределами последней страницы — валидны: 200, Items = [], фактический TotalCount.
- Результат. Возвращаются
Items, Page, PageSize, TotalCount; HTTP 200. Маскирование не
требуется — менеджер видит полные данные своего tenant-а. Freshness: read-committed снимок;
PricePerMeter денормализована, ReservedUntil/BookedUntil/Status отражают последнее применённое
событие CRM (истёкший таймер при ещё Reserved/Booked — норма до страховочного job
property-object-hold-expiration).
Acceptance Criteria
- Happy path. Given проект с объектами недвижимости; When
GET .../projects/{projectId}/property-objects;
Then 200, страница в порядке шахматки (FloorSectionNumber), включая Hidden.
- Фильтр по зданию и статусу. Given
BuildingId=<id>&Statuses=Free; When запрос; Then только свободные
объекты этого здания.
- Диапазон цены. Given
Price.Min/Price.Max; When запрос; Then только объекты в диапазоне; при
Price.Min > Price.Max — 400 ValidationError.
- Чужое здание. Given
BuildingId вне проекта/tenant-а; When запрос; Then 404
CHESSBOARD_BUILDING_NOT_FOUND.
- Чужой/вне-scope проект. Given
projectId чужого tenant-а или вне scope; When запрос; Then 404
CHESSBOARD_PROJECT_NOT_FOUND.
- Нет права. Given нет
chessboard.property-objects.manage; When запрос; Then 403 FORBIDDEN.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | Неверные paging/enum/диапазоны/дубли/длина Search | Список ошибок полей | no retry |
FORBIDDEN | 403 | Нет chessboard.property-objects.manage в scope | «Недостаточно прав» | no retry |
CHESSBOARD_PROJECT_NOT_FOUND | 404 | Проект не найден / чужой tenant | «Проект не найден» | no retry |
CHESSBOARD_BUILDING_NOT_FOUND | 404 | BuildingId не принадлежит проекту tenant-а | «Здание не найдено» | no retry |
Сквозные ошибки области (UNAUTHORIZED 401) — в реестре README.md, здесь не повторяются.
Совместимость и наблюдаемость
- Новые optional фильтры/поля ответа — additive; изменение семантики сортировок или default-порядка —
breaking (rollout). Enum-строки — клиентский fallback на unknown обязателен.
- Logs:
TenantId, ProjectId, BuildingId, имена применённых фильтров (без значений), TotalCount;
metrics: chessboard_developers_property_objects_search_total, latency p99 < 300 мс (SLO README);
traces: OTel (traceparent).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы