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

POST /api/v1/developers/projects — Создание проекта

Назначение

Менеджер застройщика заводит новый проект (ЖК / апарт-отель) своей компании: базовые атрибуты и пять вкладок карточки (Локация, Инфраструктура, Финансирование, Юрданные, Ход строительства). Проект создаётся черновиком (Status = Draft) и попадает в публичную ленту каталога только после отдельной операции публикации. Дом полей — сущность Проект.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (менеджер застройщика)
Auth policy / Permissionschessboard.projects.manage
Scope (RBAC)организация (создание — только при scope Organization; scope «проект» создавать не может)
Tenant isolationTenantId = CompanyId из JWT → DeveloperId создаваемого проекта (INV-PRJ-1)
Auditсоздание: CreatedByUserId, Version = 1

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/developers/projects
Success status201 (Location: /api/v1/developers/projects/{id})
Idempotency headerIdempotency-Key (опционально; повтор с тем же ключом возвращает первый результат)
Correlationсквозной trace через заголовок traceparent

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

Ограничения полей — из дома ../domain/project.md.

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequired; Developer-профиль существуетTenant isolation
NamebodystringДа1..256 симв.
Descriptionbodystring?Нет<= 8000 симв.
TypebodyProjectTypeДаenum ResidentialComplex/ApartHotel/Hotel/Villa
ClassbodyProjectClassДаenum Standard/Comfort/Business/Premium/Elite
ConstructionStatusbodyConstructionStatusДаenum Planned/UnderConstruction/Commissioned/Delivered
CountryIdbodyshort?Нет (default — страна «Россия», запись с IsoCode2 = 'RU')существующая неархивная страна справочника (реплика ../domain/handbook-replica.md, INV-PRJ-10)
RegionIdbodylong?Нетсуществующий неархивный регион справочника, согласован со страной (INV-PRJ-10); обязателен (резолвлен) к моменту публикации (INV-PRJ-3)
CityIdbodylong?Нетсуществующий неархивный город справочника, согласован с регионом/страной (INV-PRJ-10); обязателен (резолвлен) к моменту публикации (INV-PRJ-3)
Starsbodyshort?Нет1..5; только при Type ∈ {ApartHotel, Hotel}
HotelierIdbodyGuid?Нетсуществующий не архивный chessboard.hoteliers.id
LocationbodyProjectLocationDocument?Нет (default {} v1)typed-модель project.md (адресные детали ниже уровня города; страна/регион/город — колонки-ссылки выше): District/AddressLine/PostalCode длины, Geo (−90..90 / −180..180), SeaDistanceMeters >= 0, CenterDistanceMinutes >= 0, TransportNodes ≤ 20 (каждый: Kind enum, Name ≤ 128, DistanceMinutes 0..600, TravelMode enum)
InfrastructurebodyProjectInfrastructureDocument?Нет (default)OwnItems/NearbyItems ≤ 50; элемент: Kind enum, Title ≤ 256, DistanceMinutes 0..600?, IconFileId?
FinancingbodyProjectFinancingDocument?Нет (default: SalesModel = Ddu214, пустые списки, MortgageAvailable = false, InstallmentAvailable = false)SalesModel enum; EscrowBankIds ≤ 10; MortgageBankIds ≤ 20; каждый bank id — существующий (INV-PRJ-7)
LegalInfobodyProjectLegalInfoDocument?Нет (default)Inn pattern ^\d{10}(\d{2})?$; Ogrn ^\d{13}(\d{2})?$; Declarations ≤ 50 (элемент: Title ≤ 256, DocumentFileId Guid, PublishedAt DateOnly)
ConstructionProgressbodyProjectConstructionProgressDocument?Нет (default)WebcamUrls ≤ 10 (URL ≤ 512); Reports ≤ 200 (элемент: Period YYYY-MM, Title ≤ 256, Description ≤ 4000, PhotoFileIds ≤ 100, VideoUrls ≤ 10, ReadinessPercent 0..100)
PresentationVideoUrlbodystring?Нет<= 512, absolute URL http/https
CoverImageFileIdbodyGuid?НетFileId storage/CDN (существование — граница storage)
CurrencyIdbodyshort?Нет (default — валюта «RUB», запись с IsoCode = 'RUB')существующая неархивная валюта справочника (INV-PRJ-10); типизированная ссылка ≠ мультивалютность — денежные операции RUB-only (ADR-0048)
PaybackYearsbodydecimal?Нет> 0, precision (5,2)

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

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаProjectEntity.IdId созданного проекта.
StatusProjectStatusДаStatusВсегда Draft.
VersionlongДаVersion1.
CreatedAtDateTimeOffsetДаCreatedAtМомент создания.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания-застройщик; требуется право chessboard.projects.manage в scope Organization. При заголовке Idempotency-Key повтор запроса с тем же ключом возвращает первый результат (тот же Id) без повторного создания и без повторной публикации события; тот же ключ с иным телом → IDEMPOTENCY_KEY_MISMATCH (409).
  2. Проверка входа и предусловий.
    • формат/диапазоны/enum всех полей формы (включая вложенные вкладки) — иначе ValidationError;
    • согласованность: Stars задан при Type ∉ {ApartHotel, Hotel}ValidationError;
    • справочные ссылки (INV-PRJ-10, по реплике ../domain/handbook-replica.md): CountryId/RegionId/CityId/CurrencyId существуют и не архивны — иначе CHESSBOARD_GEO_REF_UNKNOWN / CHESSBOARD_GEO_REF_ARCHIVED (422); гео-цепочка согласована (city.CountryId = CountryId; при заданном RegionIdcity.RegionId = RegionId и region.CountryId = CountryId) — иначе CHESSBOARD_CITY_REGION_MISMATCH (422);
    • профиль застройщика существует и не архивирован — иначе CHESSBOARD_DEVELOPER_NOT_FOUND (INV-PRJ-2);
    • если задан HotelierId — отельер существует и не архивен, иначе CHESSBOARD_HOTELIER_NOT_FOUND;
    • все банки из Financing (эскроу, ипотечные, банк финансирования) существуют — иначе CHESSBOARD_PROJECT_BANK_NOT_FOUND (INV-PRJ-7).
  3. Создание проекта. Проект заводится со статусом Draft (начальное состояние жизненного цикла) и SyncSource = Pin; TenantId = DeveloperId = TenantId актора (INV-PRJ-1). Поля карточки берутся из формы: пустые вкладки инициализируются пустыми typed-моделями (ContractVersion = 1); незаданный CountryId — страна «Россия» (IsoCode2 = 'RU'), незаданный CurrencyId — валюта «RUB» (IsoCode = 'RUB'); display-имена RegionName/CityName снимаются из реплики справочника при записи (заполнены ⇔ соответствующий id задан); денормализации Price пусты, счётчики объектов недвижимости — нули; проставляются CreatedByUserId и Version = 1.
  4. Факты и события. Фиксируется факт создания проекта — публикуется событие pin.chessboard.project-upserted.v1 (снапшот проекта; см. «Побочные эффекты»). Черновик в публичную витрину не попадает — потребители фильтруют по Status.
  5. Результат. Возвращаются Id, Status = Draft, Version = 1, CreatedAt; HTTP 201 + Location. Маскирование не требуется.

Диаграмма

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

ТипДетали
Интеграционные событияProjectUpsertedKafkaEventpin.chessboard.project-upserted.v1, key = Id; потребители: catalog, dev-stats (измерение Project), identity (scope), news — Draft-проект в public-витрину не попадает (фильтр по Status у потребителя)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Нарушение ограничений полей / Stars без типа отеляСписок ошибок полейno retry
CHESSBOARD_DEVELOPER_NOT_FOUND404Developer-профиль tenant-а отсутствует/архивирован«Профиль застройщика не найден»no retry
CHESSBOARD_HOTELIER_NOT_FOUND400HotelierId не найден или архивный«Отельер не найден»no retry
CHESSBOARD_PROJECT_BANK_NOT_FOUND400Id банка из Financing не существует (INV-PRJ-7)«Банк не найден»no retry
CHESSBOARD_GEO_REF_UNKNOWN422Справочная ссылка (CountryId/RegionId/CityId/CurrencyId) не найдена (INV-PRJ-10)«Запись справочника не найдена»no retry
CHESSBOARD_GEO_REF_ARCHIVED422Указана архивная запись справочника (INV-PRJ-10)«Запись справочника архивирована»no retry
CHESSBOARD_CITY_REGION_MISMATCH422Город/регион/страна рассогласованы (INV-PRJ-10)«Гео-цепочка не согласована»no retry
IDEMPOTENCY_KEY_MISMATCH409Тот же Idempotency-Key переиспользован с иным телом запроса«Ключ идемпотентности переиспользован»no retry
FORBIDDEN403Нет chessboard.projects.manage в scope Organization«Недостаточно прав»no retry

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

  • Новые optional поля формы — additive; изменение семантики вкладок — ContractVersion + 1 (project.md).
  • Logs: TenantId, ProjectId (после создания), Type; metrics: chessboard_developers_project_create_total{result}; traces: OTel (traceparent).

Acceptance Criteria

  • Happy path. Дано: менеджер застройщика с chessboard.projects.manage в scope Organization и существующим Developer-профилем. Когда: POST /developers/projects с валидным телом. Тогда: создан проект Status = Draft, Version = 1, DeveloperId = TenantId (INV-PRJ-1); ответ 201 + Location; событие pin.chessboard.project-upserted.v1 (key = Id) опубликовано; проект в публичной ленте catalog не виден (Draft фильтруется потребителем).
  • Stars без отельного типа. Дано: Type = ResidentialComplex/Villa, задан Stars. Когда: создание. Тогда: ValidationError (400); проект не создан, событие не опубликовано.
  • Нет профиля застройщика. Дано: Developer-профиль tenant-а отсутствует или архивирован. Когда: создание. Тогда: CHESSBOARD_DEVELOPER_NOT_FOUND (INV-PRJ-2).
  • Битые ссылки на справочники. Дано: HotelierId архивен/неизвестен либо любой id из Financing (эскроу/ипотечный/финансирующий банк) неизвестен. Когда: создание. Тогда: CHESSBOARD_HOTELIER_NOT_FOUND либо CHESSBOARD_PROJECT_BANK_NOT_FOUND (INV-PRJ-7); проект не создан.
  • Битая гео/валютная ссылка справочника. Дано: CityId неизвестен или архивен, либо город не принадлежит указанным RegionId/CountryId. Когда: создание. Тогда: CHESSBOARD_GEO_REF_UNKNOWN / CHESSBOARD_GEO_REF_ARCHIVED / CHESSBOARD_CITY_REGION_MISMATCH (422, INV-PRJ-10); проект не создан.
  • Идемпотентный повтор. Дано: первый вызов создал проект под Idempotency-Key = K. Когда: повтор с тем же K и тем же телом. Тогда: тот же Id, без второго создания и второго события. При K с иным телом → IDEMPOTENCY_KEY_MISMATCH (409).
  • Недостаточный scope. Дано: право есть только в scope конкретного проекта (не Organization). Когда: создание. Тогда: FORBIDDEN (403).
  • Eventual consistency. После 201 проект доступен в кабинете застройщика сразу; scope «проект» для назначения ролей в identity, витрина catalog, измерение dev-stats и лента news появляются после асинхронного потребления project-upserted.v1 (окно — SLO применения событий сервиса). До этого назначить роль на новый проект в identity нельзя.

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

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

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