GET /api/v1/developers/projects/{projectId}/layout-groups — Список групп планировок
Назначение
Раздел «Группы планировок» страницы проекта в кабинете застройщика: список укрупнённых типов ассортимента
(«Студии», «1-комнатные», «Люкс с видом на море») проекта своей компании с пагинацией, поиском по названию и
сортировкой по SortOrder — с числом планировок в группе и диапазоном площадей (для настройки правил
вознаграждений rewards и группировки карточки проекта). Дом полей — сущность
Группа планировок.
| Поле | Значение |
|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | layouts |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/list-layout-groups.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}/layout-groups |
| 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 (групп в проекте немного) | — |
Search | query | string? | Нет | <= 128 симв.; подстрока lower(name) like '%' || lower(@Search) || '%' | — |
IncludeArchived | query | bool | Нет (default false) | false → фильтр DeletedAt == null (архив — soft-delete) | — |
Сортировка не параметризуется — фиксирована по SortOrder (см. алгоритм).
Модель ответа ListDeveloperLayoutGroupsResult
Постраничный контейнер: Items, Page, PageSize, TotalCount, TotalPages (не сырой массив).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Items | IReadOnlyList<DeveloperLayoutGroupListItem> | Да | проекция LayoutGroupEntity | — | Страница групп (сортировка по SortOrder). |
Page | int | Да | echo запроса | — | Текущая страница. |
PageSize | int | Да | echo запроса | — | Размер страницы. |
TotalCount | long | Да | подсчёт под фильтром | — | Всего групп под фильтром. |
TotalPages | int | Да | вычисляется | — | ceil(TotalCount / PageSize). |
Модель DeveloperLayoutGroupListItem
Дельта-проекция; дом полей и инварианты — ../domain/layout-group.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | LayoutGroupEntity.Id | — | Id группы. |
Name | string | Да | Name | — | Название группы (уникально в проекте, INV-LGR-2). |
Description | string? | Нет | Description | — | Описание группы (PIN-поле). |
SortOrder | int | Да | SortOrder | — | Порядок отображения в ассортименте (0..1000). |
LayoutsCount | int | Да | LayoutsCount | — | Планировок в группе (денормализация, job project-aggregates-recalc). |
Area | NullableRange<decimal>? | Нет | Area (Area.Min/Area.Max) | — | Диапазон TotalArea планировок группы. |
Version | long | Да | Version | — | Доменная версия (для последующих PUT/archive). |
Предусловия
- Пользователь авторизован;
tenant_id = его компания-застройщик.
- У актора есть permission
chessboard.layouts.manage в scope, покрывающем projectId.
- Проект-родитель существует и принадлежит tenant-у актора (любой статус); иначе — 404.
Алгоритм
Операция read-only, доменных событий не публикует.
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.layouts.manage; при scope «группа проектов/проект» — ProjectId должен входить в
разрешённый набор (иначе проект недостижим → 404). Нет permission ни в одном scope → FORBIDDEN (403);
просроченный/отозванный доступ → UNAUTHORIZED (401) / FORBIDDEN (403).
- Проверка входа.
Page >= 1, PageSize 1..200, длина Search <= 128, ProjectId != Guid.Empty —
иначе ValidationError. Проект-родитель существует в пределах tenant-а и в scope — иначе
CHESSBOARD_PROJECT_NOT_FOUND (404). Допустим любой статус проекта.
- Выборка. Страница проекций собирается по фильтрам:
ProjectId + tenant-фильтр; при
IncludeArchived = false — только не архивные; Search — подстрока имени. Сортировка — SortOrder asc,
затем lower(Name) asc (стабильный порядок при равном SortOrder, default 0). Считается TotalCount
под тем же фильтром. Пустой результат и Page за пределами последней страницы — валидны: 200,
Items = [], фактический TotalCount.
- Результат. Возвращаются
Items, Page, PageSize, TotalCount, TotalPages; HTTP 200.
Маскирование не требуется (PII нет). Freshness: read-committed снимок; LayoutsCount/Area могут
отставать до фонового пересчёта project-aggregates-recalc (окно ~40 с).
Acceptance Criteria
- Happy path. Given проект с группами планировок; When
GET .../projects/{projectId}/layout-groups; Then
200, страница в порядке SortOrder, LayoutsCount/Area заполнены.
- Поиск. Given
Search=<часть названия>; When запрос; Then только группы с подстрокой в Name.
- Архив. Given
IncludeArchived=false; When запрос; Then архивные (soft-deleted) группы отсутствуют;
при IncludeArchived=true — включены.
- Пустой/за-предельный. 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 |
|---|
ValidationError | 400 | Неверные paging / длина Search / пустой ProjectId | Список ошибок полей | no retry |
CHESSBOARD_PROJECT_NOT_FOUND | 404 | Проект не найден или чужой tenant | «Проект не найден» | no retry |
FORBIDDEN | 403 | Нет chessboard.layouts.manage в scope | «Недостаточно прав» | no retry |
UNAUTHORIZED | 401 | Нет/просрочен JWT | «Требуется авторизация» | no retry |
Совместимость и наблюдаемость
- Новые optional параметры фильтра/поля ответа — additive (без breaking change); смена семантики сортировки
по
SortOrder или удаление/переименование поля — breaking (migration + rollout).
- Logs:
TenantId, ProjectId, флаги фильтра (без значения Search); metrics:
chessboard_developers_layout_groups_list_total, latency p99 < 300 мс (SLO README); traces — OTel
(traceparent).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы