GET /api/v1/developers/property-objects/{propertyObjectId} — Карточка объекта недвижимости (кабинет застройщика)
Назначение
Экран карточки/редактирования объекта недвижимости в кабинете застройщика. Возвращает полное состояние объекта своего tenant-а без маскирования — в отличие от публичной карточки public/property-objects/get-property-object.md, скрывающей цену/статус от анонима, и от списка объектов. Дом полей — сущность Объект недвижимости.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | propertyObjects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/get-property-object.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.property-objects.manage |
| Scope (RBAC) | организация / группа проектов / проект (propertyObject.ProjectId в scope) |
| Tenant isolation | TenantId = CompanyId = DeveloperId из JWT; чужой объект недостижим → 404 (существование чужой записи не раскрывается) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/developers/property-objects/{propertyObjectId} |
| Success status | 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation (= DeveloperId) |
PropertyObjectId | route | Guid | Да | Required, != Guid.Empty, существующий объект недвижимости tenant-а | 404 при чужом/отсутствующем |
Модель ответа GetDeveloperPropertyObjectResult
Все поля — проекция PropertyObjectEntity (дом полей), без дельты типов;
масштаб — свой tenant (видны и Hidden, и цена/статус/таймеры/HoldDealId), маскирование не применяется.
| Поле | Тип | Обязательность | Источник | Описание |
|---|---|---|---|---|
Id | Guid | Да | PropertyObject.Id | Идентификатор объекта недвижимости. |
TenantId | Guid | Да | PropertyObject.TenantId | Застройщик (= DeveloperId, ADR-0052). |
ProjectId | Guid | Да | PropertyObject.ProjectId | Проект (денормализация). |
BuildingId | Guid | Да | PropertyObject.BuildingId | Здание (литер/корпус). |
LayoutId | Guid? | Нет | PropertyObject.LayoutId | Планировка (layout.md); null — не привязана. |
Status | PropertyObjectStatus (string) | Да | PropertyObject.Status | Free/Reserved/Booked/Sold/Hidden (reference-data.md). |
StatusSource | PropertyObjectStatusSource (string) | Да | PropertyObject.StatusSource | Кто выполнил переход: Crm/TalentSync/DeveloperApi/ExpirationJob. |
StatusChangedAt | DateTimeOffset | Да | PropertyObject.StatusChangedAt | Момент последнего перехода Status. |
ReservedUntil | DateTimeOffset? | Нет | PropertyObject.ReservedUntil | Таймер резерва (⇔ Reserved, INV-CHS-002). |
BookedUntil | DateTimeOffset? | Нет | PropertyObject.BookedUntil | Таймер брони (⇔ Booked). |
HoldDealId | Guid? | Нет | PropertyObject.HoldDealId | Id сделки CRM, удерживающей объект недвижимости (⇔ Reserved/Booked). |
SoldAt | DateTimeOffset? | Нет | PropertyObject.SoldAt | Момент продажи (⇔ Sold). |
Number | string | Да | PropertyObject.Number | Номер объекта («квартира 145»); уникален в здании. |
Floor | short | Да | PropertyObject.Floor | Этаж (ось Y шахматки). |
Section | short? | Нет | PropertyObject.Section | Секция/подъезд (ось X). |
Rooms | short | Да | PropertyObject.Rooms | Комнатность; 0 = студия. |
TotalArea | decimal | Да | PropertyObject.TotalArea | Общая площадь, м². |
LivingArea | decimal? | Нет | PropertyObject.LivingArea | Жилая площадь, м². |
CeilingHeightMeters | decimal? | Нет | PropertyObject.CeilingHeightMeters | Высота потолков (null → берётся у здания). |
Price | decimal | Да | PropertyObject.Price | Текущая цена (без masking — свой tenant). |
CurrencyId | short | Да | PropertyObject.CurrencyId | Валюта — ссылка на справочник handbook (= Project.CurrencyId, INV-PRJ-6; RUB-only, ADR-0048). |
PricePerMeter | decimal | Да | PropertyObject.PricePerMeter | Цена за м² (вычисляемая денормализация). |
WindowViews | PropertyObjectWindowViewsDocument | Да | PropertyObject.WindowViews | Виды из окон + панорама (typed, ContractVersion = 1; property-object.md). |
Capacity | short? | Нет | PropertyObject.Capacity | Вместимость (гостей) — апарт-отели. |
Description | string? | Нет | PropertyObject.Description | Публичное описание (PIN-поле). |
SyncSource | ChessboardSyncSource (string) | Да | PropertyObject.SyncSource | Pin/TalentChessboard. |
SyncManagedFields | IReadOnlyList<string> | Да (может быть пустым) | вычисляется | Имена реплика-полей, закрытых для правки при SyncSource = TalentChessboard: Number,Floor,Section,Rooms,TotalArea,LivingArea,WindowViews,LayoutId; при Pin — пустой список. UI дизейблит поля. |
ExternalTalentPropertyObjectId | Guid? | Нет | PropertyObject.ExternalTalentPropertyObjectId | Id объекта в Talent Chessboard. |
LastSyncedAt | DateTimeOffset? | Нет | PropertyObject.LastSyncedAt | Последний применённый синк Talent. |
CreatedAt | DateTimeOffset | Да | PropertyObject.CreatedAt | Создан. |
UpdatedAt | DateTimeOffset | Да | PropertyObject.UpdatedAt | Изменён. |
Version | long | Да | PropertyObject.Version | Версия агрегата (для optimistic PUT, update-property-object.md). |
Предусловия
- Пользователь авторизован;
tenant_id= его компания-застройщик (=DeveloperId). - У актора есть permission
chessboard.property-objects.manageхотя бы в одном scope (иначеFORBIDDEN). - Объект недвижимости существует, принадлежит tenant-у актора и не soft-deleted; иначе неотличим от отсутствия (404).
Status/таймеры/HoldDealIdотражают последнее применённое событие CRM; в момент запроса они могут на несколько секунд опережать/отставать от реального применения release-события (eventual consistency, ниже).
Алгоритм
Чтение полного паспорта объекта недвижимости по идентификатору (кабинет застройщика — без маскирования). Операция read-only, доменных событий не публикует.
- Контекст и доступ. Актор — менеджер застройщика;
TenantId = DeveloperIdиз claims. Требуется permissionchessboard.property-objects.manage; нет ни в одном scope →FORBIDDEN(403). Просроченный/ отозванный доступ →UNAUTHORIZED(401) /FORBIDDEN(403). - Выборка и разграничение. Объект ищется по
PropertyObjectIdсреди не удалённых записей tenant-а актора:- записи нет,
DeletedAt != nullили объект чужого tenant-а →CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND(404, единый код без утечки существования); - актор с суженным scope «группа проектов/проект»:
propertyObject.ProjectIdвне разрешённого набора — объект недостижим и отдаётся тем жеCHESSBOARD_PROPERTY_OBJECT_NOT_FOUND(404), не 403 (симметрично фильтрации выдачи в search-property-objects.md);FORBIDDEN(403) — только при полном отсутствии permission.
- записи нет,
- Формирование ответа. Допустим любой статус, включая
HiddenиSold(карточка застройщика показывает весь паспорт: цену/статус/таймеры/HoldDealId). Все поля берутся из объекта;SyncManagedFieldsвычисляется поSyncSource(реплика-поля приTalentChessboard, пусто приPin); маскирование не применяется (свой tenant).
Read-committed снимок. Таймеры ReservedUntil/BookedUntil могут на несколько секунд опережать применение
release-события CRM (истёкший таймер при ещё Reserved/Booked — норма до страховочного job
property-object-hold-expiration, grace 10 мин); статус/таймеры/HoldDealId отражают последнее применённое
CRM-событие (INV-CHS-002: таймер заполнен ⇔ соответствующий статус).
Acceptance Criteria
- Happy path. Given объект tenant-а актора в scope; When
GET .../property-objects/{propertyObjectId}; Then200с полным паспортом,Versionпригоден для optimisticPUT. - Скрытый/проданный виден. Given объект в
Hidden(илиSold); When чтение; Then200со всеми полями (в отличие от публичной карточки). - Занятый объект. Given объект в
ReservedсReservedUntil/HoldDealId; When чтение; Then200, таймер иHoldDealIdзаполнены (INV-CHS-002). - Чужой/удалённый. Given
propertyObjectIdчужого tenant-а или soft-deleted; When чтение; Then404CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND. - Вне scope. Given scope сужен; When чтение объекта вне scope; Then
404(не 403). - Нет права. Given нет
chessboard.property-objects.manage; When чтение; Then403FORBIDDEN.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
CHESSBOARD_PROPERTY_OBJECT_NOT_FOUND | 404 | Объект недвижимости не существует / чужой tenant / удалён / вне scope-набора | «Объект не найден» (единый код — без утечки существования чужого) | no retry |
FORBIDDEN | 403 | Нет chessboard.property-objects.manage в scope | «Недостаточно прав» | no retry |
Сквозные ошибки области (ValidationError 400, UNAUTHORIZED 401) — по реестру README.md,
здесь не повторяются.
Совместимость и наблюдаемость
- Новые optional-поля ответа — additive; изменение
PropertyObjectWindowViewsDocument— по правиламContractVersion(property-object.md); enum-строки (PropertyObjectStatus,PropertyObjectStatusSource,ChessboardSyncSource,WindowViewKind) — клиентский fallback на unknown обязателен. - Logs:
TenantId,PropertyObjectId(без PII); metrics:chessboard_developers_property_object_get_total, latency histogram (SLO p99 < 300 мс); traces: OTel (traceparent). Dashboard — chessboard-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): Chessboard API — реестр операций областей Developers и Admin, GET /api/v1/developers/projects/{projectId}/property-objects — Список объектов недвижимости проекта, PUT /api/v1/developers/property-objects/{propertyObjectId} — Обновление объекта недвижимости (паспортные поля)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../domain/property-object.md (паспортные поля,
PropertyObjectWindowViewsDocument, инварианты статусов); enum-ы: ../domain/reference-data.md. - Соседние операции: search-property-objects.md (список),
update-property-object.md (правка по
Version), публичная карточка public/property-objects/get-property-object.md; реестр: README.md.