POST /api/v1/developers/projects — Создание проекта
Назначение
Менеджер застройщика заводит новый проект (ЖК / апарт-отель) своей компании: базовые атрибуты и пять
вкладок карточки (Локация, Инфраструктура, Финансирование, Юрданные, Ход строительства). Проект создаётся
черновиком (Status = Draft) и попадает в публичную ленту каталога только после отдельной операции
публикации. Дом полей — сущность Проект.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | projects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/create-project.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.projects.manage |
| Scope (RBAC) | организация (создание — только при scope Organization; scope «проект» создавать не может) |
| Tenant isolation | TenantId = CompanyId из JWT → DeveloperId создаваемого проекта (INV-PRJ-1) |
| Audit | создание: CreatedByUserId, Version = 1 |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/developers/projects |
| Success status | 201 (Location: /api/v1/developers/projects/{id}) |
| Idempotency header | Idempotency-Key (опционально; повтор с тем же ключом возвращает первый результат) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
Ограничения полей — из дома ../domain/project.md.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required; Developer-профиль существует | Tenant isolation |
Name | body | string | Да | 1..256 симв. | — |
Description | body | string? | Нет | <= 8000 симв. | — |
Type | body | ProjectType | Да | enum ResidentialComplex/ApartHotel/Hotel/Villa | — |
Class | body | ProjectClass | Да | enum Standard/Comfort/Business/Premium/Elite | — |
ConstructionStatus | body | ConstructionStatus | Да | enum Planned/UnderConstruction/Commissioned/Delivered | — |
CountryId | body | short? | Нет (default — страна «Россия», запись с IsoCode2 = 'RU') | существующая неархивная страна справочника (реплика ../domain/handbook-replica.md, INV-PRJ-10) | — |
RegionId | body | long? | Нет | существующий неархивный регион справочника, согласован со страной (INV-PRJ-10); обязателен (резолвлен) к моменту публикации (INV-PRJ-3) | — |
CityId | body | long? | Нет | существующий неархивный город справочника, согласован с регионом/страной (INV-PRJ-10); обязателен (резолвлен) к моменту публикации (INV-PRJ-3) | — |
Stars | body | short? | Нет | 1..5; только при Type ∈ {ApartHotel, Hotel} | — |
HotelierId | body | Guid? | Нет | существующий не архивный chessboard.hoteliers.id | — |
Location | body | ProjectLocationDocument? | Нет (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) | — |
Infrastructure | body | ProjectInfrastructureDocument? | Нет (default) | OwnItems/NearbyItems ≤ 50; элемент: Kind enum, Title ≤ 256, DistanceMinutes 0..600?, IconFileId? | — |
Financing | body | ProjectFinancingDocument? | Нет (default: SalesModel = Ddu214, пустые списки, MortgageAvailable = false, InstallmentAvailable = false) | SalesModel enum; EscrowBankIds ≤ 10; MortgageBankIds ≤ 20; каждый bank id — существующий (INV-PRJ-7) | — |
LegalInfo | body | ProjectLegalInfoDocument? | Нет (default) | Inn pattern ^\d{10}(\d{2})?$; Ogrn ^\d{13}(\d{2})?$; Declarations ≤ 50 (элемент: Title ≤ 256, DocumentFileId Guid, PublishedAt DateOnly) | — |
ConstructionProgress | body | ProjectConstructionProgressDocument? | Нет (default) | WebcamUrls ≤ 10 (URL ≤ 512); Reports ≤ 200 (элемент: Period YYYY-MM, Title ≤ 256, Description ≤ 4000, PhotoFileIds ≤ 100, VideoUrls ≤ 10, ReadinessPercent 0..100) | — |
PresentationVideoUrl | body | string? | Нет | <= 512, absolute URL http/https | — |
CoverImageFileId | body | Guid? | Нет | FileId storage/CDN (существование — граница storage) | — |
CurrencyId | body | short? | Нет (default — валюта «RUB», запись с IsoCode = 'RUB') | существующая неархивная валюта справочника (INV-PRJ-10); типизированная ссылка ≠ мультивалютность — денежные операции RUB-only (ADR-0048) | — |
PaybackYears | body | decimal? | Нет | > 0, precision (5,2) | — |
Модель ответа CreateDeveloperProjectResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | ProjectEntity.Id | — | Id созданного проекта. |
Status | ProjectStatus | Да | Status | — | Всегда Draft. |
Version | long | Да | Version | — | 1. |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Момент создания. |
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания-застройщик;
требуется право
chessboard.projects.manageв scopeOrganization. При заголовкеIdempotency-Keyповтор запроса с тем же ключом возвращает первый результат (тот жеId) без повторного создания и без повторной публикации события; тот же ключ с иным телом →IDEMPOTENCY_KEY_MISMATCH(409). - Проверка входа и предусловий.
- формат/диапазоны/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; при заданномRegionId—city.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).
- формат/диапазоны/enum всех полей формы (включая вложенные вкладки) — иначе
- Создание проекта. Проект заводится со статусом
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. - Факты и события. Фиксируется факт создания проекта — публикуется событие
pin.chessboard.project-upserted.v1(снапшот проекта; см. «Побочные эффекты»). Черновик в публичную витрину не попадает — потребители фильтруют поStatus. - Результат. Возвращаются
Id,Status = Draft,Version = 1,CreatedAt; HTTP201+Location. Маскирование не требуется.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | ProjectUpsertedKafkaEvent → pin.chessboard.project-upserted.v1, key = Id; потребители: catalog, dev-stats (измерение Project), identity (scope), news — Draft-проект в public-витрину не попадает (фильтр по Status у потребителя) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Нарушение ограничений полей / Stars без типа отеля | Список ошибок полей | no retry |
CHESSBOARD_DEVELOPER_NOT_FOUND | 404 | Developer-профиль tenant-а отсутствует/архивирован | «Профиль застройщика не найден» | no retry |
CHESSBOARD_HOTELIER_NOT_FOUND | 400 | HotelierId не найден или архивный | «Отельер не найден» | no retry |
CHESSBOARD_PROJECT_BANK_NOT_FOUND | 400 | Id банка из Financing не существует (INV-PRJ-7) | «Банк не найден» | no retry |
CHESSBOARD_GEO_REF_UNKNOWN | 422 | Справочная ссылка (CountryId/RegionId/CityId/CurrencyId) не найдена (INV-PRJ-10) | «Запись справочника не найдена» | no retry |
CHESSBOARD_GEO_REF_ARCHIVED | 422 | Указана архивная запись справочника (INV-PRJ-10) | «Запись справочника архивирована» | no retry |
CHESSBOARD_CITY_REGION_MISMATCH | 422 | Город/регион/страна рассогласованы (INV-PRJ-10) | «Гео-цепочка не согласована» | no retry |
IDEMPOTENCY_KEY_MISMATCH | 409 | Тот же Idempotency-Key переиспользован с иным телом запроса | «Ключ идемпотентности переиспользован» | no retry |
FORBIDDEN | 403 | Нет 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в scopeOrganizationи существующим 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нельзя.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): Chessboard API — реестр операций областей Developers и Admin, PUT /api/v1/developers/projects/{projectId} — Обновление проекта
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../domain/project.md (INV-PRJ-1/2/7/10, вкладки-модели); справочники: ../domain/bank.md, ../domain/hotelier.md; реплика справочников handbook: ../domain/handbook-replica.md.
- Публикация: publish-project.md; правка: update-project.md; реестр: README.md.