POST /api/v1/developers/projects/{projectId}/buildings — Создание здания
Назначение
Менеджер застройщика добавляет литер/корпус в проект своей компании: этажность (ось Y будущей шахматки),
секции (ось X), срок сдачи, статус строительства, готовность. Здание создаётся черновиком
(Status = Draft, SyncSource = Pin). Дом полей — сущность Здание.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | buildings |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/create-building.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.buildings.manage |
| Scope (RBAC) | организация / группа проектов / проект (projectId в scope) |
| Tenant isolation | TenantId = CompanyId из JWT; Building.TenantId наследуется от проекта (INV-BLD-1) |
| Audit | создание: CreatedByUserId, Version = 1 |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/developers/projects/{projectId}/buildings |
| Success status | 201 (Location: /api/v1/developers/buildings/{id}) |
| Idempotency header | Idempotency-Key (опционально) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ProjectId | route | Guid | Да | существующий проект tenant-а, не Archived | 404 при чужом |
Name | body | string | Да | 1..128; уникально в проекте среди не удалённых (INV-BLD-2) | — |
FloorsTotal | body | short | Да | 1..200 | — |
SectionsCount | body | short? | Нет | 1..50 | — |
DeliveryYear | body | short? | Нет | 2000..2100 | — |
DeliveryQuarter | body | short? | Нет | 1..4; обязателен при заполненном DeliveryYear (и наоборот запрещён без него) | — |
ConstructionStatus | body | ConstructionStatus | Да | enum Planned/UnderConstruction/Commissioned/Delivered; Delivered при создании запрещён (INV-BLD-6 — только через MarkDelivered) | — |
ReadinessPercent | body | decimal | Нет (default 0) | 0..100, precision (5,2) | — |
CeilingHeightMeters | body | decimal? | Нет | 2..10, precision (4,2) | — |
Поле, не принимаемое в запросе
Code не принимается — реплика-поле Talent (building.md), для PIN-зданий остаётся null.
Модель ответа CreateDeveloperBuildingResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | BuildingEntity.Id | — | Id созданного здания. |
ProjectId | Guid | Да | ProjectId | — | Проект. |
Status | BuildingStatus | Да | Status | — | Всегда Draft. |
Version | long | Да | Version | — | 1. |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Момент создания. |
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.buildings.manageв scope, покрывающемprojectId. При заголовкеIdempotency-Keyповтор возвращает сохранённыйIdбез повторной вставки и без повторного события; тот же ключ с иным телом →IDEMPOTENCY_KEY_MISMATCH(409). - Проверка входа и предусловий.
- формат/диапазоны полей, парность
DeliveryYear/DeliveryQuarter, запретConstructionStatus = Delivered— иначеValidationError; - проект-родитель существует в пределах tenant-а — иначе
CHESSBOARD_PROJECT_NOT_FOUND(404); - проект не архивирован (
Status != Archived, не удалён) — иначеCHESSBOARD_PROJECT_ARCHIVED(409); - имя корпуса уникально в проекте среди не удалённых (INV-BLD-2) — иначе
CHESSBOARD_BUILDING_NAME_TAKEN(409); гонка добивается unique indexux_buildings_project_name.
- формат/диапазоны полей, парность
- Создание здания. Здание заводится со статусом
DraftиSyncSource = Pin;TenantIdберётся из проекта (INV-BLD-1),ProjectId— от проекта. Поля берутся из формы (ReadinessPercentпо умолчанию0);Code = null,DeliveredAt = null; счётчики объектов недвижимости — нули; проставляютсяCreatedByUserId,Version = 1. - Факты и события. Публикуется событие
pin.chessboard.building-upserted.v1(снапшот здания). - Результат. Возвращаются
Id,ProjectId,Status = Draft,Version = 1,CreatedAt; HTTP201Location.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | BuildingUpsertedKafkaEvent → pin.chessboard.building-upserted.v1; потребители: catalog, dev-stats (измерение Building) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Нарушение ограничений / непарные Delivery-поля / Delivered при создании | Список ошибок полей | no retry |
CHESSBOARD_PROJECT_NOT_FOUND | 404 | Проект не найден / чужой | «Проект не найден» | no retry |
CHESSBOARD_PROJECT_ARCHIVED | 409 | Проект в архиве | «Проект в архиве» | no retry |
CHESSBOARD_BUILDING_NAME_TAKEN | 409 | Дубль имени в проекте (INV-BLD-2) | «Название корпуса занято» | no retry |
IDEMPOTENCY_KEY_MISMATCH | 409 | Тот же Idempotency-Key переиспользован с иным телом запроса | «Ключ идемпотентности переиспользован» | no retry |
FORBIDDEN | 403 | Нет permission | «Недостаточно прав» | no retry |
Совместимость и наблюдаемость
- Новые optional поля — additive.
- Logs:
TenantId,ProjectId,BuildingId; metrics:chessboard_developers_building_create_total{result}; traces: OTel.
Acceptance Criteria
- Happy path. Дано: менеджер застройщика с
chessboard.buildings.manageв scope проекта; проект tenant-а существует и не архивирован. Когда:POSTс валидным телом. Тогда: создано зданиеStatus = Draft,SyncSource = Pin,Version = 1,TenantIdунаследован от проекта (INV-BLD-1);201+Location; опубликованоpin.chessboard.building-upserted.v1. - Delivered при создании. Дано:
ConstructionStatus = Delivered. Когда: создание. Тогда:ValidationError(400) —Deliveredдостигается только черезMarkDelivered(INV-BLD-6). - Непарные Delivery-поля. Дано: задан
DeliveryYearбезDeliveryQuarter(или наоборот). Когда: создание. Тогда:ValidationError(400). - Проект чужой/архивный. Дано:
projectIdдругого tenant-а либо архивирован. Когда: создание. Тогда:CHESSBOARD_PROJECT_NOT_FOUND(404) для чужого/отсутствующего,CHESSBOARD_PROJECT_ARCHIVED(409) для архивного своего. - Дубль имени. Дано: в проекте уже есть не удалённое здание с тем же
Name. Когда: создание (в т.ч. гонка двух параллельных запросов). Тогда:CHESSBOARD_BUILDING_NAME_TAKEN(409, INV-BLD-2), одно здание из гонки добивается unique index. - Идемпотентный повтор. Дано: первый вызов под
Idempotency-Key = K. Когда: повтор с тем жеKи телом → тот жеIdбез второй вставки; при ином теле →IDEMPOTENCY_KEY_MISMATCH(409). - Eventual consistency. После
201здание видно в кабинете сразу; измерение Building вdev-statsи проекция вcatalogпоявляются после потребленияbuilding-upserted.v1(окно — SLO событий).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): Chessboard API — реестр операций областей Developers и Admin, POST /api/v1/developers/buildings/{buildingId}/property-objects — Создание объекта недвижимости, POST /api/v1/developers/buildings/{buildingId}/status — Смена статуса здания
- События (1): Event Contract: pin.chessboard.building-upserted (BuildingUpsertedKafkaEvent)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../domain/building.md (INV-BLD-1/2/6).
- Публикация: set-building-status.md; правка: update-building.md; реестр: README.md.