POST /api/v1/developers/property-objects/{propertyObjectId}/unhide — Возврат объекта недвижимости в продажу
Назначение
Менеджер застройщика возвращает ранее скрытый объект недвижимости в продажу: переход Hidden → Free. Объект
снова показывается зелёным в public-шахматке/каталоге и доступен к резервированию. Обратная операция к
Hide. Дом полей и жизненный цикл — сущность
Объект недвижимости.
| Поле | Значение |
|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | propertyObjects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/unhide-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}/unhide |
| Success status | 200 |
| Idempotency header | не применяется — повтор при уже достигнутом Free идемпотентен (no-op) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
PropertyObjectId | route | Guid | Да | существующий объект недвижимости tenant-а | 404 при чужом/отсутствии |
Version | body | long | Да | >= 1; optimistic-сверка с PropertyObjectEntity.Version | защита от гонки правки застройщика |
Модель ответа UnhideDeveloperPropertyObjectResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | PropertyObjectEntity.Id | — | Id объекта недвижимости. |
Status | PropertyObjectStatus | Да | Status | — | Всегда Free после успеха. |
Version | long | Да | Version | — | Новая версия агрегата. |
StatusChangedAt | DateTimeOffset | Да | 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 совпадает): состояние не меняется, новое событие/
пересчёт не порождаются.
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.property-objects.manage в scope проекта. Нет permission в scope → FORBIDDEN (403). Если
объект уже Free (при совпадении Version) — идемпотентный no-op: возвращается текущее состояние без
нового события/пересчёта.
- Проверка входа и предусловий.
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).
- Переход статуса. Объект переводится
Hidden → Free; таймеры и HoldDealId остаются пустыми
(INV-CHS-002); StatusSource = DeveloperApi, StatusChangedAt = now; прочие поля
(номер/этаж/площади/цена/планировка) не трогаются; проставляются UpdatedByUserId, инкремент Version.
- Факты и события. Публикуется событие
pin.chessboard.property-object-status-changed.v1
(FromStatus = Hidden, ToStatus = Free, StatusSource = DeveloperApi, таймеры пусты,
StatusChangedAt, Version). Запрашивается фоновый пересчёт Project.PropertyObjectsFreeCount
(project-aggregates-recalc, debounce 30 с).
- Результат. Возвращаются
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_FOUND | 404 | Нет объекта недвижимости / чужой tenant | «Объект не найден» | no retry |
CHESSBOARD_PROPERTY_OBJECT_INVALID_STATUS_TRANSITION | 409 | Текущий Status не Hidden (Reserved/Booked/Sold) — недопустимый переход (INV-CHS-001) | «Недопустимый переход статуса» | no retry |
CHESSBOARD_CONCURRENCY_CONFLICT | 409 | Version не совпал | «Данные изменены параллельно» | retry после перечитывания |
ValidationError | 400 | Version < 1 | Список ошибок полей | no retry |
FORBIDDEN | 403 | Нет 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.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы