PUT /api/v1/developers/buildings/{buildingId} — Обновление здания
Назначение
Менеджер застройщика редактирует литер/корпус своей компании (полная замена редактируемых полей).
Реплика-поля Talent при SyncSource = TalentChessboard менять нельзя (INV-BLD-7); уменьшение этажности
ниже занятого объектами недвижимости этажа запрещено (INV-BLD-3). Дом полей — сущность
Здание.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | buildings |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/update-building.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.buildings.manage |
| Scope (RBAC) | организация / группа проектов / проект |
| Tenant isolation | TenantId = CompanyId из JWT; чужое здание → 404 |
| Audit | обновление: UpdatedByUserId, инкремент Version |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | PUT |
| Route | /api/v1/developers/buildings/{buildingId} |
| Success status | 200 |
| Idempotency header | не применяется (PUT + Version) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
BuildingId | route | Guid | Да | существующее здание tenant-а | 404 при чужом |
Name | body | string | Да | 1..128; уникально в проекте (INV-BLD-2); реплика-поле (INV-BLD-7) | — |
FloorsTotal | body | short | Да | 1..200; >= max(PropertyObject.Floor) здания (INV-BLD-3); реплика-поле | — |
SectionsCount | body | short? | Нет | 1..50; >= max(PropertyObject.Section); реплика-поле | — |
DeliveryYear | body | short? | Нет | 2000..2100; парность с кварталом; реплика-поле | — |
DeliveryQuarter | body | short? | Нет | 1..4; реплика-поле | — |
ConstructionStatus | body | ConstructionStatus | Да | enum; переход в Delivered — только методом MarkDelivered (INV-BLD-6): здесь запрещён; реплика-поле | — |
ReadinessPercent | body | decimal | Да | 0..100; реплика-поле | — |
CeilingHeightMeters | body | decimal? | Нет | 2..10 (PIN-поле) | — |
Version | body | long | Да | >= 1; optimistic-сверка | — |
Пока SyncSource = TalentChessboard, поля Name, FloorsTotal, SectionsCount, DeliveryYear,
DeliveryQuarter, ConstructionStatus, ReadinessPercent управляются синхронизацией Talent — правке через
этот метод не подлежат, их значения в запросе должны совпадать с текущими, иначе
CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED (409). Редактируемо всегда — PIN-поле CeilingHeightMeters.
Модель ответа UpdateDeveloperBuildingResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | BuildingEntity.Id | — | Id здания. |
Version | long | Да | Version (после инкремента) | — | Новая версия. |
UpdatedAt | DateTimeOffset | Да | UpdatedAt | — | Момент сохранения. |
Предусловия и постусловия
Предусловия:
- Актор авторизован и имеет
chessboard.buildings.manageв scope, покрывающем проект здания. - Здание существует в tenant актора и не удалено.
- Клиент располагает актуальным
Versionздания. - Реплика-поля Talent редактируемы только когда
SyncSource = Pin; приSyncSource = TalentChessboardих значения совпадают с текущими (иначе будут отклонены sync-guard-ом).
Постусловия при успехе:
- Обновлён набор полей, разрешённый
SyncSource(приPin— все; приTalentChessboard— только PIN-полеCeilingHeightMeters);Version+1, проставленыUpdatedByUserId/UpdatedAt. - Инвариант уникальности имени в проекте (INV-BLD-2) и «этажность/секции ≥ занятых объектами недвижимости»
(INV-BLD-3) не нарушены; статус
ConstructionStatusне переведён вDelivered(INV-BLD-6 — отдельная операция). - В том же согласованном изменении поставлено на публикацию
pin.chessboard.building-upserted.v1с полным снапшотом здания после правки.
Постусловие при отказе: здание не изменено, событие не публикуется, Version сохранён.
Интеграционная последовательность
- Синхронно возвращается
200+Version/UpdatedAt; синхронных вызовов соседних сервисов нет. - Асинхронно
pin.chessboard.building-upserted.v1(партиционный ключIdздания, порядок событий по зданию гарантирован) потребляютcatalog(карточка/фильтр «срок сдачи») иdev-stats. Потребители применяют снапшоты монотонно поVersion; переупорядочивание/дубли безопасны. - Ожидания от других контекстов: реплика-поля здания при
SyncSource = TalentChessboardведёт consumer синка (talent-chessboard-sync) — эта операция их не трогает, поэтому конфликта «кто владелец поля» нет. - Окно eventual-consistency: «срок сдачи»/готовность на витрине
catalogобновляются с лагом consumer (цель — секунды). Компенсаций не требуется — снапшот полный и идемпотентен на стороне потребителя.
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.buildings.manage. - Проверка входа и предусловий.
- формат/диапазоны полей — иначе
ValidationError; - здание существует в пределах tenant-а — иначе
CHESSBOARD_BUILDING_NOT_FOUND(404); - здание не удалено; попытка
ConstructionStatus = Deliveredэтим методом →CHESSBOARD_BUILDING_INVALID_DELIVERED_STATE(409, ввод в эксплуатацию — отдельная операция, INV-BLD-6); - sync-guard (INV-BLD-7): при
SyncSource = TalentChessboardлюбое отличие входных реплика-полей (Name,FloorsTotal,SectionsCount,Delivery*,ConstructionStatus,ReadinessPercent) от текущих →CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED(409); - при изменении имени — оно уникально в проекте среди не удалённых (INV-BLD-2), иначе
CHESSBOARD_BUILDING_NAME_TAKEN(409); - при уменьшении
FloorsTotal/SectionsCount— нет объектов недвижимости выше новой границы (INV-BLD-3), иначеCHESSBOARD_BUILDING_FLOORS_CONFLICT(409); Versionсовпадает с текущей — иначеCHESSBOARD_CONCURRENCY_CONFLICT(409).
- формат/диапазоны полей — иначе
- Применение правок. Набор мутируемых полей зависит от
SyncSource(INV-BLD-7): всегда — высота потолков; приSyncSource = Pinдополнительно — имя, этажность (с guard INV-BLD-3), секции, срок сдачи, статус строительства, готовность. ПроставляютсяUpdatedByUserId, инкрементVersion. - Факты и события. Публикуется событие
pin.chessboard.building-upserted.v1— полный снапшот после правки. - Результат. Возвращаются
Id,Version,UpdatedAt; HTTP200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | BuildingUpsertedKafkaEvent → pin.chessboard.building-upserted.v1; потребители: catalog (карточка/фильтр «срок сдачи»), dev-stats |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Нарушение ограничений | Список ошибок полей | no retry |
CHESSBOARD_BUILDING_NOT_FOUND | 404 | Нет здания / чужой tenant | «Здание не найдено» | no retry |
CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED | 409 | Правка реплика-поля Talent-реплики (INV-BLD-7) | «Поле управляется синхронизацией Talent» | no retry |
CHESSBOARD_BUILDING_NAME_TAKEN | 409 | Дубль имени (INV-BLD-2) | «Название корпуса занято» | no retry |
CHESSBOARD_BUILDING_FLOORS_CONFLICT | 409 | Уменьшение этажности/секций ниже занятых объектами недвижимости (INV-BLD-3) | «Есть объекты недвижимости выше новой этажности» | no retry |
CHESSBOARD_BUILDING_INVALID_DELIVERED_STATE | 409 | Попытка Delivered через update (INV-BLD-6) | «Ввод в эксплуатацию — отдельная операция» | no retry |
CHESSBOARD_CONCURRENCY_CONFLICT | 409 | Version не совпал | «Данные изменены параллельно» | retry после перечитывания |
FORBIDDEN | 403 | Нет permission | «Недостаточно прав» | no retry |
Совместимость и наблюдаемость
- Новые optional поля — additive; операция
MarkDelivered— отдельный будущий метод. - Logs:
TenantId,BuildingId, изменённые поля; metrics:chessboard_developers_building_update_total{result}; traces: OTel.
Acceptance Criteria
- AC1 (правка PIN-полей). Дано: здание с
SyncSource = Pinи актуальныйVersion. Когда: меняются имя, этажность (не ниже занятых этажей), готовность. Тогда: поля обновлены,Version+1, публикуетсяpin.chessboard.building-upserted.v1с новым снапшотом. - AC2 (sync-guard реплики). Дано:
SyncSource = TalentChessboard. Когда: входнойName/FloorsTotal/… отличается от текущего. Тогда:CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED(409); правка толькоCeilingHeightMetersпри совпадающих реплика-полях проходит. - AC3 (дубль имени). Дано: в проекте уже есть другое не удалённое здание с тем же именем. Когда:
переименование. Тогда:
CHESSBOARD_BUILDING_NAME_TAKEN(409). - AC4 (уменьшение этажности ниже занятой). Дано: есть объект недвижимости на этаже выше новой границы.
Когда: уменьшение
FloorsTotal/SectionsCount. Тогда:CHESSBOARD_BUILDING_FLOORS_CONFLICT(409). - AC5 (запрет Delivered через update). Дано: любой корпус. Когда:
ConstructionStatus = Deliveredв теле. Тогда:CHESSBOARD_BUILDING_INVALID_DELIVERED_STATE(409) — ввод в эксплуатацию отдельной операцией (INV-BLD-6). - AC6 (конкурентная правка). Дано: устаревший
Version. Когда: сохранение. Тогда:CHESSBOARD_CONCURRENCY_CONFLICT(409); проигравший перечитывает и повторяет. - AC7 (чужой tenant). Дано: здание другого застройщика. Когда: операция. Тогда:
CHESSBOARD_BUILDING_NOT_FOUND(404), событие не публикуется.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): Chessboard API — реестр операций областей Developers и Admin, GET /api/v1/developers/buildings/{buildingId} — Карточка здания, POST /api/v1/developers/buildings/{buildingId}/status — Смена статуса здания, POST /api/v1/developers/projects/{projectId}/buildings — Создание здания
- События (1): Event Contract: pin.chessboard.building-upserted (BuildingUpsertedKafkaEvent)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../domain/building.md (INV-BLD-2/3/6/7); объекты недвижимости: ../domain/property-object.md.
- Событие: ../events/building-upserted.md (снапшот, партиционный ключ, версионирование).
- Статусы: set-building-status.md; реестр: README.md.