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

POST /api/v1/developers/property-objects/{propertyObjectId}/unhide — Возврат объекта недвижимости в продажу

Назначение

Менеджер застройщика возвращает ранее скрытый объект недвижимости в продажу: переход Hidden → Free. Объект снова показывается зелёным в public-шахматке/каталоге и доступен к резервированию. Обратная операция к Hide. Дом полей и жизненный цикл — сущность Объект недвижимости.

Metadata

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

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

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

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
PropertyObjectIdrouteGuidДасуществующий объект недвижимости tenant-а404 при чужом/отсутствии
VersionbodylongДа>= 1; optimistic-сверка с PropertyObjectEntity.Versionзащита от гонки правки застройщика

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаPropertyObjectEntity.IdId объекта недвижимости.
StatusPropertyObjectStatusДаStatusВсегда Free после успеха.
VersionlongДаVersionНовая версия агрегата.
StatusChangedAtDateTimeOffsetДаStatusChangedAtМомент перехода Hidden → Free.

Предусловия

  • Пользователь авторизован; tenant_id = его компания-застройщик; есть permission chessboard.property-objects.manage в scope проекта.
  • Объект недвижимости существует, принадлежит tenant-у актора и не удалён.
  • Объект в статусе Hidden (единственный допустимый источник перехода, INV-CHS-001); Reserved/Booked/Sold вернуть в продажу через этот endpoint нельзя; уже Free — идемпотентный no-op при совпадении Version.
  • Version в теле равен текущей доменной версии агрегата (optimistic guard против затирания чужой правки).

Постусловия

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

  • Status = Free, StatusSource = DeveloperApi, StatusChangedAt = now, таймеры и HoldDealId пусты (INV-CHS-002), UpdatedByUserId проставлен, Version инкрементирован; паспортные поля (номер/этаж/площади/ цена/планировка) не изменены.
  • Опубликовано событие pin.chessboard.property-object-status-changed.v1 (Hidden → Free).
  • Запрошен фоновый пересчёт Project.PropertyObjectsFreeCount (project-aggregates-recalc, debounce 30 с).
  • Объект снова показывается зелёным в public-шахматке/каталоге (после обработки события catalog) и доступен к резервированию.

При идемпотентном повторе (объект уже Free, Version совпадает): состояние не меняется, новое событие/ пересчёт не порождаются.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.property-objects.manage в scope проекта. Нет permission в scope → FORBIDDEN (403). Если объект уже Free (при совпадении Version) — идемпотентный no-op: возвращается текущее состояние без нового события/пересчёта.
  2. Проверка входа и предусловий.
    • Version >= 1 — иначе ValidationError;
    • объект недвижимости существует в пределах tenant-а — иначе CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND (404);
    • переход разрешён только из Hidden (INV-CHS-001: Developers API оперирует лишь Free ↔ Hidden); текущий Status ∉ {Hidden, Free}CHESSBOARD_PROPERTY_OBJECT_INVALID_STATUS_TRANSITION (409); Reserved/Booked/Sold вернуть в продажу через этот endpoint нельзя (release/продажа — только события CRM / job истечения);
    • Version совпадает с текущей — иначе CHESSBOARD_CONCURRENCY_CONFLICT (409).
  3. Переход статуса. Объект переводится Hidden → Free; таймеры и HoldDealId остаются пустыми (INV-CHS-002); StatusSource = DeveloperApi, StatusChangedAt = now; прочие поля (номер/этаж/площади/цена/планировка) не трогаются; проставляются UpdatedByUserId, инкремент Version.
  4. Факты и события. Публикуется событие pin.chessboard.property-object-status-changed.v1 (FromStatus = Hidden, ToStatus = Free, StatusSource = DeveloperApi, таймеры пусты, StatusChangedAt, Version). Запрашивается фоновый пересчёт Project.PropertyObjectsFreeCount (project-aggregates-recalc, debounce 30 с).
  5. Результат. Возвращаются Id, Status = Free, Version, StatusChangedAt; HTTP 200.

Диаграмма

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

ТипДетали
Интеграционные событияPropertyObjectStatusChangedKafkaEvent (Hidden → Free) → pin.chessboard.property-object-status-changed.v1; потребители: catalog (объект недвижимости снова зелёный в шахматке/витрине), favorites/selections (снятие пометки недоступности), notifications
Проекции / пересчётProjectAggregatesRecalcRequestedEvent → фоновый пересчёт Project.PropertyObjectsFreeCount (debounce 30 с); объект недвижимости появляется в public-проекциях (фильтр Status != Hidden в query-слое)

Интеграционная последовательность и согласованность. Переход и событие атомарны (outbox). Потребители асинхронны: catalog возвращает объект в публичные срезы < 1 мин, favorites/selections/notifications снимают пометку недоступности < 5 мин (SLA property-object-status-changed.md). В окне eventual-consistency публичная витрина может кратковременно ещё не показывать объект; застройщик видит Free сразу (read-after-write своего tenant). Возврат объекта в продажу не воссоздаёт таймеры/холды — они остаются пустыми до нового CRM-события резервирования.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND404Нет объекта недвижимости / чужой tenant«Объект не найден»no retry
CHESSBOARD_PROPERTY_OBJECT_INVALID_STATUS_TRANSITION409Текущий Status не Hidden (Reserved/Booked/Sold) — недопустимый переход (INV-CHS-001)«Недопустимый переход статуса»no retry
CHESSBOARD_CONCURRENCY_CONFLICT409Version не совпал«Данные изменены параллельно»retry после перечитывания
ValidationError400Version < 1Список ошибок полейno retry
FORBIDDEN403Нет permission chessboard.property-objects.manage в scope«Недостаточно прав»no retry

UNAUTHORIZED (401) и сквозные ошибки — по таблице сквозных ошибок области README.md.

Acceptance Criteria

  • Happy path. Given объект в Hidden, корректный Version; When POST .../unhide; Then 200, Status = Free, Version инкрементирован, событие Hidden → Free опубликовано, запрошен пересчёт FreeCount.
  • Идемпотентный повтор. Given объект уже Free, Version совпадает; When повторный POST .../unhide; Then 200 с текущим состоянием, без нового события/пересчёта.
  • Недопустимый переход. Given объект в Reserved/Booked/Sold; When POST .../unhide; Then 409 CHESSBOARD_PROPERTY_OBJECT_INVALID_STATUS_TRANSITION (INV-CHS-001), состояние не изменено.
  • Гонка версий. Given Version в теле не совпал с текущим (параллельная правка/CRM-событие); When POST .../unhide; Then 409 CHESSBOARD_CONCURRENCY_CONFLICT; проигравший перечитывает и повторяет.
  • Не найден. Given propertyObjectId чужого tenant-а или отсутствует; When POST .../unhide; Then 404 CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND.
  • Невалидный вход. Given Version < 1; When POST .../unhide; Then 400 ValidationError.
  • Нет права. Given нет chessboard.property-objects.manage; When POST .../unhide; Then 403 FORBIDDEN.

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

  • Контракт стабилен; операция симметрична hide. Идемпотентна по целевому статусу (Free). Изменение состава payload PropertyObjectStatusChangedKafkaEvent — по правилам версионирования property-object-status-changed.md (additive-only без bump; breaking → .v2).
  • Logs: TenantId, ProjectId, PropertyObjectId, Hidden → Free, StatusSource = DeveloperApi; metrics: chessboard_property_object_status_transition_total{from,to,source}; traces — OTel (traceparent); dashboard chessboard-overview.

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

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

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