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

GET /api/v1/developers/projects/{projectId} — Карточка проекта застройщика

Назначение

Экран редактирования проекта в кабинете застройщика. Возвращает полное состояние проекта своей компании: все плоские поля и пять вкладок карточки (Локация, Инфраструктура, Финансирование, Юрданные, Ход строительства), а также признаки полей, управляемых синхронизацией Talent. В отличие от списка проектов, отдаёт карточку целиком. Дом полей — сущность Проект.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (менеджер застройщика)
Auth policy / Permissionschessboard.projects.manage
Scope (RBAC)организация / группа проектов / проект (projectId должен входить в scope)
Tenant isolationTenantId = CompanyId из JWT; чужой проект недостижим → 404

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/developers/projects/{projectId}
Success status200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ProjectIdrouteGuidДаRequired, существующий проект tenant-а404 при чужом/отсутствующем

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

Все поля — из ../domain/project.md (дом полей), без дельты типов.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаProjectEntity.IdId проекта.
DeveloperIdGuidДаDeveloperIdЗастройщик (= TenantId).
StatusProjectStatusДаStatusDraft/Published/Hidden/Archived.
NamestringДаNameНазвание.
Descriptionstring?НетDescriptionОписание.
TypeProjectTypeДаTypeТип.
ClassProjectClassДаClassКласс.
ConstructionStatusConstructionStatusДаConstructionStatusСтатус строительства.
CountryIdshortДаCountryIdСтрана — ссылка на справочник handbook (handbook-replica.md).
RegionIdlong?НетRegionIdРегион — ссылка на справочник; null — не резолвлен (публикация заблокирована, INV-PRJ-3).
RegionNamestring?НетRegionNameDisplay-имя региона (денормализация; заполнено ⇔ RegionId != null).
CityIdlong?НетCityIdГород — ссылка на справочник; null — не резолвлен (публикация заблокирована, INV-PRJ-3).
CityNamestring?НетCityNameDisplay-имя города (заполнено ⇔ CityId != null).
Starsshort?НетStarsЗвёздность (отели).
HotelierIdGuid?НетHotelierIdОтельер (hotelier.md).
LocationProjectLocationDocumentДаLocationВкладка «Локация» (typed-модель project.md).
InfrastructureProjectInfrastructureDocumentДаInfrastructureИнфраструктура.
FinancingProjectFinancingDocumentДаFinancingФинансирование (банки — id, резолв имён через lookup).
LegalInfoProjectLegalInfoDocumentДаLegalInfoЮрданные (публичны по 214-ФЗ).
ConstructionProgressProjectConstructionProgressDocumentДаConstructionProgressХод строительства.
PresentationVideoUrlstring?НетPresentationVideoUrlВидео.
CoverImageFileIdGuid?НетCoverImageFileIdОбложка.
PriceNullableRange<decimal>?НетProject.Price (Price.Min/Price.Max)Диапазон цен свободных объектов недвижимости (денормализация, read-only для клиента).
CurrencyIdshortДаCurrencyIdВалюта — ссылка на справочник handbook (RUB-only, ADR-0048); display-код/имя — lookup справочника.
PropertyObjectsTotalCountintДаPropertyObjectsTotalCountДенормализация.
PropertyObjectsFreeCountintДаPropertyObjectsFreeCountДенормализация.
PaybackYearsdecimal?НетPaybackYearsОкупаемость (ручной ввод).
SyncSourceChessboardSyncSourceДаSyncSourcePin/TalentChessboard.
SyncManagedFieldsstring[]ДавычисляетсяИмена реплика-полей, закрытых для правки при SyncSource = TalentChessboard (INV-PRJ-9): Name,Type,Class,ConstructionStatus,CountryId,RegionId,CityId,Stars,CoverImageFileId,Location.Address* (display-имена RegionName/CityName следуют за id); при Pin — пустой список. UI дизейблит поля.
LastSyncedAtDateTimeOffset?НетLastSyncedAtПоследний синк Talent.
CreatedAtDateTimeOffsetДаCreatedAtСоздан.
UpdatedAtDateTimeOffsetДаUpdatedAtИзменён.
VersionlongДаVersionДля optimistic PUT.

Предусловия

  • Пользователь авторизован; tenant_id = его компания-застройщик.
  • У актора есть permission chessboard.projects.manage хотя бы в одном scope компании (иначе FORBIDDEN).
  • Запрашиваемый проект существует и принадлежит tenant-у актора (включая Archived — карточка читается для истории); иначе неотличим от отсутствия (404).
  • Банки в Financing — id справочника; резолв названий/логотипов — отдельным lookup (references-lookup.md); карточка отдаёт только id.

Алгоритм

Чтение карточки по идентификатору с разграничением доступа. Операция read-only, доменных событий не публикует.

  1. Контекст и доступ. Актор — менеджер застройщика; TenantId из claims. Требуется permission chessboard.projects.manage; нет ни в одном scope → FORBIDDEN (403). Просроченный/отозванный к моменту запроса доступ → UNAUTHORIZED (401) / FORBIDDEN (403).
  2. Выборка и разграничение. Проект ищется по ProjectId в пределах tenant-а актора:
    • записи нет или проект чужого tenant-а → CHESSBOARD_PROJECT_NOT_FOUND (404, существование чужого не раскрывается);
    • актор с суженным scope «группа проектов/проект»: ProjectId вне разрешённого набора — проект недостижим и отдаётся тем же CHESSBOARD_PROJECT_NOT_FOUND (404), не 403 (симметрично фильтрации выдачи в search-projects.md). FORBIDDEN (403) — только при полном отсутствии permission chessboard.projects.manage.
  3. Формирование ответа. Допустим любой статус, включая Archived (DeletedAt != null) — карточка читается для истории. Все поля берутся из проекта; пять вкладок (Локация/Инфраструктура/Финансирование/ Юрданные/Ход строительства) — typed-модели project.md. SyncManagedFields вычисляется: при SyncSource = TalentChessboard — список реплика-полей (INV-PRJ-9, UI дизейблит), при Pin — пустой. Маскирование не требуется (свой tenant; LegalInfo публична по 214-ФЗ).
Freshness

Read-committed снимок. Денормализации — Price (границы свободных объектов недвижимости) и счётчики PropertyObjects*Count — могут отставать до фонового пересчёта агрегатов (project-aggregates-recalc, окно ~40 с); реплика-поля вкладок отражают последний применённый синк Talent (LastSyncedAt).

Acceptance Criteria

  • Happy path. Given проект tenant-а актора в scope; When GET .../projects/{projectId}; Then 200 с полной карточкой (плоские поля + 5 вкладок), Version пригоден для optimistic PUT.
  • Архив читается. Given проект в статусе Archived; When чтение; Then 200 (история доступна).
  • Чужой проект. Given projectId другого tenant-а; When чтение; Then 404 CHESSBOARD_PROJECT_NOT_FOUND.
  • Вне scope. Given scope сужен до другой группы проектов; When чтение проекта вне scope; Then 404 (не 403).
  • Нет права. Given нет chessboard.projects.manage; When чтение; Then 403 FORBIDDEN.
  • Отставание денормализации. Given только что изменилась цена объекта недвижимости, пересчёт не завершён; When чтение карточки; Then 200 с прежним Price-диапазоном (eventual consistency, не ошибка).

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
CHESSBOARD_PROJECT_NOT_FOUND404Проект не существует / чужой tenant / ProjectId вне scope-набора«Проект не найден»no retry
FORBIDDEN403Нет permission chessboard.projects.manage ни в одном scope«Недостаточно прав»no retry

Сквозные ошибки (UNAUTHORIZED 401, ValidationError 400 — ProjectId == Guid.Empty) — по реестру README.md.

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

  • Новые optional поля ответа — additive; изменение вкладок-моделей — по правилам ContractVersion project.md; enum-строки (ProjectStatus/ProjectType/ProjectClass/ConstructionStatus/ChessboardSyncSource) — клиентский fallback на unknown обязателен.
  • Logs: TenantId, ProjectId, Status, SyncSource (без PII); metrics: chessboard_developers_project_get_total, latency histogram (SLO p99 < 300 мс); traces: OTel (traceparent). Dashboard chessboard-overview; read-only — runbook не требуется.

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

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

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