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

PUT /api/v1/developers/buildings/{buildingId} — Обновление здания

Назначение

Менеджер застройщика редактирует литер/корпус своей компании (полная замена редактируемых полей). Реплика-поля Talent при SyncSource = TalentChessboard менять нельзя (INV-BLD-7); уменьшение этажности ниже занятого объектами недвижимости этажа запрещено (INV-BLD-3). Дом полей — сущность Здание.

Metadata

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

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

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

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
BuildingIdrouteGuidДасуществующее здание tenant-а404 при чужом
NamebodystringДа1..128; уникально в проекте (INV-BLD-2); реплика-поле (INV-BLD-7)
FloorsTotalbodyshortДа1..200; >= max(PropertyObject.Floor) здания (INV-BLD-3); реплика-поле
SectionsCountbodyshort?Нет1..50; >= max(PropertyObject.Section); реплика-поле
DeliveryYearbodyshort?Нет2000..2100; парность с кварталом; реплика-поле
DeliveryQuarterbodyshort?Нет1..4; реплика-поле
ConstructionStatusbodyConstructionStatusДаenum; переход в Delivered — только методом MarkDelivered (INV-BLD-6): здесь запрещён; реплика-поле
ReadinessPercentbodydecimalДа0..100; реплика-поле
CeilingHeightMetersbodydecimal?Нет2..10 (PIN-поле)
VersionbodylongДа>= 1; optimistic-сверка
Sync-guard реплика-полей Talent (INV-BLD-7)

Пока SyncSource = TalentChessboard, поля Name, FloorsTotal, SectionsCount, DeliveryYear, DeliveryQuarter, ConstructionStatus, ReadinessPercent управляются синхронизацией Talent — правке через этот метод не подлежат, их значения в запросе должны совпадать с текущими, иначе CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED (409). Редактируемо всегда — PIN-поле CeilingHeightMeters.

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаBuildingEntity.IdId здания.
VersionlongДаVersion (после инкремента)Новая версия.
UpdatedAtDateTimeOffsetДа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 (цель — секунды). Компенсаций не требуется — снапшот полный и идемпотентен на стороне потребителя.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.buildings.manage.
  2. Проверка входа и предусловий.
    • формат/диапазоны полей — иначе 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).
  3. Применение правок. Набор мутируемых полей зависит от SyncSource (INV-BLD-7): всегда — высота потолков; при SyncSource = Pin дополнительно — имя, этажность (с guard INV-BLD-3), секции, срок сдачи, статус строительства, готовность. Проставляются UpdatedByUserId, инкремент Version.
  4. Факты и события. Публикуется событие pin.chessboard.building-upserted.v1 — полный снапшот после правки.
  5. Результат. Возвращаются Id, Version, UpdatedAt; HTTP 200.

Диаграмма

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

ТипДетали
Интеграционные событияBuildingUpsertedKafkaEventpin.chessboard.building-upserted.v1; потребители: catalog (карточка/фильтр «срок сдачи»), dev-stats

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Нарушение ограниченийСписок ошибок полейno retry
CHESSBOARD_BUILDING_NOT_FOUND404Нет здания / чужой tenant«Здание не найдено»no retry
CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED409Правка реплика-поля Talent-реплики (INV-BLD-7)«Поле управляется синхронизацией Talent»no retry
CHESSBOARD_BUILDING_NAME_TAKEN409Дубль имени (INV-BLD-2)«Название корпуса занято»no retry
CHESSBOARD_BUILDING_FLOORS_CONFLICT409Уменьшение этажности/секций ниже занятых объектами недвижимости (INV-BLD-3)«Есть объекты недвижимости выше новой этажности»no retry
CHESSBOARD_BUILDING_INVALID_DELIVERED_STATE409Попытка Delivered через update (INV-BLD-6)«Ввод в эксплуатацию — отдельная операция»no retry
CHESSBOARD_CONCURRENCY_CONFLICT409Version не совпал«Данные изменены параллельно»retry после перечитывания
FORBIDDEN403Нет 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), событие не публикуется.

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

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

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