POST /api/v1/developers/property-objects/{propertyObjectId}/hide — Скрытие объекта недвижимости
Назначение
Менеджер застройщика временно снимает свободный объект недвижимости с продажи (перепланировка, спор,
придержать до следующей очереди): переход Free → Hidden. Скрытый объект исчезает из публичных проекций
(витрина/шахматка catalog), но данные сохраняются; вернуть в продажу — обратной операцией
Unhide (Hidden → Free). Дом полей и жизненный цикл — сущность
Объект недвижимости.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | propertyObjects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/hide-property-object.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.property-objects.manage |
| Scope (RBAC) | организация / группа проектов / проект |
| Tenant isolation | TenantId = CompanyId из JWT (ADR-0052); чужой объект недвижимости → 404 |
| Audit | обновление: UpdatedByUserId, инкремент Version, StatusSource = DeveloperApi, StatusChangedAt |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/developers/property-objects/{propertyObjectId}/hide |
| Success status | 200 |
| Idempotency header | не применяется — повтор при уже Hidden идемпотентен (no-op 200) |
| Correlation | сквозной trace через заголовок traceparent |
Тело запроса пустое: операция полностью описывается маршрутом (propertyObjectId) и identity-контекстом.
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation (ADR-0052) |
PropertyObjectId | route | Guid | Да | != Guid.Empty; существующий объект недвижимости tenant-а | 404 при чужом / несуществующем |
Модель ответа HideDeveloperPropertyObjectResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PropertyObjectEntity.Id | — | Id объекта недвижимости. |
Status | PropertyObjectStatus | Да | PropertyObjectEntity.Status | — | Hidden (enum строкой). |
Version | long | Да | PropertyObjectEntity.Version | — | Новая доменная версия агрегата. |
StatusChangedAt | DateTimeOffset | Да | PropertyObjectEntity.StatusChangedAt | — | Момент перехода Free → Hidden. |
Предусловия
- Пользователь авторизован;
tenant_id= его компания-застройщик; есть permissionchessboard.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 не
меняется, новое событие/пересчёт не порождаются.
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.property-objects.manage. Нет permission в scope →FORBIDDEN(403). Если объект ужеHidden— идемпотентный no-op: возвращается текущее состояние без нового события/пересчёта. - Проверка входа и предусловий.
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.
- Переход статуса. Объект переводится
Free → Hidden(StatusTransitionGuard INV-CHS-001:DeveloperApiоперирует толькоFree ↔ Hidden);StatusSource = DeveloperApi,StatusChangedAt = now, таймеры иHoldDealIdостаются пустыми (INV-CHS-002); проставляютсяUpdatedByUserId, инкрементVersion. Правка не должна затирать параллельное CRM-событие статуса — при конфликте версииCHESSBOARD_CONCURRENCY_CONFLICT. - Факты и события. Публикуется событие
pin.chessboard.property-object-status-changed.v1(FromStatus = Free,ToStatus = Hidden,StatusSource = DeveloperApi, таймеры пусты,StatusChangedAt,Version). Запрашивается фоновый пересчётProject.PropertyObjectsFreeCount(project-aggregates-recalc, debounce 30 с). - Результат. Возвращаются
Id,Status = Hidden,Version,StatusChangedAt; HTTP200. Идемпотентный повтор — тот же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 |
|---|---|---|---|---|
ValidationError | 400 | PropertyObjectId == Guid.Empty | Список ошибок полей | no retry |
CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND | 404 | Нет объекта недвижимости / чужой tenant | «Объект не найден» | no retry |
CHESSBOARD_PROPERTY_OBJECT_HIDE_FORBIDDEN | 409 | Status ∈ {Reserved, Booked, Sold} (INV-CHS-009) | «Занятый объект нельзя снять с продажи» | no retry |
CHESSBOARD_CONCURRENCY_CONFLICT | 409 | Гонка с событием CRM при сохранении | «Данные изменены параллельно» | retry после перечитывания |
FORBIDDEN | 403 | Нет chessboard.property-objects.manage | «Недостаточно прав» | no retry |
Acceptance Criteria
- Happy path. Given объект в
Free; WhenPOST .../hide; Then200,Status = Hidden,StatusSource = DeveloperApi,Versionинкрементирован, событиеFree → Hiddenопубликовано, запрошен пересчётFreeCount. - Идемпотентный повтор. Given объект уже
Hidden; When повторныйPOST .../hide; Then200с текущим состоянием, без нового события и без повторного пересчёта. - Занятый объект. Given объект в
Reserved/Booked/Sold; WhenPOST .../hide; Then409CHESSBOARD_PROPERTY_OBJECT_HIDE_FORBIDDEN(INV-CHS-009), состояние не изменено. - Гонка с CRM. Given параллельно применяется CRM-событие статуса того же объекта; When сохранение
перехода конфликтует по версии; Then
409CHESSBOARD_CONCURRENCY_CONFLICT; проигравший перечитывает и повторяет. - Не найден. Given
propertyObjectIdчужого tenant-а или отсутствует; WhenPOST .../hide; Then404CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND. - Нет права. Given нет
chessboard.property-objects.manage; WhenPOST .../hide; Then403FORBIDDEN.
Совместимость и наблюдаемость
- Контракт стабилен; обратная операция 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); dashboardchessboard-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): Chessboard API — реестр операций областей Developers и Admin, POST /api/v1/developers/property-objects/{propertyObjectId}/unhide — Возврат объекта недвижимости в продажу, PUT /api/v1/developers/property-objects/{propertyObjectId} — Обновление объекта недвижимости (паспортные поля)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность / инварианты / жизненный цикл: ../domain/property-object.md
(INV-CHS-009 HideGuard, INV-CHS-001 StatusTransitionGuard,
PropertyObjectStatus); ../domain/reference-data.md (PropertyObjectStatusSource.DeveloperApi). - Событие: ../events/property-object-status-changed.md
(
pin.chessboard.property-object-status-changed.v1). - Обратная операция: unhide-property-object.md; реестр: README.md.