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

POST /api/v1/developers/property-objects/{propertyObjectId}/hide — Скрытие объекта недвижимости

Назначение

Менеджер застройщика временно снимает свободный объект недвижимости с продажи (перепланировка, спор, придержать до следующей очереди): переход Free → Hidden. Скрытый объект исчезает из публичных проекций (витрина/шахматка catalog), но данные сохраняются; вернуть в продажу — обратной операцией Unhide (Hidden → Free). Дом полей и жизненный цикл — сущность Объект недвижимости.

Metadata

ПолеЗначение
Сервис/контекстchessboard
API areadevelopers
Feature grouppropertyObjects
Статусdraft
Документdocs/06-services/chessboard/api/developers/hide-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}/hide
Success status200
Idempotency headerне применяется — повтор при уже Hidden идемпотентен (no-op 200)
Correlationсквозной trace через заголовок traceparent

Тело запроса пустое: операция полностью описывается маршрутом (propertyObjectId) и identity-контекстом.

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

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

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

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

Предусловия

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

Постусловия

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

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

Если объект уже находится в Hidden — операция идемпотентна: 200 с текущим состоянием, Version не меняется, новое событие/пересчёт не порождаются.

Алгоритм

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

Диаграмма

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

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

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

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400PropertyObjectId == Guid.EmptyСписок ошибок полейno retry
CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND404Нет объекта недвижимости / чужой tenant«Объект не найден»no retry
CHESSBOARD_PROPERTY_OBJECT_HIDE_FORBIDDEN409Status ∈ {Reserved, Booked, Sold} (INV-CHS-009)«Занятый объект нельзя снять с продажи»no retry
CHESSBOARD_CONCURRENCY_CONFLICT409Гонка с событием CRM при сохранении«Данные изменены параллельно»retry после перечитывания
FORBIDDEN403Нет chessboard.property-objects.manage«Недостаточно прав»no retry

Acceptance Criteria

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

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

  • Контракт стабилен; обратная операция Unhide (Hidden → Free) — unhide-property-object.md. Событие статуса версионируется отдельно (event-contract, суффикс .v1).
  • Logs: TenantId, PropertyObjectId, Free → Hidden, StatusSource = DeveloperApi; metrics: chessboard_property_object_status_transition_total{from=Free,to=Hidden,source=DeveloperApi}; traces — OTel (traceparent); dashboard chessboard-overview.

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

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

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