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

PUT /api/v1/developers/layout-groups/{layoutGroupId} — Обновление группы планировок

Назначение

Менеджер застройщика правит редактируемые поля группы планировок своей компании: Name, Description, SortOrder (переименование, описание, порядок отображения в ассортименте). Дом полей — сущность Группа планировок.

Реплика-поле Name

При SyncSource = TalentChessboard поле Name управляется синхронизацией Talent и через эту операцию не меняется (INV-LGR-2 в реплике поддерживает потребитель синка).

Metadata

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

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

ПроверкаЗначение
Actor typeUser (менеджер застройщика)
Auth policy / Permissionschessboard.layouts.manage
Scope (RBAC)организация / группа проектов / проект (group.ProjectId в scope)
Tenant isolationTenantId = CompanyId из JWT; чужая группа → 404 (существование не раскрывается)
Auditобновление: UpdatedByUserId, инкремент Version

HTTP-контракт

ПолеЗначение
MethodPUT
Route/api/v1/developers/layout-groups/{layoutGroupId}
Success status200
Idempotency headerне применяется — PUT семантически идемпотентен (full replace + Version)
Correlationсквозной trace через заголовок traceparent

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

Ограничения полей — из дома ../domain/layout-group.md; ProjectId, TenantId и SyncSource не принимаются (наследуются от загруженной группы / управляются синком).

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
LayoutGroupIdrouteGuidДасуществующая группа tenant-а, не архивная404 при чужой/отсутствии
NamebodystringДа1..128 (trim); уникально в проекте среди не удалённых (INV-LGR-2). Реплика-поле — при SyncSource = TalentChessboard изменение игнорируется (управляется синком)
Descriptionbodystring?Нет<= 2000 симв. (PIN-поле)
SortOrderbodyintДа0..1000 (PIN-поле; порядок отображения групп)
VersionbodylongДа>= 1; сверка с LayoutGroupEntity.Version (optimistic)

LayoutsCount, Area не принимаются — денормализация считается фоново (project-aggregates-recalc); ExternalTalentLayoutGroupId, ReplicaVersion, LastSyncedAt — только синк.

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаLayoutGroupEntity.IdId группы.
ProjectIdGuidДаLayoutGroupEntity.ProjectIdПроект-владелец.
VersionlongДаVersion (после инкремента)Новая доменная версия.
UpdatedAtDateTimeOffsetДаUpdatedAtМомент сохранения.

Полный список групп перечитывается через list-layout-groups.md.

Предусловия и постусловия

Предусловия:

  • Актор авторизован и имеет chessboard.layouts.manage в scope проекта группы.
  • Группа существует в tenant актора и не архивна.
  • Клиент располагает актуальным Version группы.
  • Имя (Name) — реплика-поле: реально меняется только при SyncSource = Pin; при TalentChessboard вход Name трактуется как no-op.

Постусловия при успехе:

  • Обновлены разрешённые SyncSource поля (всегда — Description, SortOrder; при Pin дополнительно — Name); Version +1, проставлены UpdatedByUserId/UpdatedAt; статус не меняется.
  • Инвариант уникальности имени группы в проекте (INV-LGR-2) не нарушен.
  • Денормализованные LayoutsCount/Area этой операцией не трогаются — их уточняет фоновый пересчёт.
  • В том же согласованном изменении поставлено на публикацию pin.chessboard.layout-group-upserted.v1.

Постусловие при отказе: группа не изменена, событие не публикуется, Version сохранён.

Интеграционная последовательность

  • Синхронно возвращается 200 + Id/ProjectId/Version/UpdatedAt; синхронных вызовов соседей нет.
  • Асинхронно pin.chessboard.layout-group-upserted.v1 (партиционный ключ Id группы, порядок per-group гарантирован) потребляют: dev-stats (измерение LayoutGroup, ADR-0057), rewards (условия правил по LayoutGroup), catalog (группировка ассортимента). Потребители применяют снапшоты монотонно по Version.
  • Отдельный фоновый канал: LayoutsCount/Area пересчитывает job project-aggregates-recalc — его результат приходит потребителям следующим снапшотом группы, а не этой правкой (окно debounce).
Окно eventual-consistency

Переименование/переупорядочивание в ассортименте catalog и в отчётах dev-stats/rewards видно с лагом consumer. Компенсаций не требуется — снапшот полный, идемпотентен.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.layouts.manage в scope проекта группы.
  2. Проверка входа и предусловий.
    • Name 1..128 trim, Description <= 2000, SortOrder 0..1000, Version >= 1 — иначе ValidationError;
    • группа существует в пределах tenant-а и не архивна — иначе CHESSBOARD_LAYOUT_GROUP_NOT_FOUND (404, архивная неотличима от отсутствующей наружу);
    • Version совпадает с текущей — иначе CHESSBOARD_CONCURRENCY_CONFLICT (409);
    • при SyncSource = Pin и изменении имени — оно уникально в проекте среди не удалённых (INV-LGR-2), иначе CHESSBOARD_LAYOUT_GROUP_NAME_TAKEN (409); при SyncSource = TalentChessboard вход Name — не ошибка, а no-op (поле управляется синком, проверка уникальности не выполняется).
  3. Применение правок. Набор мутируемых полей зависит от SyncSource: всегда — описание, порядок (SortOrder); при SyncSource = Pin дополнительно — имя. Проставляются UpdatedByUserId, инкремент Version. Переходов статуса нет.
  4. Факты и события. Публикуется событие pin.chessboard.layout-group-upserted.v1 — снапшот после правки (состав — layout-group.md «События»).
  5. Результат. Возвращаются Id, ProjectId, Version, UpdatedAt; HTTP 200. Маскирование не требуется (PII нет).

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияLayoutGroupUpsertedKafkaEventpin.chessboard.layout-group-upserted.v1, key = Id; потребители: dev-stats (измерение LayoutGroup, ADR-0057), rewards (условия правил по LayoutGroup), catalog (группировка ассортимента)
Проекции / пересчётLayoutsCount/Area уточняются фоново job-ом project-aggregates-recalc (эта операция их не трогает)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Нарушение ограничений полей (Name/Description/SortOrder/Version)Список ошибок полейno retry
CHESSBOARD_LAYOUT_GROUP_NOT_FOUND404Нет группы / чужой tenant / вне scope / архивная«Группа планировок не найдена»no retry
CHESSBOARD_LAYOUT_GROUP_NAME_TAKEN409Дубль имени в проекте (INV-LGR-2)«Название группы планировок занято»no retry
CHESSBOARD_CONCURRENCY_CONFLICT409Несовпадение Version / параллельная правка«Данные изменены параллельно»retry после перечитывания
FORBIDDEN403Нет chessboard.layouts.manage в scope«Недостаточно прав»no retry

Совместимость и наблюдаемость

  • Новые optional поля формы — additive; удаление/переименование/смена типа — breaking (migration + rollout); изменение snapshot события — по правилам ContractVersion (layout-group.md «События»).
  • Logs: TenantId, ProjectId, LayoutGroupId, изменённые поля (имена, без значений); metrics: chessboard_developers_layout_group_update_total{result}; traces: OTel (traceparent); dashboard chessboard-overview.

Acceptance Criteria

  • AC1 (правка PIN-полей). Дано: группа с актуальным Version. Когда: меняются Description/SortOrder. Тогда: поля обновлены, Version +1, публикуется pin.chessboard.layout-group-upserted.v1; порядок отображения групп в ассортименте меняется после обработки события.
  • AC2 (переименование при Pin). Дано: SyncSource = Pin, новое имя свободно в проекте. Когда: переименование. Тогда: Name обновлён, событие несёт новое имя.
  • AC3 (имя под синком — no-op). Дано: SyncSource = TalentChessboard. Когда: входной Name отличается. Тогда: Name не меняется (без ошибки), проверка уникальности не выполняется; Description/SortOrder применяются.
  • AC4 (дубль имени). Дано: SyncSource = Pin, имя занято другой не удалённой группой проекта. Когда: переименование. Тогда: CHESSBOARD_LAYOUT_GROUP_NAME_TAKEN (409).
  • AC5 (конкурентная правка). Дано: устаревший Version. Когда: сохранение. Тогда: CHESSBOARD_CONCURRENCY_CONFLICT (409); проигравший перечитывает и повторяет.
  • AC6 (архивная/чужая группа). Дано: группа архивна, чужого tenant или вне scope. Когда: операция. Тогда: CHESSBOARD_LAYOUT_GROUP_NOT_FOUND (404), событие не публикуется.

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

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

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