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

GET /api/v1/developers/projects/{projectId}/layout-groups — Список групп планировок

Назначение

Раздел «Группы планировок» страницы проекта в кабинете застройщика: список укрупнённых типов ассортимента («Студии», «1-комнатные», «Люкс с видом на море») проекта своей компании с пагинацией, поиском по названию и сортировкой по SortOrder — с числом планировок в группе и диапазоном площадей (для настройки правил вознаграждений rewards и группировки карточки проекта). Дом полей — сущность Группа планировок.

Metadata

ПолеЗначение
Сервис/контекстchessboard
API areadevelopers
Feature grouplayouts
Статусdraft
Документdocs/06-services/chessboard/api/developers/list-layout-groups.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}/layout-groups
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 (групп в проекте немного)
Searchquerystring?Нет<= 128 симв.; подстрока lower(name) like '%' || lower(@Search) || '%'
IncludeArchivedqueryboolНет (default false)false → фильтр DeletedAt == null (архив — soft-delete)

Сортировка не параметризуется — фиксирована по SortOrder (см. алгоритм).

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

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

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

Модель DeveloperLayoutGroupListItem

Дельта-проекция; дом полей и инварианты — ../domain/layout-group.md.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаLayoutGroupEntity.IdId группы.
NamestringДаNameНазвание группы (уникально в проекте, INV-LGR-2).
Descriptionstring?НетDescriptionОписание группы (PIN-поле).
SortOrderintДаSortOrderПорядок отображения в ассортименте (0..1000).
LayoutsCountintДаLayoutsCountПланировок в группе (денормализация, job project-aggregates-recalc).
AreaNullableRange<decimal>?НетArea (Area.Min/Area.Max)Диапазон TotalArea планировок группы.
VersionlongДаVersionДоменная версия (для последующих PUT/archive).

Предусловия

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

Алгоритм

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

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.layouts.manage; при scope «группа проектов/проект» — ProjectId должен входить в разрешённый набор (иначе проект недостижим → 404). Нет permission ни в одном scope → FORBIDDEN (403); просроченный/отозванный доступ → UNAUTHORIZED (401) / FORBIDDEN (403).
  2. Проверка входа. Page >= 1, PageSize 1..200, длина Search <= 128, ProjectId != Guid.Empty — иначе ValidationError. Проект-родитель существует в пределах tenant-а и в scope — иначе CHESSBOARD_PROJECT_NOT_FOUND (404). Допустим любой статус проекта.
  3. Выборка. Страница проекций собирается по фильтрам: ProjectId + tenant-фильтр; при IncludeArchived = false — только не архивные; Search — подстрока имени. Сортировка — SortOrder asc, затем lower(Name) asc (стабильный порядок при равном SortOrder, default 0). Считается TotalCount под тем же фильтром. Пустой результат и Page за пределами последней страницы — валидны: 200, Items = [], фактический TotalCount.
  4. Результат. Возвращаются 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
ValidationError400Неверные paging / длина Search / пустой ProjectIdСписок ошибок полейno retry
CHESSBOARD_PROJECT_NOT_FOUND404Проект не найден или чужой tenant«Проект не найден»no retry
FORBIDDEN403Нет chessboard.layouts.manage в scope«Недостаточно прав»no retry
UNAUTHORIZED401Нет/просрочен 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).

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

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

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