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

Entity / Aggregate: ProjectEntity

Дом полей агрегата «Проект» (ЖК / апарт-отель) контекста chessboard. Все проекции (лента и карточка проекта в catalog, измерение Project в dev-stats, payload событий) ссылаются сюда и описывают только дельту.

Назначение

ProjectEntity — единица застройки застройщика: жилой комплекс или апарт-отель. Инвестор видит ленту published-проектов, ищет и фильтрует по типу/классу/региону/статусу строительства/цене, открывает карточку с вкладками: Информация, Локация, Финансирование, Юридическая информация, Ход строительства, Видео, Карта. Проект агрегирует здания (литеры/корпуса) и объекты недвижимости, несёт диапазон цен и окупаемость для карточки, декларации и ход строительства.

Гибридная модель данных

Как и весь домен шахматки — гибрид: базовые данные реплицируются из внешней системы Talent Chessboard, PIN дополняет и корректирует локальные поля.

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

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

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKapp / Talent syncИдентификатор проекта.
TenantIdGuidДаindex ix_projects_tenant_id; = DeveloperIdappTenant boundary (ADR-0052).
DeveloperIdGuidДаFK fk_projects_developerchessboard.developers.id, indexapp / Talent syncЗастройщик-владелец. Дублирует TenantId семантически (единое значение, INV-PRJ-1).
StatusProjectStatusДаenum (текст), varchar(32), index ix_projects_statusappDraft / Published / Hidden / Archived (см. stateDiagram). Лента показывает только Published.
NamestringДаvarchar(256), min 1, index ix_projects_name (lower)Talent sync / appНазвание проекта (ЖК/отеля). Полнотекстовый поиск ленты (name+address) — Elasticsearch-проекция search/projects.md (ADR-0059).
Descriptionstring?Нетvarchar(8000)app / Talent syncПубличное описание (вкладка «Информация»).
TypeProjectTypeДаenum (текст), varchar(32), index ix_projects_typeTalent sync / appТип проекта: ResidentialComplex / ApartHotel / Hotel / Villareference-data.md. Фильтр ленты.
ClassProjectClassДаenum (текст), varchar(32), index ix_projects_classTalent sync / appКласс: Standard / Comfort / Business / Premium / Elitereference-data.md. Фильтр ленты.
ConstructionStatusConstructionStatusДаenum (текст), varchar(32), index ix_projects_construction_statusTalent sync / appСтатус строительства проекта в целом: Planned / UnderConstruction / Commissioned / Deliveredreference-data.md. Фильтр ленты.
CountryIdshortДалогическая ссылка на справочник стран (реплика handbook-replica.md, без FK); default — страна «Россия» (запись с IsoCode2 = 'RU')app / Talent syncСтрана проекта.
RegionIdlong?Нетлогическая ссылка на справочник регионов (реплика, без FK), index ix_projects_region_id (where not null)app / Talent sync (name-match)Регион проекта — ключ фильтра «регион» ленты. Обязателен (резолвлен) при публикации (INV-PRJ-3).
RegionNamestring?Нетvarchar(128)реплика при записи / строка Talent при нерезолвленном синкеDisplay-имя региона (денормализация для карточки/ленты): при RegionId != null снимается из реплики; при нерезолвленном name-match синка Talent — исходное имя из Talent (RegionId = null, talent-chessboard-sync); refresh при переименовании в справочнике — consumer handbook-sync (HR-6, handbook-replica.md).
CityIdlong?Нетлогическая ссылка на справочник городов (реплика, без FK), index ix_projects_city_id (where not null)app / Talent sync (name-match)Город проекта. Обязателен (резолвлен) при публикации (INV-PRJ-3).
CityNamestring?Нетvarchar(128)реплика при записи / строка Talent при нерезолвленном синкеDisplay-имя города: при CityId != null снимается из реплики; при нерезолвленном синке Talent — исходное имя из Talent (CityId = null); refresh — consumer handbook-sync (HR-6).
Starsshort?Нетcheck stars between 1 and 5; заполняется при Type in (ApartHotel, Hotel)Talent sync / appЗвёздность отеля.
HotelierIdGuid?НетFK fk_projects_hotelierchessboard.hoteliers.id, index where not nullappОператор апарт-отеля (hotelier.md); обязателен для Type in (ApartHotel, Hotel) при публикации (INV-PRJ-4).
LocationProjectLocationDocument (jsonb)Да (дефолт {} v1)jsonb, typedapp / Talent syncЛокация: адрес, гео, транспорт (вкладки «Локация», «Карта»).
InfrastructureProjectInfrastructureDocument (jsonb)Да (дефолт)jsonb, typedappИнфраструктура проекта и окружения.
FinancingProjectFinancingDocument (jsonb)Да (дефолт)jsonb, typedappФинансирование: эскроу-банки, проектное финансирование (вкладка «Финансирование»).
LegalInfoProjectLegalInfoDocument (jsonb)Да (дефолт)jsonb, typedappЮридические данные: застройщик-юрлицо, разрешения, проектные декларации (вкладка «Юридическая информация»).
ConstructionProgressProjectConstructionProgressDocument (jsonb)Да (дефолт)jsonb, typedappХод строительства: отчёты по периодам, фото/видео, вебкамеры (вкладки «Ход строительства», «Видео»).
PresentationVideoUrlstring?Нетvarchar(512), валидация URLappГлавное презентационное видео карточки.
CoverImageFileIdGuid?Нетбез FK (storage/CDN)app / Talent syncОбложка карточки в ленте.
PriceNullableRange<decimal>Нетколонки price_min / price_max, numeric(14,2), >= 0, price_max >= price_min, index ix_projects_price_minвычисляется (background)Диапазон цен свободных объектов недвижимости проекта (Price.MinPrice.Max, денормализация для ленты и фильтра «ценовой диапазон»; пересчёт — job project-aggregates-recalc по событиям объектов недвижимости).
CurrencyIdshortДалогическая ссылка на справочник валют (реплика handbook-replica.md, без FK); default — валюта «RUB» (запись с IsoCode = 'RUB')appВалюта цен проекта (одна на проект, INV-PRJ-6). Типизированная ссылка ≠ мультивалютность: денежные операции остаются RUB-only (ADR-0048); в исходящих событиях — CurrencyId + CurrencySnapshot (snapshots.md).
PropertyObjectsTotalCountintДа>= 0, default 0вычисляется (background)Всего объектов недвижимости (карточка «кол-во объектов»).
PropertyObjectsFreeCountintДа>= 0, default 0вычисляется (background)Свободных объектов недвижимости.
PaybackYearsdecimal?Нетnumeric(5,2), > 0app / invest-analytics (ручной ввод либо расчёт)Окупаемость проекта в годах (карточка ленты; детальный расчёт — контекст invest-analytics).
ExternalTalentProjectIdGuid?Нетunique ux_projects_external_talent_project_id (where not null)Talent syncId проекта в Talent Chessboard.
SyncSourceChessboardSyncSourceДаenum (текст), varchar(32)app / consumerPin / TalentChessboard (реплика-поля: Name, Type, Class, ConstructionStatus, гео-поля CountryId/RegionId/RegionName/CityId/CityName, Stars, CoverImageFileId, address-часть Location).
ReplicaVersionlong?Нет (обязателен при реплике)>= 0Talent syncAnti-out-of-order guard (INV-PRJ-5).
LastSyncedAtDateTimeOffset?НетTalent syncВремя последнего применённого события синка.
CreatedAtDateTimeOffsetДаindex ix_projects_created_at (сортировка «по дате добавления»)appДата создания.
CreatedByUserIdGuid?НетappКто создал.
UpdatedAtDateTimeOffsetДаappДата изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
DeletedAtDateTimeOffset?Нетpartial-index filterappSoft-delete; заполняется вместе с Status = Archived.
DeletedByUserIdGuid?НетappКто архивировал.
VersionlongДа>= 1appВерсия агрегата.

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

ЗначениеОписание
DraftПроект заведён (синком/менеджером), заполняется; в публичных проекциях отсутствует.
PublishedОпубликован: попадает в ленту/поиск/фильтры каталога.
HiddenВременно скрыт застройщиком/ПИН: не отдаётся публично, данные и объекты недвижимости сохранны.
ArchivedАрхив (soft-delete, DeletedAt заполнен): продажи завершены/проект снят; исторические ссылки живы.

Value objects

GeoPoint (внутри ProjectLocationDocument)

ПолеТипОбязательностьОграниченияПуть
LatitudedoubleДа-90..90jsonb path location.geo.latitude
LongitudedoubleДа-180..180jsonb path location.geo.longitude

PriceNullableRange<decimal>

Диапазон цен свободных объектов недвижимости проекта, хранится двумя границами price_min / price_max (обе nullable); инвариант price_max >= price_min при обоих заполненных значениях; значения вычисляются фоновым процессом project-aggregates-recalc. Индекс ix_projects_price_min — по нижней границе (фильтр «ценовой диапазон» ленты). Остальные плоские поля — обычные колонки; составные структуры проекта живут в typed jsonb (ниже).

Модели JSONB (versioned)

Версионирование JSONB-моделей

Все пять документов — самостоятельные типизированные модели c собственным ContractVersion (добавление поля — additive той же версией; изменение семантики — bump версии); бессхемные object/metadata запрещены.

ProjectLocationDocument (jsonb, колонка location, ContractVersion = 1)

Страна/регион/город — не в документе, а в колонках-ссылках проекта (CountryId/RegionId/CityId + display-имена, см. таблицу полей); документ несёт только адресные детали ниже уровня города и гео-данные.

ПолеТипОбязательностьОграниченияSince version
ContractVersionintДа= 1v1
Districtstring?Нетvarchar(128) — район; свободная строка (сущности «район» в справочнике нет)v1
AddressLinestring?Нетvarchar(512) — улица, домv1
PostalCodestring?Нетvarchar(16)v1
GeoGeoPoint?Нетсм. VO вышеv1
SeaDistanceMetersint?Нет>= 0 — расстояние до моряv1
CenterDistanceMinutesint?Нет>= 0 — минут до центраv1
TransportNodesIReadOnlyList<TransportNodeModel>Да (может быть пустым)≤ 20 элементовv1

Вложенная модель TransportNodeModel

ПолеТипОбязательностьОграниченияSince version
KindTransportNodeKind (enum-string)ДаMetro / Airport / Railway / Highway / BusStopv1
NamestringДаvarchar(128)v1
DistanceMinutesintДа0..600v1
TravelModeTravelMode (enum-string)ДаWalk / Car / PublicTransportv1

ProjectInfrastructureDocument (jsonb, колонка infrastructure, ContractVersion = 1)

ПолеТипОбязательностьОграниченияSince version
ContractVersionintДа= 1v1
OwnItemsIReadOnlyList<InfrastructureItemModel>Да (может быть пустым)≤ 50 элементов — инфраструктура самого проекта (паркинг, СПА, бассейн, ресторан)v1
NearbyItemsIReadOnlyList<InfrastructureItemModel>Да (может быть пустым)≤ 50 элементов — окружение (школы, магазины, пляж)v1

Вложенная модель InfrastructureItemModel

ПолеТипОбязательностьОграниченияSince version
KindInfrastructureKind (enum-string)ДаParking / Pool / Spa / Fitness / Restaurant / Kindergarten / School / Shop / Beach / Park / Medical / Otherv1
TitlestringДаvarchar(256)v1
DistanceMinutesint?Нет0..600; null для собственной инфраструктурыv1
IconFileIdGuid?Нетstorage/CDNv1

ProjectFinancingDocument (jsonb, колонка financing, ContractVersion = 1)

ПолеТипОбязательностьОграниченияSince version
ContractVersionintДа= 1v1
SalesModelSalesModel (enum-string)ДаDdu214 (ДДУ, эскроу) / Dkp (ДКП) / Mixed; дефолт Ddu214v1
EscrowBankIdsIReadOnlyList<Guid>Да (может быть пустым)каждый — существующий chessboard.banks.id (валидация при записи, INV-PRJ-7); ≤ 10v1
ProjectFinancingBankIdGuid?Нетсуществующий chessboard.banks.idv1
MortgageAvailableboolДадефолт falsev1
MortgageBankIdsIReadOnlyList<Guid>Да (может быть пустым)существующие banks.id; ≤ 20v1
InstallmentAvailableboolДадефолт false — наличие рассрочки (параметры программ — контекст crm, booking)v1
ПолеТипОбязательностьОграниченияSince version
ContractVersionintДа= 1v1
LegalEntityNamestring?Нетvarchar(512) — юрлицо-застройщик проекта (СПВ)v1
Innstring?Нетvarchar(12), pattern ^\d{10}(\d{2})?$v1
Ogrnstring?Нетvarchar(15), pattern ^\d{13}(\d{2})?$v1
BuildingPermitNumberstring?Нетvarchar(128) — разрешение на строительствоv1
BuildingPermitIssuedAtDateOnly?Нетv1
LandPlotCadastralNumberstring?Нетvarchar(64)v1
DeclarationsIReadOnlyList<ProjectDeclarationModel>Да (может быть пустым)≤ 50 элементовv1

Вложенная модель ProjectDeclarationModel

ПолеТипОбязательностьОграниченияSince version
TitlestringДаvarchar(256) — «Проектная декларация №N»v1
DocumentFileIdGuidДаstorage/CDNv1
PublishedAtDateOnlyДаv1

ProjectConstructionProgressDocument (jsonb, колонка construction_progress, ContractVersion = 1)

ПолеТипОбязательностьОграниченияSince version
ContractVersionintДа= 1v1
WebcamUrlsIReadOnlyList<string>Да (может быть пустым)элемент varchar(512), URL; ≤ 10v1
ReportsIReadOnlyList<ConstructionReportModel>Да (может быть пустым)≤ 200 элементов; сортировка по Period descv1

Вложенная модель ConstructionReportModel

ПолеТипОбязательностьОграниченияSince version
PeriodstringДаchar(7), формат YYYY-MMv1
TitlestringДаvarchar(256)v1
Descriptionstring?Нетvarchar(4000)v1
PhotoFileIdsIReadOnlyList<Guid>Да (может быть пустым)≤ 100v1
VideoUrlsIReadOnlyList<string>Да (может быть пустым)URL, ≤ 10v1
ReadinessPercentdecimal?Нет0..100, precision (5,2) — готовность проекта на конец периодаv1

Связи

СвязьТипКлючDelete behavior
DeveloperEntityreference (родитель)projects.developer_id → developers.idrestrict
BuildingEntity (здания)reference (обратная)buildings.project_id → projects.idrestrict (архив проекта каскадно скрывает public-проекции зданий/объектов недвижимости, физически не удаляет)
PropertyObjectEntity (объекты недвижимости)reference (обратная, через здание и напрямую)property_objects.project_id → projects.idrestrict
LayoutGroupEntity / LayoutEntityreference (обратная)layout_groups.project_id, layouts.project_idrestrict
HotelierEntityreferenceprojects.hotelier_id → hoteliers.idrestrict
BankEntity (эскроу/ипотека)логическая (id внутри Financing jsonb, без FK)Financing.EscrowBankIds[]— (архив банка не ломает проект; проекции фильтруют)
Talent projectreplicaExternalTalentProjectId

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

ИнвариантУсловиеКогда проверяетсяОшибка
INV-PRJ-1 TenantEqualsDeveloperTenantId == DeveloperId всегдапри созданииCHESSBOARD_PROJECT_TENANT_MISMATCH
INV-PRJ-2 DeveloperExistsDeveloperId существует и не удалёнпри созданииCHESSBOARD_DEVELOPER_NOT_FOUND
INV-PRJ-3 PublishRequiresDataПубликация только при заполненных Name, Description, CoverImageFileId, Location.Geo, резолвленных RegionId и CityId (ссылки справочника, не null) и Developer.Status = Publishedпри публикацииCHESSBOARD_PROJECT_PUBLISH_INCOMPLETE
INV-PRJ-4 HotelRequiresHotelierПри Type in (ApartHotel, Hotel) публикация требует HotelierId != nullпри публикацииCHESSBOARD_PROJECT_HOTELIER_REQUIRED
INV-PRJ-5 MonotonicReplicaСобытие синка применяется только при event.Version > ReplicaVersionпри применении события синкаидемпотентный пропуск
INV-PRJ-6 SingleCurrencyВсе объекты недвижимости проекта имеют CurrencyId = Project.CurrencyIdпри изменении объекта недвижимостиCHESSBOARD_PROPERTY_OBJECT_CURRENCY_MISMATCH
INV-PRJ-7 BanksExistКаждый id в Financing.EscrowBankIds / MortgageBankIds / ProjectFinancingBankId существуетпри записи FinancingCHESSBOARD_PROJECT_BANK_NOT_FOUND
INV-PRJ-8 ArchiveConsistencyStatus = ArchivedDeletedAt != null; архив допустим только без объектов недвижимости в Reserved/Bookedпри архивированииCHESSBOARD_PROJECT_HAS_ACTIVE_HOLDS
INV-PRJ-9 SyncOwnedFieldsРеплика-поля меняются только синком; PIN-поля — только локальнопри изменении полей (раздельные пути)CHESSBOARD_PROJECT_FIELD_SYNC_MANAGED
INV-PRJ-10 HandbookRefsValidCountryId/RegionId/CityId/CurrencyId существуют в справочнике и не архивны; гео-цепочка согласована (city.CountryId = CountryId; при заданном RegionIdcity.RegionId = RegionId и region.CountryId = CountryId) — проверка по реплике (handbook-replica.md)при создании/изменении значения справочной ссылкиCHESSBOARD_GEO_REF_UNKNOWN / CHESSBOARD_GEO_REF_ARCHIVED / CHESSBOARD_CITY_REGION_MISMATCH (см. таблицу ошибок)

Переходы состояний (из / в / условие / инициатор / событие)

ИзВУсловие (guard)ИнициаторПорождает
[*]DraftDeveloperId существует и не удалён (INV-PRJ-2); TenantId = DeveloperId (INV-PRJ-1)синк Talent / застройщик (Developers API)ProjectCreatedEvent + ProjectUpsertedKafkaEvent
DraftPublishedзаполнены Name, Description, CoverImageFileId, Location.Geo; резолвлены RegionId и CityId; Developer.Status = Published (INV-PRJ-3); при Type in (ApartHotel, Hotel)HotelierId != null (INV-PRJ-4)застройщик / ПИНProjectStatusChangedEvent + ProjectUpsertedKafkaEvent
PublishedHiddenрешение застройщика/ПИН (временное скрытие; данные и объекты сохранны)застройщик / ПИНProjectStatusChangedEvent + ProjectUpsertedKafkaEvent
HiddenPublishedDeveloper.Status = Publishedзастройщик / ПИНProjectStatusChangedEvent + ProjectUpsertedKafkaEvent
PublishedDraftснятие с публикации для доработкизастройщик / ПИНProjectStatusChangedEvent + ProjectUpsertedKafkaEvent
Draft / HiddenArchivedStatus = Archived ⇔ DeletedAt != null; нет объектов недвижимости в Reserved/Booked (INV-PRJ-8)застройщик / ПИНProjectStatusChangedEvent + ProjectUpsertedKafkaEvent
Терминальность, каскады и синк

Терминальность: Archived — конечное состояние (soft-delete, DeletedAt заполнен); исторические ссылки (сделки crm, статистика dev-stats) остаются валидны. Hidden профиля застройщика каскадно скрывает public-проекции всех его проектов фильтром при чтении (INV-DEV-6), не меняя Project.Status. Реплика-поля проекта (Name, Type, Class, ConstructionStatus, гео-поля, Stars, CoverImageFileId, address-часть Location) меняет только синк, публичность и PIN-вкладки — только локально (INV-PRJ-9). Публикация каскадом из синка не делается — синк заводит проект в Draft.

Ошибки справочных ссылок (INV-PRJ-10)

Резолв справочных ссылок при синке

Валидация выполняется по локальной реплике справочников с fallback-резолвом в handbook при лаге реплики — механика и порядок проверок: handbook-replica.md. Синк Talent резолвит адресные строки внешней системы в ссылки справочника name-match-ем по реплике; нерезолвленная ссылка (RegionId/CityId = null, исходное имя Talent сохраняется в display-поле RegionName/CityName, warn-метрика) не блокирует применение синка, но блокирует публикацию проекта (INV-PRJ-3) до ручного разрешения (../consumers/talent-chessboard-sync.md).

КодHTTPУсловие
CHESSBOARD_GEO_REF_UNKNOWN422справочная ссылка (CountryId/RegionId/CityId/CurrencyId) не найдена ни в реплике, ни fallback-резолвом
CHESSBOARD_GEO_REF_ARCHIVED422при создании/изменении указана архивная запись справочника
CHESSBOARD_CITY_REGION_MISMATCH422город/регион/страна рассогласованы (гео-цепочка не сходится)

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

СобытиеТипКогдаSnapshot/поля
ProjectCreatedEventдоменноеУспешное созданиеId, DeveloperId, SyncSource
ProjectStatusChangedEventдоменноеЛюбой переход StatusId, From, To, actor
ProjectAggregatesRecalcRequestedEventдоменноеИзменение цен/статусов объектов недвижимостиProjectId — триггер пересчёта Price (Min/Max), PropertyObjectsFreeCount
ProjectUpsertedKafkaEventинтеграционное (ContractVersion = 1, топик pin.chessboard.project-upserted.v1, key = Id)Создание и каждое значимое обновление (Name/Status/Type/Class/ConstructionStatus/гео-полей/цены/jsonb-вкладки), а также refresh display-имён при переименовании в справочнике (HR-6, handbook-replica.md)Snapshot: Id, TenantId, DeveloperId, Status, Name, Type, Class, ConstructionStatus, гео-ссылки CountryId/RegionId/CityId + снапшоты CountrySnapshot/RegionSnapshot/CitySnapshot (snapshots.md), Stars, HotelierId, Price (NullableRange<decimal>), CurrencyId + CurrencySnapshot, PropertyObjectsTotalCount, PropertyObjectsFreeCount, PaybackYears, CoverImageFileId, Version. Потребители: catalog (витрина), dev-stats (измерение Project), identity (scope «группа проектов/проект»), news (привязка новостей к проекту)

Интеграционные события публикуются атомарно со сменой состояния агрегата.

Кросс-контекстный контракт (ProjectUpsertedKafkaEvent)

  • Ключ / версия. key = Id, ContractVersion = 1, монотонный Version; потребители — upsert-by-id, доставка at-least-once, дедуп по Id+Version.
  • Что предоставляет. Snapshot карточки/ленты; топик pin.chessboard.project-upserted.v1 — источник проекта для catalog (витрина/лента/фильтры), dev-stats (измерение Project), identity (материализация scope «группа проектов/проект» для Scoped-RBAC), news (привязка новостей к проекту).
  • Ценовые/счётные поля — производные. Price (Min/Max), PropertyObjectsTotalCount, PropertyObjectsFreeCount пересчитывает джоб project-aggregates-recalc по событиям объектов недвижимости (debounce 30 с) и переопубликует ProjectUpsertedKafkaEvent при их изменении. Downstream получают их из этого же snapshot, отдельного «счётного» события нет.
  • Порядок между сущностями. События Project/Building/Layout/LayoutGroup/PropertyObject идут разными топиками, каждый keyed своим Idкросс-сущностный порядок не гарантирован; catalog строит витрину идемпотентно по Id+Version, толерантно к порядку прихода родитель/дочерний.
  • Окно eventual-consistency. Публикация проекта → появление в ленте/поиске catalog: асинхронно, лаг — доставка Kafka + применение (+ Elasticsearch-проекция для поиска, search/projects.md).

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK pk_projects; ux_projects_external_talent_project_id unique where not null; ix_projects_tenant_id; ix_projects_developer_id; ix_projects_status; ix_projects_type; ix_projects_class; ix_projects_construction_status; ix_projects_region_id (where not null); ix_projects_city_id (where not null); ix_projects_price_min; ix_projects_created_at. Все рабочие индексы — where deleted_at is null. Полнотекстовый поиск ленты (name+address) — Elasticsearch-проекция ProjectSearchDocument (search/projects.md, ADR-0059)
Партиционирование / retentionnone — проектов тысячи; retention бессрочный (soft-delete)
Concurrencyоптимистическая блокировка на HTTP-мутациях; для событий синка — guard по ReplicaVersion (INV-PRJ-5); денормализованные счётчики обновляются точечно, без загрузки агрегата
Permissions / PIIPublic-чтение Published — без permission, но неавторизованным скрываются точные цены и шахматка (проекция «ограниченная карточка»); CRUD своих проектов — chessboard.projects.manage (scope = организация/группа проектов/проект); admin — полный доступ (ADR-0053). PII нет; юрданные (LegalInfo) — публичные по 214-ФЗ
Auditверсия агрегата + отметки кто/когда; значимые изменения — события

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

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

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