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

POST /api/v1/developers/buildings/{buildingId}/status — Смена статуса здания

Назначение

Менеджер застройщика управляет видимостью литера/корпуса в шахматке и ассортименте published-проекта: вводит корпус в продажу (Publish), возвращает на доработку (Unpublish), временно скрывает очередь не в продаже (Hide) и возвращает её (Unhide). Конкретный переход задаётся полем Action в теле запроса — один endpoint вместо четырёх. Дом полей и жизненный цикл — сущность Здание.

Metadata

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

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

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

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/developers/buildings/{buildingId}/status
Success status200
Idempotency headerне применяется — повтор того же Action при уже достигнутом целевом статусе идемпотентен (no-op)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
BuildingIdrouteGuidДасуществующее здание tenant-а404 при чужом
ActionbodyBuildingStatusAction (enum-string)ДаPublish / Unpublish / Hide / Unhide; прочее → ValidationError
VersionbodylongДа>= 1; optimistic-сверка с BuildingEntity.Version

Enum BuildingStatusAction → матрица переходов

примечание
Action — тип-контракт запроса, не доменный enum

Однозначно задаёт переход From → To. Целевой BuildingStatus выводится из Action (не принимается отдельным полем, чтобы исключить неоднозначность → Published между Publish и Unhide).

ActionДопустимый FromToGuard
PublishDraftPublishedINV-BLD-4 (полнота, ниже)
UnpublishPublishedDraft
HidePublishedHidden
UnhideHiddenPublished— (данные уже полны)

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаBuildingEntity.IdId здания.
ProjectIdGuidДаProjectIdПроект-родитель.
StatusBuildingStatusДаStatusНовый статус (Draft/Published/Hidden).
VersionlongДаVersionНовая версия.
UpdatedAtDateTimeOffsetДаUpdatedAtМомент перехода.

Предусловия

  • Пользователь авторизован; tenant_id = его компания-застройщик; есть permission chessboard.buildings.manage в scope проекта.
  • Здание существует, принадлежит tenant-у актора и не удалено.
  • Version в теле равен текущей доменной версии здания (optimistic guard).
  • Текущий Status входит в «Допустимый From» для выбранного Action (матрица переходов выше); для Action = Publish дополнительно выполнены guard-ы полноты INV-BLD-4 (проект-родитель Status ∈ {Draft, Published}, заполнены FloorsTotal, DeliveryYear, DeliveryQuarter).
  • Если здание уже в целевом статусе для Action — операция идемпотентна (no-op).

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.buildings.manage в scope проекта. Нет permission в scope → FORBIDDEN (403). Если статус уже равен целевому для Action — идемпотентный no-op: возвращается текущее состояние без нового события.
  2. Проверка входа и предусловий.
    • Action ∈ {Publish, Unpublish, Hide, Unhide}, Version >= 1 — иначе ValidationError;
    • здание существует в пределах tenant-а — иначе CHESSBOARD_BUILDING_NOT_FOUND (404, чужой tenant неотличим от отсутствия);
    • текущий статус входит в «Допустимый From» для Action (матрица выше) — иначе CHESSBOARD_BUILDING_INVALID_STATUS_TRANSITION (409);
    • для Action = Publish (INV-BLD-4): проект-родитель имеет Status ∈ {Draft, Published} и у здания заполнены FloorsTotal, DeliveryYear, DeliveryQuarter; иначе CHESSBOARD_BUILDING_PUBLISH_INCOMPLETE (409, metadata reason = ProjectNotPublishable или missingFields = [...]);
    • Version совпадает с текущей — иначе CHESSBOARD_CONCURRENCY_CONFLICT (409).
  3. Переход статуса. Здание переводится в целевой статус по Action (матрица From → To); guard-ы переходов и INV-BLD-4 продублированы в проверке. Меняется только Status; проставляются UpdatedByUserId, инкремент Version; прочие поля не трогаются.
  4. Факты и события. Публикуется событие pin.chessboard.building-upserted.v1 — полный снапшот с новым Status. Отдельного status-топика у здания нет: потребители читают Status из снапшота.
  5. Результат. Возвращаются Id, ProjectId, Status, Version, UpdatedAt; HTTP 200.

Диаграмма

Постусловия

При успешном переходе:

  • Status = целевой по Action (матрица From → To); UpdatedByUserId проставлен, Version инкрементирован; прочие поля здания не изменены; объекты недвижимости здания не мутируются.
  • Опубликовано pin.chessboard.building-upserted.v1 — полный снапшот с новым Status (отдельного status-топика у здания нет).
  • В публичной шахматке: Publish/Unhide — корпус и его Published объекты недвижимости появляются; Unpublish/Hide — выпадают (фильтр Status = Published в query-слое catalog, после обработки события).

При идемпотентном повторе (статус уже целевой): состояние не меняется, новое событие не порождается.

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

ТипДетали
Интеграционные событияBuildingUpsertedKafkaEvent (новый Status) → pin.chessboard.building-upserted.v1; потребители: catalog (карточка/фильтр «срок сдачи», включение/скрытие корпуса в шахматке), dev-stats (измерение Building)
Проекции / поискPublish/Unhide — корпус и его Published объекты недвижимости появляются в public-шахматке; Unpublish/Hide — выпадают (фильтр Status = Published в query-слое; объекты недвижимости не мутируются)

Интеграционная последовательность и согласованность. Переход и публикация снапшота атомарны (outbox). catalog обновляет карточку/фильтр «срок сдачи» и включение/скрытие корпуса в шахматке асинхронно; dev-stats обновляет измерение Building. Building.PropertyObjects*Count в событие не входят (дом building-upserted.md) — счётчики потребители выводят из property-object-потока. В окне eventual-consistency публичная шахматка может кратко отражать прежнее состояние корпуса; застройщик видит новый статус в кабинете сразу (read-after-write). Порядок снапшотов по зданию гарантирован key-ом Id; потребители — upsert-by-id с монотонным Version-guard.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Action вне enum / Version < 1Список ошибок полейno retry
CHESSBOARD_BUILDING_NOT_FOUND404Нет здания / чужой tenant«Здание не найдено»no retry
CHESSBOARD_BUILDING_INVALID_STATUS_TRANSITION409Текущий Status не в «Допустимый From» для Action«Недопустимый переход статуса»no retry
CHESSBOARD_BUILDING_PUBLISH_INCOMPLETE409INV-BLD-4: Project.Status ∉ {Draft, Published} или не заполнены FloorsTotal/DeliveryYear/DeliveryQuarter; metadata reason/missingFields«Корпус не готов к публикации»no retry (заполнить поля)
CHESSBOARD_CONCURRENCY_CONFLICT409Version не совпал«Данные изменены параллельно»retry после перечитывания
FORBIDDEN403Нет permission«Недостаточно прав»no retry

Acceptance Criteria

  • Publish. Given здание Draft полно по INV-BLD-4, проект-родитель Draft/Published, корректный Version; When Action = Publish; Then 200, Status = Published, снапшот building-upserted опубликован.
  • Publish неполон. Given Action = Publish, но не заполнены FloorsTotal/DeliveryYear/DeliveryQuarter или проект вне {Draft, Published}; When запрос; Then 409 CHESSBOARD_BUILDING_PUBLISH_INCOMPLETE с missingFields/reason; состояние не изменено.
  • Unpublish / Hide / Unhide. Given здание в допустимом From для Action; When соответствующий Action; Then 200 с целевым Status по матрице (Published→Draft / Published→Hidden / Hidden→Published).
  • Недопустимый переход. Given текущий Status вне «Допустимый From» для Action (например Hide из Draft); When запрос; Then 409 CHESSBOARD_BUILDING_INVALID_STATUS_TRANSITION.
  • Идемпотентный повтор. Given здание уже в целевом для Action статусе; When повторный запрос; Then 200 с текущим состоянием, без нового события.
  • Гонка версий. Given Version не совпал; When запрос; Then 409 CHESSBOARD_CONCURRENCY_CONFLICT.
  • Невалидный вход. Given Action вне enum или Version < 1; When запрос; Then 400 ValidationError.
  • Не найден / нет права. Given здание чужого tenant-а → 404 CHESSBOARD_BUILDING_NOT_FOUND; нет permission → 403 FORBIDDEN.

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

  • Контракт стабилен; добавление нового Action — additive (потребители имеют fallback на unknown enum, правило ../domain/reference-data.md); удаление/переименование значения — breaking. MarkDelivered (ConstructionStatus = Delivered) — отдельная операция, не через этот endpoint.
  • Logs: TenantId, ProjectId, BuildingId, Action, From → To; metrics: chessboard_building_status_transition_total{action,from,to}; traces: OTel (traceparent).

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

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

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