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

GET /api/v1/developers/projects/{projectId}/layouts — Список планировок проекта

Назначение

Первый экран раздела «Планировки» страницы проекта в кабинете застройщика: список типовых планировок проекта своей компании с пагинацией и фильтрами (группа / комнатность / название) — с комнатностью, площадями, схемами и счётчиком объектов недвижимости. Источник выбора планировки при заведении/правке объекта недвижимости и точка входа в редактирование. Дом полей — сущность Планировка.

Metadata

ПолеЗначение
Сервис/контекстchessboard
API areadevelopers
Feature grouplayouts
Статусdraft
Документdocs/06-services/chessboard/api/developers/list-layouts.md

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

ПроверкаЗначение
Actor typeUser (менеджер застройщика)
Auth policy / Permissionschessboard.layouts.manage (../../identity/domain/permission.md)
Scope (RBAC)организация / группа проектов / проект (projectId в scope)
Tenant isolationTenantId = CompanyId (= DeveloperId) из JWT; выборка ограничивается tenant-ом актора; чужой проект → 404

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/developers/projects/{projectId}/layouts
Success status200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ProjectIdrouteGuidДа!= Guid.Empty; существующий проект tenant-а404 при чужом/несуществующем
PagequeryintНет (default 1)>= 1
PageSizequeryintНет (default 50)1..200 (планировок в проекте десятки)
LayoutGroupIdqueryGuid?Нетфильтр layout_group_id == @LayoutGroupId; несуществующий/чужой id → пустая страница (утечки нет)
Roomsqueryshort?Нет0..20 (0 = студия); точное сравнение rooms == @Rooms
Searchquerystring?Нет<= 128 симв.; префиксный `lower(name) like lower(@Search)
IncludeArchivedqueryboolНет (default false)false → фильтр DeletedAt == null; true — включает архивные планировки

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

Постраничный контейнер: Items, Page, PageSize, TotalCount, TotalPages (не сырой массив).

ПолеТипОбязательностьИсточникMaskingОписание
ItemsIReadOnlyList<DeveloperLayoutListItem>Да (может быть пустым)проекция LayoutEntityСтраница планировок.
PageintДаecho запросаТекущая страница.
PageSizeintДаecho запросаРазмер страницы.
TotalCountlongДаподсчёт под фильтромВсего планировок под фильтром.
TotalPagesintДавычисляетсяceil(TotalCount / PageSize).

Модель DeveloperLayoutListItem (проекция layout.md)

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаLayoutEntity.IdId планировки (переход в правку/архив).
NamestringДаNameНазвание («1К-А 32.5», «Студия S»), уникально в проекте (INV-LAY-2).
RoomsshortДаRoomsТиповая комнатность (0 = студия).
TotalAreadecimalДаTotalAreaТиповая общая площадь, м².
LivingAreadecimal?НетLivingAreaТиповая жилая площадь, м².
SchemasLayoutSchemasDocumentДаSchemas (typed jsonb)Схемы планировки; дом модели — layout.md (LayoutSchemasDocument.Items : LayoutSchemaModel[]).
LayoutGroupIdGuid?НетLayoutGroupIdГруппа планировок (nullable, INV-LAY-3).
PropertyObjectsCountintДаPropertyObjectsCount (денормализация)Число объектов недвижимости с этой планировкой (пересчёт — job project-aggregates-recalc).
VersionlongДаVersionДоменная версия (для последующих PUT/archive, optimistic concurrency).
Enum-fallback

Enum-строки (LayoutSchemaModel.Kind) — клиентский fallback на unknown обязателен (reference-data.md).

Предусловия

  • Пользователь авторизован; tenant_id = его компания-застройщик.
  • У актора есть permission chessboard.layouts.manage в scope, покрывающем projectId.
  • Проект-родитель существует и принадлежит tenant-у актора (любой статус); иначе — 404.

Алгоритм

Операция read-only, доменных событий не публикует.

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.layouts.manage в scope Organization; при сужении «группа проектов/проект» — ProjectId должен входить в разрешённый набор (иначе проект недостижим → 404). Нет permission ни в одном scope → FORBIDDEN (403); просроченный/отозванный доступ → UNAUTHORIZED (401) / FORBIDDEN (403).
  2. Проверка входа. Диапазоны Page/PageSize, Rooms in 0..20, длина Search — иначе ValidationError (400). Проект-родитель существует в пределах tenant-а и в scope — иначе CHESSBOARD_PROJECT_NOT_FOUND (404). Допустим любой статус проекта.
  3. Выборка. Страница проекций собирается по фильтрам: ProjectId + tenant-фильтр; при IncludeArchived = false — только не архивные; LayoutGroupId (равенство; несуществующий/чужой id даёт пустую страницу — утечки существования нет); Rooms (равенство); Search (префикс имени). Сортировка — Rooms asc, lower(Name) asc (студии → 1к → …), тай-брейк Id asc. Считается TotalCount под тем же фильтром. Пустой результат и Page за пределами последней страницы — валидны: 200, Items = [], фактический TotalCount.
  4. Результат. Возвращаются Items, Page, PageSize, TotalCount, TotalPages; HTTP 200. Маскирование не требуется (PII нет). Freshness: read-committed снимок; денормализованный PropertyObjectsCount может отставать до фонового пересчёта project-aggregates-recalc (окно ~40 с).

Acceptance Criteria

  • Happy path. Given проект с планировками; When GET .../projects/{projectId}/layouts; Then 200, страница отсортирована Rooms asc, Name asc, Schemas/PropertyObjectsCount заполнены.
  • Фильтр по группе. Given LayoutGroupId=<id>; When запрос; Then только планировки этой группы; для несуществующего/чужого LayoutGroupId200 с пустой страницей (не 404).
  • Фильтр по комнатности. Given Rooms=0; When запрос; Then только студии.
  • Пустой/за-предельный. Given фильтр без совпадений или Page за последней страницей; When запрос; Then 200, Items = [].
  • Чужой/вне-scope проект. Given projectId чужого tenant-а или вне scope; When запрос; Then 404 CHESSBOARD_PROJECT_NOT_FOUND.
  • Нет права. Given нет chessboard.layouts.manage; When запрос; Then 403 FORBIDDEN.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Неверные Page/PageSize/Rooms/длина SearchСписок ошибок полейno retry
CHESSBOARD_PROJECT_NOT_FOUND404Проект не найден / чужой tenant«Проект не найден»no retry
FORBIDDEN403Нет 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.

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

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

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