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

DealSearchDocument

Поисковая проекция списков сделок DealSearchDocument: обслуживает поиск по Title и ФИО клиента в списках агента, застройщика и Менеджера ПИН (../api/agent/list-deals.md, ../api/developer/list-deals.md, ../api/pin-manager/list-deals.md). Проекция перестраиваемая — её всегда можно восстановить из источника истины.

Назначение

Источник истины — сущность DealEntity в PostgreSQL (crm.deals); проекция хранит только индексируемые/фильтруемые/сортируемые/возвращаемые в список поля + ФИО клиента (snapshot из ClientEntity на момент создания/смены). Прямого обращения к поисковому индексу из бизнес-логики нет — она работает с проекцией через типизированный поисковый запрос (ADR-0059).

Index

ПолеЗначение
Index aliascrm-deals
Versioned indexcrm-deals-v1 (переключение alias — ADR-0029)
Owner servicecrm
Source of truthDealEntity (../domain/deal.md) в PostgreSQL (crm.deals)
Rebuild jobпериодический/ручной ребилд DealSearchRebuildJob

Наполнение — инкрементально по доменным событиям DealEntity плюс периодический/ручной rebuild для bootstrap и восстановления консистентности (ADR-0059).

Document Id И Routing

  • Document id: <TenantId>:<DealId> (TenantId = DeveloperCompanyId, ADR-0052) — детерминирован, upsert идемпотентен.
  • Routing key: TenantId (DeveloperCompanyId) — сделки одного застройщика колокализуются (кластер малый, ADR-0056).
  • Delete/tombstone: soft-delete (DeletedAt != null, административное сокрытие) → удаление документа. Финалы (Completed/Cancelled) документ не удаляют (завершённые сделки ищутся в списках/статистике), лишь меняют фильтруемые Status/StepGroup.

Поля

ПолеТипIndexedStoredAnalyzerSourceSecurity
DocumentIdkeywordдадаTenantId:DealIdinclude
DealIdkeywordдадаDealEntity.Idinclude
TenantIdkeywordданетDealEntity.TenantId (DeveloperCompanyId)scope-only (не выдаётся)
OriginAgencyCompanyIdkeywordдадаDealEntity.OriginAgencyCompanyIdrecord-level scope (агентство-атрибуция)
ExecutorAgentUserIdkeywordдадаDealEntity.ExecutorAgentUserIdrecord-level scope (текущий исполнитель) + фильтр
PrimaryInvestorUserIdkeywordдадаDealEntity.PrimaryInvestorUserIdrecord-level scope («Мои сделки»)
Titletextдадаru_translit_fuzzyDealEntity.Titleinclude/матч
ClientNameSearchtextданетru_translit_fuzzyФИО клиента (snapshot из ClientEntity)PII (только матч, не выдаётся; маска в логах)
SearchTexttextданетru_translit_fuzzyTitle ⊕ ClientNameSearch (copy_to)основной матч поиска
StatuskeywordдадаDealEntity.Status (DealStatus)фильтр
StepGroupkeywordдадаDealEntity.StepGroup (PipelineStepGroup)фильтр (канбан-группы)
StepIdkeywordдадаDealEntity.StepIdфильтр
ProjectIdkeywordдадаDealEntity.ProjectId (реплика chessboard)фильтр (US-DEV-01)
BuildingIdkeywordдадаDealEntity.BuildingIdфильтр
PropertyObjectIdkeywordдадаDealEntity.PropertyObjectIdinclude
CreatedAtdateдадаDealEntity.CreatedAtфильтр-диапазон + сортировка
UpdatedAtdateдадаDealEntity.UpdatedAtфильтр-диапазон + сортировка (default desc) + freshness
SchemaVersionintegerдадаверсия схемы проекцииверсия схемы проекции
IndexedAtdateдадаиндексаторfreshness
Поисковый запрос — типизированный, без raw-DSL

Внешний ввод — только поисковая строка (multi-match по SearchText с fuzziness), не raw-DSL. Фильтры (Status/StepGroup/StepId/ProjectId/BuildingId/ExecutorAgentUserId/диапазоны CreatedAt/UpdatedAt) и сортировки (CreatedAt/UpdatedAt/relevance) задаются типизированными параметрами запроса. ФИО клиента индексируется для матча, но не возвращается в выдаче (Stored = нет).

Source Events

EventVersionTransform
DealCreated1upsert документа из состояния DealEntity (Title + ФИО клиента snapshot)
DealStageChanged / DealPropertyObjectChanged / DealUpdated1partial-upsert изменённых полей (Status/StepGroup/StepId/ProjectId/BuildingId/PropertyObjectId/Title/UpdatedAt)
DealCompleted / DealCancelled1partial-upsert Status (+StepGroup)
pin.identity.users.v11обновление ClientNameSearch при смене ФИО зарегистрированного клиента (по ClientId сделки)
soft-delete (DeletedAt)1delete документа по DocumentId

Проекция наполняется по доменным событиям агрегата DealEntity плюс периодический/ручной rebuild для bootstrap и восстановления консистентности из PostgreSQL.

Порядок и идемпотентность применения событий

События одной сделки упорядочены по DealId; partial-upsert применяется монотонно по UpdatedAt — более старое/переставленное событие документ не откатывает (отбрасывается как устаревшее); upsert по детерминированному DocumentId делает повтор идемпотентным. pin.identity.users.v1 для сделки, чей клиент ещё не отражён в проекции, — no-op. Неизвестный тип события пропускается (лог, без остановки индексатора); рассинхрон добирается ближайшим rebuild.

Freshness

  • SLA: search (near-real-time, < 1 мин; eventual относительно PostgreSQL, FreshnessView — ADR-0019).
  • Watermark: IndexedAt + UpdatedAt сделки в документе.
  • Stale behavior: показать результаты с freshness-маркером; актуальную карточку/канбан пользователь открывает через ../api/agent/get-deal.md, читающий source-of-truth PostgreSQL.

Security

  • Scope-фильтр применяется до выполнения запроса (ADR-0059, ADR-0014) — record-level: агент видит сделки, где он текущий исполнитель (ExecutorAgentUserId = actor); руководитель/админ агентства — сделки своего OriginAgencyCompanyId c фильтром по агенту; застройщик — сделки своего TenantId (DeveloperCompanyId); инвестор — только свои (PrimaryInvestorUserId = actor, «Мои сделки», без фиксаций); Менеджер ПИН — platform-scope (сквозные списки). Trimming — до query execution.
  • Полнотекстовый индекс не хранит secrets, tokens, платёжные/KYC-данные; TenantId — только scope-фильтр.
PII: ФИО клиента

ClientNameSearch (ФИО клиента) — индексируется только для матча, наружу не выдаётся (Stored = нет); в логах — маска. EscrowAccountNumber/документы/суммы в проекцию не кладутся.

Rebuild

  1. Создать версионный индекс crm-deals-vN (маппинг + ru_translit_fuzzy).
  2. Backfill из PostgreSQL: живые сделки (DeletedAt == null) стримом по DealEntity, map в DealSearchDocument (ФИО клиента — join crm.clients по ClientId).
  3. Валидация counts/checksums против источника (crm.deals).
  4. Атомарно переключить alias crm-deals на новую версию.
  5. Держать предыдущий индекс до конца rollback-окна.

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

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

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