PUT /api/admin/v1/buildings/{buildingId}/override — Создание/изменение оверрайда корпуса (admin)
Назначение
Полный upsert корпусного оверрайда (BuildingDisplayOverride,
display-override.md): администратор перекрывает название комплекса,
звёздность, срок сдачи, готовность, адрес, дистанцию и видимость корпуса поверх данных chessboard.
Семантика PUT — полное состояние (поле null = не переопределено). Правка каскадна: пересчитываются
эффективные поля карточки проекта и всех объектов недвижимости корпуса в той же транзакции
(INV-6 display-override); IsVisible=false скрывает все объекты недвижимости корпуса
(INV-1 property-object-card).
Снятие оверрайда — симметричная операция DELETE /api/admin/v1/buildings/{buildingId}/override
(отдельный документ не заводится, дельта описана здесь): soft-delete + тот же каскадный пересчёт из
SourceSnapshot; ответ 200 (Removed = true); отсутствие живого оверрайда → 404 CATALOG_OVERRIDE_NOT_FOUND.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | catalog |
| API area | admin (ADR-0053) |
| Feature group | admin-overrides |
| Статус | draft |
| Документ | docs/06-services/catalog/api/admin/put-building-override.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | ExternalSystem (администратор платформы через admin API; m2m client-credentials — ADR-0053) |
| Auth policy / Permissions | policy AdminSystemOnly (клиент pin-admin-system); каталожное соответствие — permission catalog.overrides.manage (identity/domain/permission.md) |
| Scope (RBAC) | платформа (не tenanted — ADR-0052 п.3) |
| Audit | обязателен: deep audit admin-мутаций — actor (X-Admin-User-Id), buildingId, диф полей, Comment, результат; actor-stamping CreatedByUserId/UpdatedByUserId |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | PUT (delete-дельта: DELETE, тот же route) |
| Route | /api/admin/v1/buildings/{buildingId}/override |
| Success status | 200 |
| Idempotency header | Idempotency-Key (Guid; обязателен) |
| Correlation | сквозной trace через traceparent; X-Admin-User-Id — обязателен |
Входные данные / параметры запроса
Типы/ограничения — дом display-override.md.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
BuildingId | route | Guid | Да | != Guid.Empty; существование корпуса в витрине — шаг 2 (INV-2 TargetKnown) | — |
IsVisible | body | bool? | Нет | — | false каскадно скрывает объекты недвижимости корпуса |
ComplexName | body | string? | Нет | 1..256 символов после trim | — |
Stars | body | short? | Нет | 1..5 | — |
DeliveryDate | body | DateOnly? | Нет | — | — |
ReadinessPercent | body | short? | Нет | 0..100 | — |
Address | body | string? | Нет | ≤ 512 символов | — |
DistanceMinutes | body | int? | Нет | >= 0 | — |
Comment | body | string? | Нет | ≤ 1024 символов | internal — только admin-область и audit |
ExpectedVersion | body | long? | Нет | >= 1; optimistic-check против BuildingDisplayOverride.Version | — |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | audit-stamping |
IdempotencyKey | header Idempotency-Key | Guid | Да | != Guid.Empty | — |
Кросс-полевая валидация: хотя бы одно override-поле (IsVisible…DistanceMinutes) не null —
INV-3 AtLeastOneField → CATALOG_OVERRIDE_EMPTY.
Модель ответа BuildingOverrideMutationResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
OverrideId | Guid | Да | BuildingDisplayOverride.Id | — | Оверрайд (созданный/обновлённый/снятый). |
BuildingId | Guid | Да | BuildingDisplayOverride.BuildingId | — | Корпус. |
ProjectId | Guid | Да | BuildingDisplayOverride.ProjectId | — | Проект (денормализован в оверрайде). |
Created | bool | Да | вычисление | — | Создан новый / обновлён существующий. |
Removed | bool | Да | вычисление | — | true только в delete-дельте. |
Version | long | Да | BuildingDisplayOverride.Version | — | Версия после мутации. |
Effective | BuildingOverrideEffectiveBlock | Да | CatalogProjectCard после пересчёта | — | Эффективные значения (модель — get-building-override.md). |
ProjectCardStatus | string (enum ProjectCardStatus) | Да | CatalogProjectCard.Status после пересчёта | — | Итоговая видимость проекта. |
PropertyObjectsRecomputed | int | Да | счётчик мутаций шага 5 | — | Сколько карточек объектов недвижимости корпуса пересчитано. |
Алгоритм
- Контекст и доступ. Актор — администратор платформы (admin API, m2m
pin-admin-system, policyAdminSystemOnly); заголовкиX-Admin-User-Id,Idempotency-Key. Повтор по ключуadmin:building-override:{IdempotencyKey}→ сохранённый результат (200). - Проверка входа. Диапазоны/длины (
Stars 1..5,ReadinessPercent 0..100, `DistanceMinutes= 0
, длины строк) →ValidationError; хотя бы одно override-поле неnull(INV-3 AtLeastOneField), иначеCATALOG_OVERRIDE_EMPTY. Существование цели (INV-2 TargetKnown): корпус есть в витрине (хотя бы один объект недвижимости с этимBuildingId); нет →CATALOG_OVERRIDE_TARGET_NOT_FOUND(404). Состояние: карточка проекта корпусаDelisted→CATALOG_OVERRIDE_TARGET_DELISTED(409). Optimistic-check: заданныйExpectedVersionне совпал с версией живого оверрайда →CONCURRENCY_CONFLICT` (409). - Что читаем. Живой оверрайд корпуса (есть → update, нет → create; единственность — INV-1
SingleLiveOverride + unique partial index
ux_building_overrides_building_id), карточку проекта корпуса, объекты недвижимости корпуса (кромеDelisted) и их собственные оверрайды объектов недвижимости — для полного каскадаEffective = PropertyObjectOverride ?? BuildingOverride ?? SourceSnapshotпо корпусным полям. - Правила и переходы. Create: новый оверрайд со всеми переданными полями,
ProjectIdиз проекта корпуса,CreatedByUserId = AdminUserId,Version = 1. Update: полная замена полей (PUT-семантика),UpdatedByUserId = AdminUserId,Version++. Фиксируется доменное событиеBuildingDisplayOverrideChangedEvent(Id,BuildingId,ProjectId, набор изменённых полей). Каскадный пересчёт (INV-6): карточка проекта получает эффективныеName = ComplexName ?? SourceSnapshot.Name,Address,Stars,DeliveryDate,ReadinessPercent,DistanceMinutesи пересчитывает видимость (INV-1 project-card: все корпуса скрыты оверрайдами →Hidden); каждый объект недвижимости корпуса получает корпусные поля (Stars,DeliveryDate,ReadinessPercent,DistanceMinutes) и пересчитывает видимость (INV-1 property-object-card:BuildingOverride.IsVisible=false→Hidden); по каждому объекту фиксируетсяPropertyObjectCardRecomputedEvent, который в том же коммите пересчитывает агрегаты карточки проекта (MinPrice/MaxPrice/PropertyObjectsTotal/PropertyObjectsAvailable/Payback— INV-2 project-card). Приоритет видимости:BuildingOverride.IsVisible=falseскрывает объект недвижимости независимо от его собственного оверрайда — оверрайд объекта сIsVisible=trueне «воскрешает» объект скрытого корпуса (корпусной уровень строже); наоборот, при возврате корпуса в видимое состояние объект снова оценивается по INV-1 property-object-card (учитывая его собственный оверрайд иPropertyObjectStatusисточника). - Что меняется. Создаётся/обновляется оверрайд, обновляются карточка проекта и все карточки
объектов недвижимости корпуса (счётчик →
PropertyObjectsRecomputed). Порядок: оверрайд → проект → объекты недвижимости. Правка не должна затирать чужую (конкурентный доступ); конфликт с консюмером → retry администратором платформы с тем жеIdempotency-Key. - События. Доменные
BuildingDisplayOverrideChangedEvent,PropertyObjectCardRecomputedEvent(по объекту недвижимости),ProjectCardRecomputedEvent— фиксируются в том же коммите. Интеграционные события не публикуются (display-override.md, «События»). - Согласованность. Оверрайд + карточка проекта + карточки объектов недвижимости + отметка идемпотентности + audit — в одном согласованном изменении (INV-6 RecomputeInTx). Объём батча ограничен размером корпуса (сотни объектов недвижимости) — приемлемо для одной транзакции (ADR-0056).
- Результат.
BuildingOverrideMutationResult(200); отказ → ProblemDetails.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Проекции / поиск | пересчёт catalog.project_cards (эффективные поля, Status, NameVector по Name/Address) и всех catalog.property_object_cards корпуса (корпусные поля, Status) в той же транзакции |
Интеграционные события не публикуются (только доменные — фиксируются вместе с пересчётом); внешних вызовов и уведомлений нет.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Диапазоны/длины/заголовки | Ошибка валидации запроса | no retry |
CATALOG_OVERRIDE_EMPTY | 400 | Все override-поля null (INV-3) | Оверрайд не содержит ни одного переопределённого поля | no retry |
UNAUTHORIZED | 401 | Нет/просрочен m2m client-credentials токен | Требуется авторизация | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system / нет catalog.overrides.manage | Доступ запрещён | no retry |
CATALOG_OVERRIDE_TARGET_NOT_FOUND | 404 | Корпус отсутствует в витрине (INV-2) | Корпус не найден в каталоге | no retry |
CATALOG_OVERRIDE_NOT_FOUND | 404 | Delete-дельта: живого оверрайда нет | Оверрайд не найден | no retry |
CATALOG_OVERRIDE_TARGET_DELISTED | 409 | Проект корпуса Delisted | Проект удалён источником; правка невозможна | no retry |
CONCURRENCY_CONFLICT | 409 | ExpectedVersion не совпал или гонка версий | Данные изменены параллельно; обновите форму | retry с тем же Idempotency-Key |
Совместимость и наблюдаемость
- Новые optional override-поля — additive; удаление/переименование/смена типа — breaking (migration + rollout).
- Logs:
admin-building-override-upserted(buildingId, overrideId, created/removed, propertyObjectsRecomputed, adminUserId); metrics:catalog_admin_operations_total{operation="put-building-override"}, duration,catalog_projection_recompute_total{trigger="override"}(+ размер батча объектов недвижимости); traces: span приёма → span обработки → span обращения к хранилищу; correlation — сквозной trace черезtraceparent. Deep audit каждого вызова. Dashboard catalog-overview; runbook — rebuild проекций по id.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): Catalog Admin API — реестр методов и общие правила области, GET /api/admin/v1/buildings/{buildingId}/override — Просмотр оверрайда корпуса (admin), POST /api/admin/v1/projections/rebuild — Принудительный пересчёт карточек по id (admin), PUT /api/admin/v1/lots/{propertyObjectId}/override — Создание/изменение оверрайда объекта недвижимости (admin)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: display-override.md (INV-1..6), catalog-project-card.md (INV-1/2/5), catalog-property-object-card.md (INV-1/3).
- Чтение: get-building-override.md; объект недвижимости: put-property-object-override.md.
- ADR-0053, ADR-0056.