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

Entity / Aggregate: BuildingEntity

Дом полей агрегата «Здание» (литер/корпус) контекста chessboard. Проекции (шахматка, карточка проекта в catalog, измерение Building в dev-stats) ссылаются сюда.

Назначение

BuildingEntity — строение внутри проекта застройщика: литер, корпус, очередь. Несёт этажность (ось «этаж» шахматки), срок сдачи и готовность. Сущность гибридная: базовые поля реплицируются из внешней системы Talent Chessboard (через события синка), а часть полей PIN заводит и корректирует локально — синком они не перезаписываются.

Классификация

ПолеЗначение
Контекстchessboard
KindAggregateRoot (гибрид с репликой Talent)
Source of truthchessboard (PIN-поля) / talent.microservices.chessboard (реплика-поля)
Таблицаchessboard.buildings
Tenant isolationTenantId = DeveloperId проекта (ADR-0052); публичное чтение — только для Published зданий published-проектов

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKapp / Talent syncИдентификатор здания.
TenantIdGuidДаindex ix_buildings_tenant_id; = Project.TenantIdappTenant boundary (ADR-0052).
ProjectIdGuidДаFK fk_buildings_projectchessboard.projects.id, index ix_buildings_project_idapp / Talent syncПроект-родитель.
StatusBuildingStatusДаenum (текст), varchar(32), index ix_buildings_statusappDraft / Published / Hidden (см. stateDiagram).
NamestringДаvarchar(128), min 1; unique ux_buildings_project_name (ProjectId, lower(Name)) where deleted_at is nullTalent sync / appНазвание: «Литер 3», «Корпус Б». Уникально в проекте (INV-BLD-2).
Codestring?Нетvarchar(32)Talent syncТехнический код литера в системе застройщика/Talent.
FloorsTotalshortДаcheck floors_total between 1 and 200Talent sync / appЭтажность — ось Y шахматки; объекты недвижимости валидируются Floor <= FloorsTotal (INV-BLD-3).
SectionsCountshort?Нетcheck sections_count between 1 and 50Talent sync / appЧисло секций/подъездов — ось X шахматки (объект недвижимости несёт Section).
DeliveryYearshort?Нетcheck delivery_year between 2000 and 2100; составной index ix_buildings_delivery (delivery_year, delivery_quarter)Talent sync / appГод сдачи (срок сдачи корпуса).
DeliveryQuartershort?Нетcheck delivery_quarter between 1 and 4; обязателен при заполненном DeliveryYearTalent sync / appКвартал сдачи; отображение «IV кв. 2027».
DeliveredAtDateOnly?НетappФактическая дата ввода в эксплуатацию (заполняется при ConstructionStatus = Delivered).
ConstructionStatusConstructionStatusДаenum (текст), varchar(32), index ix_buildings_construction_statusTalent sync / appСтатус строительства здания — reference-data.md; может отличаться от статуса проекта (очереди).
ReadinessPercentdecimalДаnumeric(5,2), check readiness_percent between 0 and 100, default 0Talent sync / appГотовность здания, %.
CeilingHeightMetersdecimal?Нетnumeric(4,2), check between 2 and 10Talent sync / appТиповая высота потолков (может переопределяться объектом недвижимости).
PropertyObjectsTotalCountintДа>= 0, default 0вычисляется (background)Денормализованный счётчик объектов недвижимости здания (job project-aggregates-recalc).
PropertyObjectsFreeCountintДа>= 0, default 0вычисляется (background)Свободных объектов недвижимости.
ExternalTalentBuildingIdGuid?Нетunique ux_buildings_external_talent_building_id (where not null)Talent syncId здания в Talent Chessboard.
SyncSourceChessboardSyncSourceДаenum (текст), varchar(32)app / consumerPin / TalentChessboard (реплика-поля: Name, Code, FloorsTotal, SectionsCount, Delivery*, ConstructionStatus, ReadinessPercent).
ReplicaVersionlong?Нет (обязателен при реплике)>= 0Talent syncAnti-out-of-order guard (INV-BLD-5).
LastSyncedAtDateTimeOffset?НетTalent syncВремя последнего применённого события синка.
CreatedAtDateTimeOffsetДаindex ix_buildings_created_atappДата создания.
CreatedByUserIdGuid?НетappКто создал.
UpdatedAtDateTimeOffsetДаappДата изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
DeletedAtDateTimeOffset?Нетpartial-index filterappSoft-delete.
DeletedByUserIdGuid?НетappКто удалил.
VersionlongДа>= 1appВерсия агрегата (идёт в payload событий).

Enum BuildingStatus (хранится текстом)

ЗначениеОписание
DraftЗаведено, заполняется; в публичных проекциях (шахматка, ассортимент) отсутствует.
PublishedПоказывается в шахматке/ассортименте published-проекта.
HiddenСкрыто застройщиком (например, очередь не в продаже); объекты недвижимости сохранны, публично не отдаются.

Связи

СвязьТипКлючDelete behavior
ProjectEntityreference (родитель)buildings.project_id → projects.idrestrict
PropertyObjectEntityreference (обратная)property_objects.building_id → buildings.idrestrict (скрытие здания скрывает объекты недвижимости в public-проекциях, не мутирует их)
Talent buildingreplicaExternalTalentBuildingId

Инварианты и переходы состояний

ИнвариантУсловиеКогда проверяетсяОшибка
INV-BLD-1 TenantFromProjectTenantId == Project.TenantIdпри создании (tenant берётся из проекта)CHESSBOARD_BUILDING_TENANT_MISMATCH
INV-BLD-2 UniqueNameInProjectName уникально среди не удалённых зданий проектапри создании и переименовании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 DeliveredConsistencyConstructionStatus = DeliveredDeliveredAt != 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
DraftPublishedзаполнены FloorsTotal, DeliveryYear + DeliveryQuarter; Project.Status in (Draft, Published) (INV-BLD-4)застройщикBuildingStatusChangedEvent + BuildingUpsertedKafkaEvent
PublishedHidden— (очередь снята с продажи; объекты недвижимости сохранны, публично не отдаются)застройщик / ПИНBuildingStatusChangedEvent + BuildingUpsertedKafkaEvent
HiddenPublishedProject.Status in (Draft, Published)застройщикBuildingStatusChangedEvent + BuildingUpsertedKafkaEvent
PublishedDraftснятие с публикации для доработкизастройщик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
Разделение владения полями (INV-BLD-7)

Реплика-поля (Name, FloorsTotal, Delivery*, ConstructionStatus, ReadinessPercent и др.) меняет только синк Talent; PIN-поля и публичность — только локально: публикация/скрытие — решение застройщика, синк статус публичности не трогает. Скрытие/архив здания скрывает его объекты недвижимости в public-проекциях, но не мутирует их статусы продажи (те — только CRM/синк).

Доменные и интеграционные события

СобытиеТипКогдаSnapshot/поля
BuildingCreatedEventдоменноеУспешное созданиеId, ProjectId, SyncSource
BuildingStatusChangedEventдоменноеПереход StatusId, 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. Оба получают только реплика/структурные поля здания; данные о продажах объектов — из отдельного потока.
Eventual consistency

Между сменой состояния здания и обновлением витрины 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
Партиционирование / retentionnone — зданий десятки тысяч максимум; retention бессрочный (soft-delete)
Concurrencyоптимистическая блокировка (правка не должна затирать чужую); для событий синка — guard по ReplicaVersion; денормализованные счётчики обновляются точечно
Permissions / PIIPublic-чтение — только Published здания published-проектов; CRUD — chessboard.buildings.manage (scope застройщика); PII нет
Auditверсия агрегата + отметки кто/когда; значимые изменения — события

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

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

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