Entity / Aggregate: BuildingEntity
Дом полей агрегата «Здание» (литер/корпус) контекста chessboard. Проекции (шахматка, карточка проекта
в catalog, измерение Building в dev-stats) ссылаются сюда.
Назначение
BuildingEntity — строение внутри проекта застройщика: литер, корпус, очередь. Несёт этажность (ось
«этаж» шахматки), срок сдачи и готовность. Сущность гибридная: базовые поля реплицируются из внешней
системы Talent Chessboard (через события синка), а часть полей PIN заводит и корректирует локально —
синком они не перезаписываются.
Классификация
| Поле | Значение |
|---|---|
| Контекст | chessboard |
| Kind | AggregateRoot (гибрид с репликой Talent) |
| Source of truth | chessboard (PIN-поля) / talent.microservices.chessboard (реплика-поля) |
| Таблица | chessboard.buildings |
| Tenant isolation | TenantId = DeveloperId проекта (ADR-0052); публичное чтение — только для Published зданий published-проектов |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PK | app / Talent sync | Идентификатор здания. |
TenantId | Guid | Да | index ix_buildings_tenant_id; = Project.TenantId | app | Tenant boundary (ADR-0052). |
ProjectId | Guid | Да | FK fk_buildings_project → chessboard.projects.id, index ix_buildings_project_id | app / Talent sync | Проект-родитель. |
Status | BuildingStatus | Да | enum (текст), varchar(32), index ix_buildings_status | app | Draft / Published / Hidden (см. stateDiagram). |
Name | string | Да | varchar(128), min 1; unique ux_buildings_project_name (ProjectId, lower(Name)) where deleted_at is null | Talent sync / app | Название: «Литер 3», «Корпус Б». Уникально в проекте (INV-BLD-2). |
Code | string? | Нет | varchar(32) | Talent sync | Технический код литера в системе застройщика/Talent. |
FloorsTotal | short | Да | check floors_total between 1 and 200 | Talent sync / app | Этажность — ось Y шахматки; объекты недвижимости валидируются Floor <= FloorsTotal (INV-BLD-3). |
SectionsCount | short? | Нет | check sections_count between 1 and 50 | Talent sync / app | Число секций/подъездов — ось X шахматки (объект недвижимости несёт Section). |
DeliveryYear | short? | Нет | check delivery_year between 2000 and 2100; составной index ix_buildings_delivery (delivery_year, delivery_quarter) | Talent sync / app | Год сдачи (срок сдачи корпуса). |
DeliveryQuarter | short? | Нет | check delivery_quarter between 1 and 4; обязателен при заполненном DeliveryYear | Talent sync / app | Квартал сдачи; отображение «IV кв. 2027». |
DeliveredAt | DateOnly? | Нет | — | app | Фактическая дата ввода в эксплуатацию (заполняется при ConstructionStatus = Delivered). |
ConstructionStatus | ConstructionStatus | Да | enum (текст), varchar(32), index ix_buildings_construction_status | Talent sync / app | Статус строительства здания — reference-data.md; может отличаться от статуса проекта (очереди). |
ReadinessPercent | decimal | Да | numeric(5,2), check readiness_percent between 0 and 100, default 0 | Talent sync / app | Готовность здания, %. |
CeilingHeightMeters | decimal? | Нет | numeric(4,2), check between 2 and 10 | Talent sync / app | Типовая высота потолков (может переопределяться объектом недвижимости). |
PropertyObjectsTotalCount | int | Да | >= 0, default 0 | вычисляется (background) | Денормализованный счётчик объектов недвижимости здания (job project-aggregates-recalc). |
PropertyObjectsFreeCount | int | Да | >= 0, default 0 | вычисляется (background) | Свободных объектов недвижимости. |
ExternalTalentBuildingId | Guid? | Нет | unique ux_buildings_external_talent_building_id (where not null) | Talent sync | Id здания в Talent Chessboard. |
SyncSource | ChessboardSyncSource | Да | enum (текст), varchar(32) | app / consumer | Pin / TalentChessboard (реплика-поля: Name, Code, FloorsTotal, SectionsCount, Delivery*, ConstructionStatus, ReadinessPercent). |
ReplicaVersion | long? | Нет (обязателен при реплике) | >= 0 | Talent sync | Anti-out-of-order guard (INV-BLD-5). |
LastSyncedAt | DateTimeOffset? | Нет | — | Talent sync | Время последнего применённого события синка. |
CreatedAt | DateTimeOffset | Да | index ix_buildings_created_at | app | Дата создания. |
CreatedByUserId | Guid? | Нет | — | app | Кто создал. |
UpdatedAt | DateTimeOffset | Да | — | app | Дата изменения. |
UpdatedByUserId | Guid? | Нет | — | app | Последний изменивший. |
DeletedAt | DateTimeOffset? | Нет | partial-index filter | app | Soft-delete. |
DeletedByUserId | Guid? | Нет | — | app | Кто удалил. |
Version | long | Да | >= 1 | app | Версия агрегата (идёт в payload событий). |
Enum BuildingStatus (хранится текстом)
| Значение | Описание |
|---|---|
Draft | Заведено, заполняется; в публичных проекциях (шахматка, ассортимент) отсутствует. |
Published | Показывается в шахматке/ассортименте published-проекта. |
Hidden | Скрыто застройщиком (например, очередь не в продаже); объекты недвижимости сохранны, публично не отдаются. |
Связи
| Связь | Тип | Ключ | Delete behavior |
|---|---|---|---|
ProjectEntity | reference (родитель) | buildings.project_id → projects.id | restrict |
PropertyObjectEntity | reference (обратная) | property_objects.building_id → buildings.id | restrict (скрытие здания скрывает объекты недвижимости в public-проекциях, не мутирует их) |
| Talent building | replica | ExternalTalentBuildingId | — |
Инварианты и переходы состояний
| Инвариант | Условие | Когда проверяется | Ошибка |
|---|---|---|---|
| INV-BLD-1 TenantFromProject | TenantId == Project.TenantId | при создании (tenant берётся из проекта) | CHESSBOARD_BUILDING_TENANT_MISMATCH |
| INV-BLD-2 UniqueNameInProject | Name уникально среди не удалённых зданий проекта | при создании и переименовании | CHESSBOARD_BUILDING_NAME_TAKEN |
| INV-BLD-3 FloorsBound | Для каждого объекта недвижимости здания 1 <= PropertyObject.Floor <= FloorsTotal; уменьшение FloorsTotal ниже максимального занятого этажа запрещено | при изменении этажности | CHESSBOARD_BUILDING_FLOORS_CONFLICT |
| INV-BLD-4 PublishRequiresProject | Публикация только при Project.Status in (Draft, Published) и заполненных FloorsTotal, DeliveryYear+DeliveryQuarter | при публикации | CHESSBOARD_BUILDING_PUBLISH_INCOMPLETE |
| INV-BLD-5 MonotonicReplica | Событие синка применяется только при event.Version > ReplicaVersion | при применении события синка | идемпотентный пропуск |
| INV-BLD-6 DeliveredConsistency | ConstructionStatus = Delivered ⇔ DeliveredAt != null и ReadinessPercent = 100 | при пометке сдачи | CHESSBOARD_BUILDING_INVALID_DELIVERED_STATE |
| INV-BLD-7 SyncOwnedFields | Реплика-поля меняются только синком; PIN-поля — только локально | при изменении полей (раздельные пути) | CHESSBOARD_BUILDING_FIELD_SYNC_MANAGED |
Переходы состояний (из / в / условие / инициатор / событие)
| Из | В | Условие (guard) | Инициатор | Порождает |
|---|---|---|---|---|
[*] | Draft | новый литер; TenantId = Project.TenantId (INV-BLD-1), имя свободно в проекте (INV-BLD-2) | синк Talent / застройщик (Developers API) | BuildingCreatedEvent + BuildingUpsertedKafkaEvent |
Draft | Published | заполнены FloorsTotal, DeliveryYear + DeliveryQuarter; Project.Status in (Draft, Published) (INV-BLD-4) | застройщик | BuildingStatusChangedEvent + BuildingUpsertedKafkaEvent |
Published | Hidden | — (очередь снята с продажи; объекты недвижимости сохранны, публично не отдаются) | застройщик / ПИН | BuildingStatusChangedEvent + BuildingUpsertedKafkaEvent |
Hidden | Published | Project.Status in (Draft, Published) | застройщик | BuildingStatusChangedEvent + BuildingUpsertedKafkaEvent |
Published | Draft | снятие с публикации для доработки | застройщик | BuildingStatusChangedEvent + BuildingUpsertedKafkaEvent |
| любой | — (soft-delete DeletedAt) | нет не удалённых объектов недвижимости с BuildingId = Id (FK property_objects → buildings = restrict) | застройщик / ПИН | архивная строка не переопубликуется как активная |
Draft/Published (при ConstructionStatus → Delivered) | тот же статус | DeliveredAt != null и ReadinessPercent = 100 (INV-BLD-6) | синк Talent / застройщик | BuildingDeliveredEvent + BuildingUpsertedKafkaEvent |
Реплика-поля (Name, FloorsTotal, Delivery*, ConstructionStatus, ReadinessPercent и др.) меняет только
синк Talent; PIN-поля и публичность — только локально: публикация/скрытие — решение застройщика, синк статус
публичности не трогает. Скрытие/архив здания скрывает его объекты недвижимости в public-проекциях, но не
мутирует их статусы продажи (те — только CRM/синк).
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|---|---|---|
BuildingCreatedEvent | доменное | Успешное создание | Id, ProjectId, SyncSource |
BuildingStatusChangedEvent | доменное | Переход Status | Id, From, To, actor |
BuildingDeliveredEvent | доменное | Пометка сдачи | Id, DeliveredAt |
BuildingUpsertedKafkaEvent | интеграционное (ContractVersion = 1, топик pin.chessboard.building-upserted.v1, key = Id) | Создание и значимое обновление (Name/Status/FloorsTotal/Delivery*/ConstructionStatus/ReadinessPercent) | Snapshot: Id, TenantId, ProjectId, Status, Name, FloorsTotal, SectionsCount, DeliveryYear, DeliveryQuarter, ConstructionStatus, ReadinessPercent, Version. Потребители: catalog (карточка/фильтр «срок сдачи»), dev-stats (измерение Building) |
Интеграционные события публикуются атомарно со сменой состояния агрегата.
Кросс-контекстный контракт (BuildingUpsertedKafkaEvent)
- Ключ партиционирования / версия. key =
Id,ContractVersion = 1, монотонныйVersion— потребители применяют upsert-by-id и отбрасывают событие сVersionне новее применённого; доставка at-least-once. - Счётчики — вне payload.
PropertyObjectsTotalCount/PropertyObjectsFreeCountне входят в snapshot и на их чистый пересчёт событие не шлётся: их потребители (catalog,dev-stats) выводят из property-object-потока (PropertyObjectUpsertedKafkaEvent/PropertyObjectStatusChangedKafkaEvent). Колонки в БД джобproject-aggregates-recalcвсё равно обновляет — для API-чтений и шахматки. - Кто ожидает.
catalog— карточка/фильтр «срок сдачи», ось «этаж/секция» шахматки;dev-stats— измерение Building. Оба получают только реплика/структурные поля здания; данные о продажах объектов — из отдельного потока.
Между сменой состояния здания и обновлением витрины catalog — лаг доставки Kafka (штатно секунды); в
промежутке пользователь может видеть прежнюю карточку здания.
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_buildings; ux_buildings_project_name unique (project_id, lower(name)) where deleted_at is null; ux_buildings_external_talent_building_id unique where not null; ix_buildings_tenant_id; ix_buildings_project_id; ix_buildings_status; ix_buildings_construction_status; ix_buildings_delivery (delivery_year, delivery_quarter); ix_buildings_created_at |
| Партиционирование / retention | none — зданий десятки тысяч максимум; retention бессрочный (soft-delete) |
| Concurrency | оптимистическая блокировка (правка не должна затирать чужую); для событий синка — guard по ReplicaVersion; денормализованные счётчики обновляются точечно |
| Permissions / PII | Public-чтение — только Published здания published-проектов; CRUD — chessboard.buildings.manage (scope застройщика); PII нет |
| Audit | версия агрегата + отметки кто/когда; значимые изменения — события |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (5): Entity / Aggregate: CatalogProjectCard, Entity / Aggregate: CatalogPropertyObjectCard, Entity / Aggregate: ProjectEntity, Entity / Aggregate: PropertyObjectEntity, Enum-справочники контекста chessboard
- API (15): Chessboard API — реестр операций областей Developers и Admin, GET /api/v1/developers/buildings/{buildingId} — Карточка здания, GET /api/v1/developers/projects/{projectId}/buildings — Список зданий проекта, GET /api/v1/developers/projects/{projectId}/property-objects — Список объектов недвижимости проекта, GET /api/v1/public/projects/{projectId} — Карточка проекта (ограниченная/полная), GET /api/v1/public/projects/{projectId}/buildings/{buildingId} — Карточка здания (литера/корпуса), GET /api/v1/public/projects/{projectId}/chessboard — Шахматка проекта (матрица этажи × секции), GET /api/v1/public/property-objects — Список объектов недвижимости проекта/здания с фильтрами, GET /api/v1/public/property-objects/{propertyObjectId} — Карточка объекта/объекта недвижимости (ограниченная/полная), POST /api/v1/developers/buildings/{buildingId}/property-objects — Создание объекта недвижимости, POST /api/v1/developers/buildings/{buildingId}/property-objects/bulk — Массовый upsert объектов недвижимости, POST /api/v1/developers/buildings/{buildingId}/status — Смена статуса здания (+3)
- Use Cases (3): UC-CHS-002. Добавление здания (литера/корпуса) и объектов проекта застройщиком, UC-CHS-004. Просмотр шахматки проекта: гость vs инвестор (разница полей), UC-CHS-005. Синхронизация реплики из Talent Chessboard (system)
- События (1): Event Contract: pin.chessboard.building-upserted (BuildingUpsertedKafkaEvent)
- Консюмеры (2): Consumer: Chessboard Projection Sync (pin-catalog-chessboard-sync), Consumer: Talent Chessboard Sync (pin-chessboard-talent-sync)
- Фоновые задачи (2): Background Job: project-aggregates-recalc (пересчёт денормализованных агрегатов), Background Job: talent-initial-import (первичная загрузка / bootstrap из Talent Chessboard)
- index.md (1): Chessboard Service (Pin.Chessboard)
- dimensions (1): dim_building (ClickHouse dimension)
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- project.md — родитель; property-object.md — объекты недвижимости здания;
reference-data.md —
ConstructionStatus,ChessboardSyncSource. - ../consumers/talent-chessboard-sync.md — синк Talent.