GET /api/v1/developers/projects/{projectId}/layouts — Список планировок проекта
Назначение
Первый экран раздела «Планировки» страницы проекта в кабинете застройщика: список типовых планировок проекта своей компании с пагинацией и фильтрами (группа / комнатность / название) — с комнатностью, площадями, схемами и счётчиком объектов недвижимости. Источник выбора планировки при заведении/правке объекта недвижимости и точка входа в редактирование. Дом полей — сущность Планировка.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | layouts |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/list-layouts.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.layouts.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}/layouts |
| 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 (планировок в проекте десятки) | — |
LayoutGroupId | query | Guid? | Нет | фильтр layout_group_id == @LayoutGroupId; несуществующий/чужой id → пустая страница (утечки нет) | — |
Rooms | query | short? | Нет | 0..20 (0 = студия); точное сравнение rooms == @Rooms | — |
Search | query | string? | Нет | <= 128 симв.; префиксный `lower(name) like lower(@Search) | |
IncludeArchived | query | bool | Нет (default false) | false → фильтр DeletedAt == null; true — включает архивные планировки | — |
Модель ответа ListDeveloperLayoutsResult
Постраничный контейнер: Items, Page, PageSize, TotalCount, TotalPages (не сырой массив).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Items | IReadOnlyList<DeveloperLayoutListItem> | Да (может быть пустым) | проекция LayoutEntity | — | Страница планировок. |
Page | int | Да | echo запроса | — | Текущая страница. |
PageSize | int | Да | echo запроса | — | Размер страницы. |
TotalCount | long | Да | подсчёт под фильтром | — | Всего планировок под фильтром. |
TotalPages | int | Да | вычисляется | — | ceil(TotalCount / PageSize). |
Модель DeveloperLayoutListItem (проекция layout.md)
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | LayoutEntity.Id | — | Id планировки (переход в правку/архив). |
Name | string | Да | Name | — | Название («1К-А 32.5», «Студия S»), уникально в проекте (INV-LAY-2). |
Rooms | short | Да | Rooms | — | Типовая комнатность (0 = студия). |
TotalArea | decimal | Да | TotalArea | — | Типовая общая площадь, м². |
LivingArea | decimal? | Нет | LivingArea | — | Типовая жилая площадь, м². |
Schemas | LayoutSchemasDocument | Да | Schemas (typed jsonb) | — | Схемы планировки; дом модели — layout.md (LayoutSchemasDocument.Items : LayoutSchemaModel[]). |
LayoutGroupId | Guid? | Нет | LayoutGroupId | — | Группа планировок (nullable, INV-LAY-3). |
PropertyObjectsCount | int | Да | PropertyObjectsCount (денормализация) | — | Число объектов недвижимости с этой планировкой (пересчёт — job project-aggregates-recalc). |
Version | long | Да | Version | — | Доменная версия (для последующих PUT/archive, optimistic concurrency). |
Enum-строки (LayoutSchemaModel.Kind) — клиентский fallback на unknown обязателен
(reference-data.md).
Предусловия
- Пользователь авторизован;
tenant_id= его компания-застройщик. - У актора есть permission
chessboard.layouts.manageв scope, покрывающемprojectId. - Проект-родитель существует и принадлежит tenant-у актора (любой статус); иначе — 404.
Алгоритм
Операция read-only, доменных событий не публикует.
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.layouts.manageв scopeOrganization; при сужении «группа проектов/проект» —ProjectIdдолжен входить в разрешённый набор (иначе проект недостижим → 404). Нет permission ни в одном scope →FORBIDDEN(403); просроченный/отозванный доступ →UNAUTHORIZED(401) /FORBIDDEN(403). - Проверка входа. Диапазоны
Page/PageSize,Rooms in 0..20, длинаSearch— иначеValidationError(400). Проект-родитель существует в пределах tenant-а и в scope — иначеCHESSBOARD_PROJECT_NOT_FOUND(404). Допустим любой статус проекта. - Выборка. Страница проекций собирается по фильтрам:
ProjectId+ tenant-фильтр; приIncludeArchived = false— только не архивные;LayoutGroupId(равенство; несуществующий/чужой id даёт пустую страницу — утечки существования нет);Rooms(равенство);Search(префикс имени). Сортировка —Rooms asc, lower(Name) asc(студии → 1к → …), тай-брейкId asc. СчитаетсяTotalCountпод тем же фильтром. Пустой результат иPageза пределами последней страницы — валидны:200,Items = [], фактическийTotalCount. - Результат. Возвращаются
Items,Page,PageSize,TotalCount,TotalPages; HTTP200. Маскирование не требуется (PII нет). Freshness: read-committed снимок; денормализованныйPropertyObjectsCountможет отставать до фонового пересчётаproject-aggregates-recalc(окно ~40 с).
Acceptance Criteria
- Happy path. Given проект с планировками; When
GET .../projects/{projectId}/layouts; Then200, страница отсортированаRooms asc, Name asc,Schemas/PropertyObjectsCountзаполнены. - Фильтр по группе. Given
LayoutGroupId=<id>; When запрос; Then только планировки этой группы; для несуществующего/чужогоLayoutGroupId—200с пустой страницей (не 404). - Фильтр по комнатности. Given
Rooms=0; When запрос; Then только студии. - Пустой/за-предельный. Given фильтр без совпадений или
Pageза последней страницей; When запрос; Then200,Items = []. - Чужой/вне-scope проект. Given
projectIdчужого tenant-а или вне scope; When запрос; Then404CHESSBOARD_PROJECT_NOT_FOUND. - Нет права. Given нет
chessboard.layouts.manage; When запрос; Then403FORBIDDEN.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Неверные Page/PageSize/Rooms/длина Search | Список ошибок полей | no retry |
CHESSBOARD_PROJECT_NOT_FOUND | 404 | Проект не найден / чужой tenant | «Проект не найден» | no retry |
FORBIDDEN | 403 | Нет chessboard.layouts.manage в scope | «Недостаточно прав» | no retry |
UNAUTHORIZED (401) и CHESSBOARD_CONCURRENCY_CONFLICT (для операций записи) — сквозные ошибки области,
описаны в реестре README.md.
Совместимость и наблюдаемость
- Новые optional фильтры/поля ответа — additive; изменение семантики фильтров/default-сортировки или удаление полей — breaking (migration + rollout, changelog контекста).
- Logs:
TenantId,ProjectId, применённые фильтры (имена, без значенияSearch),TotalCount; metrics:chessboard_developers_layouts_list_total, latency p99 < 300 мс (SLO README); traces: OTel (traceparent); dashboard/runbook — chessboard-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): Chessboard API — реестр операций областей Developers и Admin, GET /api/v1/developers/projects/{projectId}/layout-groups — Список групп планировок, POST /api/v1/developers/layouts/{layoutId}/archive — Архивирование планировки, POST /api/v1/developers/projects/{projectId}/layouts — Создание планировки, PUT /api/v1/developers/layouts/{layoutId} — Обновление планировки
- События (1): Event Contract: pin.chessboard.layout-upserted (LayoutUpsertedKafkaEvent)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: ../domain/layout.md (дом полей,
LayoutSchemasDocument), ../domain/layout-group.md, ../domain/project.md, ../domain/reference-data.md (LayoutSchemaKind). - Соседние операции: create-layout.md, update-layout.md, list-layout-groups.md; реестр: README.md.