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

POST /api/v1/developers/projects/{projectId}/buildings — Создание здания

Назначение

Менеджер застройщика добавляет литер/корпус в проект своей компании: этажность (ось Y будущей шахматки), секции (ось X), срок сдачи, статус строительства, готовность. Здание создаётся черновиком (Status = Draft, SyncSource = Pin). Дом полей — сущность Здание.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (менеджер застройщика)
Auth policy / Permissionschessboard.buildings.manage
Scope (RBAC)организация / группа проектов / проект (projectId в scope)
Tenant isolationTenantId = CompanyId из JWT; Building.TenantId наследуется от проекта (INV-BLD-1)
Auditсоздание: CreatedByUserId, Version = 1

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequired
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ProjectIdrouteGuidДасуществующий проект tenant-а, не Archived404 при чужом
NamebodystringДа1..128; уникально в проекте среди не удалённых (INV-BLD-2)
FloorsTotalbodyshortДа1..200
SectionsCountbodyshort?Нет1..50
DeliveryYearbodyshort?Нет2000..2100
DeliveryQuarterbodyshort?Нет1..4; обязателен при заполненном DeliveryYear (и наоборот запрещён без него)
ConstructionStatusbodyConstructionStatusДаenum Planned/UnderConstruction/Commissioned/Delivered; Delivered при создании запрещён (INV-BLD-6 — только через MarkDelivered)
ReadinessPercentbodydecimalНет (default 0)0..100, precision (5,2)
CeilingHeightMetersbodydecimal?Нет2..10, precision (4,2)
Поле, не принимаемое в запросе

Code не принимается — реплика-поле Talent (building.md), для PIN-зданий остаётся null.

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

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

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право chessboard.buildings.manage в scope, покрывающем projectId. При заголовке Idempotency-Key повтор возвращает сохранённый Id без повторной вставки и без повторного события; тот же ключ с иным телом → IDEMPOTENCY_KEY_MISMATCH (409).
  2. Проверка входа и предусловий.
    • формат/диапазоны полей, парность 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 index ux_buildings_project_name.
  3. Создание здания. Здание заводится со статусом Draft и SyncSource = Pin; TenantId берётся из проекта (INV-BLD-1), ProjectId — от проекта. Поля берутся из формы (ReadinessPercent по умолчанию 0); Code = null, DeliveredAt = null; счётчики объектов недвижимости — нули; проставляются CreatedByUserId, Version = 1.
  4. Факты и события. Публикуется событие pin.chessboard.building-upserted.v1 (снапшот здания).
  5. Результат. Возвращаются Id, ProjectId, Status = Draft, Version = 1, CreatedAt; HTTP 201
    • Location.

Диаграмма

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

ТипДетали
Интеграционные событияBuildingUpsertedKafkaEventpin.chessboard.building-upserted.v1; потребители: catalog, dev-stats (измерение Building)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Нарушение ограничений / непарные Delivery-поля / Delivered при созданииСписок ошибок полейno retry
CHESSBOARD_PROJECT_NOT_FOUND404Проект не найден / чужой«Проект не найден»no retry
CHESSBOARD_PROJECT_ARCHIVED409Проект в архиве«Проект в архиве»no retry
CHESSBOARD_BUILDING_NAME_TAKEN409Дубль имени в проекте (INV-BLD-2)«Название корпуса занято»no retry
IDEMPOTENCY_KEY_MISMATCH409Тот же Idempotency-Key переиспользован с иным телом запроса«Ключ идемпотентности переиспользован»no retry
FORBIDDEN403Нет 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 событий).

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

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

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