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

Event Contract: pin.crm.lead-booking-intent-changed (LeadBookingIntentChangedEvent)

Версионируемый контракт интеграционного события «намерение бронирования лида изменено». Consumer-документы (notifications, опционально messenger) ссылаются сюда, не переописывая payload. Фиксирует любое изменение намерения бронирования (LeadBookingIntentEntity) — «хотелки инвестора на конкретный объект»: добавление намерения, подтверждение инвестором предложенного агентом объекта (Flow B), снятие/отмену намерения. Дом полей — entity: LeadEntity (раздел LeadBookingIntentEntity — намерения бронирования).

Назначение

Событие даёт downstream-контекстам факт изменения намерения бронирования без синхронного запроса в crm. Основной потребитель — notifications: уведомить исполнителя лида о новом/подтверждённом намерении инвестора и уведомить инвестора о предложенном агентом объекте, ждущем его подтверждения (Flow B). Опциональный потребитель — messenger: показать карточку объекта намерения в чате лида (ChatKind.Lead, ADR-0055). Намерения ведут путь «инвестор ↔ объект» внутри лида: из подтверждённого намерения при одобренной фиксации застройщика порождается сделка (гейт Draft → Application, см. deal.md), поэтому смена состояния намерения — сигнал для адресных уведомлений сторон и для карточки объекта в переписке лида.

Кто инициировал намерение, различает enum Source (InvestorAdded — Flow A, инвестор сам добавил объект, подтверждено сразу; AgentProposed — Flow B, агент предложил объект / завёл сделку по незапрошенному объекту, ждёт подтверждения инвестором), а характер изменения — enum ChangeKind (Added/Confirmed/Removed). Комбинация Source + ChangeKind + ChangedByUserId позволяет notifications адресовать уведомление противоположной стороне (инициатору не дублировать), не переспрашивая crm.

Metadata

ПолеЗначение
Event typepin.crm.lead-booking-intent-changed
Topicpin.crm.lead-booking-intent-changed.v1
Partitions / key3 (crm — низкообъёмный, 1–10k активных лидов, ADR-0056) / key = LeadId (порядок изменений намерений одного лида на одной партиции)
ContractVersion1
Producercrm: добавление намерения (InvestorAdded/AgentProposed), подтверждение инвестором (Flow B), снятие/отмена намерения (lead.md, инварианты INV-INTENT-*)
Статусdraft
Атомарность публикации

Событие публикуется атомарно с мутацией намерения: пока изменение не зафиксировано, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = LeadId.

Payload LeadBookingIntentChangedEvent

Дом полей — entity: LeadEntity / LeadBookingIntentEntity (раздел «Намерения бронирования» + «Доменные и интеграционные события»); здесь перечислено только то, что реально в событии. PII в payload отсутствует — только идентификаторы. ChangeKind несёт характер изменения, а связка Source + ConfirmedByInvestor — контекст подтверждения; получателей уведомления notifications определяет из собственной проекции состава лида (наполняется lead-created / lead-participants-changed), а ChangedByUserId разводит инициатора и контрагента.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
LeadIdGuidДаLeadBookingIntentEntity.LeadId (= LeadEntity.Id)ImmutableКлюч агрегата (= Kafka key). Родительский лид намерения; чат лида в messenger (ChatKind.Lead).
TenantIdGuidДаLeadBookingIntentEntity.TenantIdImmutableAgencyCompanyId агентства-владельца лида (ADR-0052, ADR-0071).
IntentIdGuidДаLeadBookingIntentEntity.IdImmutableИдентификатор намерения — бизнес-ключ идемпотентности у потребителя (не более одной реакции на пару IntentId + ChangeKind).
PropertyObjectIdGuidДаLeadBookingIntentEntity.PropertyObjectIdImmutableОбъект недвижимости намерения (логическая ссылка на chessboard).
ProjectIdGuidДаLeadBookingIntentEntity.ProjectIdImmutableПроект застройщика объекта (контекст; логическая ссылка на chessboard).
DeveloperCompanyIdGuidДаLeadBookingIntentEntity.DeveloperCompanyIdImmutableЗастройщик — владелец объекта; пара (инвестор × застройщик) определяет требуемую фиксацию для гейта сделки.
Sourcestring (enum LeadBookingIntentSource)ДаLeadBookingIntentEntity.SourceAdditive onlyКто инициировал намерение: InvestorAdded (Flow A) / AgentProposed (Flow B). Иммутабельно (модель ниже).
ChangeKindstring (enum BookingIntentChangeKind)Дахарактер операцииAdditive onlyЧто произошло с намерением: Added / Confirmed / Removed (модель ниже).
ConfirmedByInvestorboolДаLeadBookingIntentEntity.ConfirmedByInvestorAdditive onlyПодтверждено инвестором. InvestorAddedtrue сразу; AgentProposedfalse до подтверждения (Flow B).
ChangedByUserIdGuid?Нетактор операции (AddedByUserId / подтвердивший инвестор / RemovedByUserId)Additive onlyКто изменил намерение; null — система (консюмер заявки / backfill). Разводит инициатора и контрагента при адресации уведомления.
ChangedAtDateTimeOffsetДамомент мутации (CreatedAt / ConfirmedAt / RemovedAt)Additive onlyВремя изменения намерения.
VersionlongДаLeadEntity.VersionImmutable semanticsДоменная версия лида; монотонный guard порядка у потребителя.

Enum LeadBookingIntentSource

Enum агрегата (lead.md); в событии передаётся строкой. Иммутабелен после создания намерения (INV-INTENT-5).

ЗначениеУсловиеКонтекст
InvestorAddedИнвестор сам добавил объект в намерения (Flow A)ConfirmedByInvestor = true, ConfirmedAt = CreatedAt сразу
AgentProposedАгент предложил объект / завёл сделку по незапрошенному объекту (Flow B)ConfirmedByInvestor = false до подтверждения инвестором

Enum BookingIntentChangeKind

Enum контракта события (характер изменения); передаётся строкой. Соответствует типам ленты лида LeadEventType BookingIntentAdded / BookingIntentConfirmed / BookingIntentRemoved (lead.md).

ЗначениеУсловиеПереход намерения
AddedДобавлено новое намерение (InvestorAdded или AgentProposed)появление Active-намерения (INV-INTENT-1, INV-INTENT-3)
ConfirmedИнвестор подтвердил намерение агента (Flow B)Active → Active: ConfirmedByInvestor: false → true, ConfirmedAt = now (INV-INTENT-4)
RemovedНамерение снято/отменено (инвестором или агентом)Active → Cancelled (RemovedAt проставлен)
Без PII

Событие не содержит ФИО/телефонов: отображаемые имена/контакты сторон потребители резолвят из собственных реплик pin.identity.users.v1 по идентификаторам пользователей.

Когда публикуется

УсловиеПереход состоянияПубликация
Инвестор добавил объект в намерения (Flow A)новое Active-намерение (Source = InvestorAdded, ConfirmedByInvestor = true); Version++атомарно со сменой состояния агрегата (ChangeKind = Added)
Агент предложил объект / завёл сделку по незапрошенному объекту (Flow B)новое Active-намерение (Source = AgentProposed, ConfirmedByInvestor = false); Version++та же (ChangeKind = Added)
Инвестор подтвердил намерение агента (Flow B)ConfirmedByInvestor: false → true, ConfirmedAt = now; Version++та же (ChangeKind = Confirmed)
Намерение снято/отмененоActive → Cancelled, RemovedAt проставлен; Version++та же (ChangeKind = Removed)
Что не входит в этот контракт

Изменение намерения не меняет LeadStatus (намерения меняются только у лида Status = Active, INV-INTENT-2). Одно изменение намерения → одно событие. Повторное подтверждение уже подтверждённого намерения — no-op (INV-INTENT-4), отдельного Confirmed не публикуется. Порождение сделки из подтверждённого намерения — отдельный контракт deal-created.md; это событие описывает только жизненный цикл самого намерения.

Потребители (consumers)

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
notificationsAdded + Source = InvestorAdded → уведомить исполнителя лида («инвестор добавил объект в намерения»); Added + Source = AgentProposed → уведомить инвестора («агент предложил объект — подтвердите»); Confirmed → уведомить исполнителя («инвестор подтвердил предложенный объект», Flow B); Removed → уведомить контрагента о снятии намерения. Получатель — из проекции состава лида; инициатор (ChangedByUserId) не уведомляетсяMessageId (дедуп) + монотонный Version-guard на LeadId< 1 мин
messenger (опционально)Карточка объекта намерения в чате лида (ChatKind.Lead): Added/Confirmed — актуализировать/подсветить карточку PropertyObjectId; Removed — снять/погасить карточку; системное сообщение о смене намеренияMessageId + Version-guard на LeadId< 1 мин

Порядок событий одного лида гарантирован ключом LeadId (одна партиция). Потребитель держит Version-guard: событие с Version не больше уже применённого — пропускается (устаревшая доставка). Идентификатор IntentId делает реакцию идемпотентной по паре IntentId + ChangeKind.

Доставка, порядок и обработка сбоев

  • Гарантия публикации. Событие уходит только после фиксации изменения намерения (Version++); при откате не публикуется. Доставка — at-least-once, ключ LeadId.
  • Порядок и version-guard. События одного лида упорядочены ключом LeadId (одна партиция). Потребитель держит монотонный Version-guard: событие с Version не больше уже применённого — пропускается (устаревшая доставка). При гонке Confirmed и Removed побеждает большее Version; более раннее, доставленное позже, отбрасывается guard-ом.
  • Идемпотентность потребителя. Дедуп по MessageId + бизнес-ключ IntentId + ChangeKind (одна реакция на переход) + Version-guard; повторная доставка не шлёт второе уведомление и не дублирует карточку.
  • Retry. Транзиентный сбой потребителя → повтор обработки с backoff; событие доступно до успешной обработки.
  • DLQ. Неустранимый сбой после исчерпания попыток → dead-letter c алертом; проигрывание безопасно (реакция идемпотентна по IntentId + ChangeKind).

Версионирование и совместимость

  • ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию по конвенции суффиксом .v1.
  • Additive-only для minor: новые optional-поля с безопасным default, новые значения LeadBookingIntentSource / BookingIntentChangeKind — без bump.
  • Breaking (удаление/переименование/смена типа или смысла) — новый ContractVersion = 2 + новый топик pin.crm.lead-booking-intent-changed.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum (Source / ChangeKind) — fallback: сохранить строку как есть, warning. Неизвестный ChangeKind трактуется нейтрально (обновить карточку/проекцию по IntentId, без адресного уведомления — fail-safe), неизвестный Source — уведомление по общему шаблону без разведения Flow A/B.

Безопасность

ПолеКласс данныхПравило
LeadId, TenantId, IntentId, VersionInternalinclude
PropertyObjectId, ProjectId, DeveloperCompanyIdInternal (идентификатор, не PII)include
ChangedByUserIdInternal (идентификатор, не PII)include
Source, ChangeKind, ConfirmedByInvestorInternalinclude
ChangedAtInternalinclude
  • Событие не переносит raw PII: ФИО, телефоны, бюджет и хотелки инвестора в payload отсутствуют — только идентификаторы. Отображаемые имена сторон notifications/messenger берут из своих реплик pin.identity.users.v1; при логировании producer/consumer ФИО/телефоны не фигурируют.
  • Клиентская база агентства — внутренние данные: застройщик о намерениях лида не знает, DeveloperCompanyId в payload — лишь контекст объекта, не адресат события. Доступ к топику ограничен m2m ACL Kafka (producer crm, consumers notifications, messenger).
  • Событие НЕ содержит secrets, tokens, credentials, платёжных реквизитов, документов, вложений.

Наблюдаемость

  • Логи (без PII): факт публикации lead-booking-intent-changed с LeadId/IntentId/Version, ChangeKind, Source и ConfirmedByInvestor (идентификаторы объекта/застройщика — не PII); ФИО/телефоны/ бюджет в логах не фигурируют.
  • Метрики: число изменений намерений по топику в разрезе ChangeKind (Added/Confirmed/Removed) и Source (InvestorAdded/AgentProposed); доля подтверждений Flow B (Confirmed); сквозная задержка уведомления у notifications (< 1 мин); число срабатываний Version-guard (индикатор переупорядочения/ гонок); глубина DLQ (порог алерта > 0).
  • Доступ: топик — m2m ACL Kafka (producer crm, consumers notifications, messenger); PII в событие не входит.

Acceptance Criteria

  • Given Active-лид без активного намерения на объект, When инвестор добавляет объект в намерения (Flow A), Then Version++ и ровно одно lead-booking-intent-changed публикуется с ChangeKind = Added, Source = InvestorAdded, ConfirmedByInvestor = true; LeadStatus не меняется.
  • Given Active-лид, When агент предлагает объект / заводит сделку по незапрошенному объекту (Flow B), Then событие с ChangeKind = Added, Source = AgentProposed, ConfirmedByInvestor = false; notifications уведомляет инвестора (не инициатора-агента).
  • Given неподтверждённое намерение AgentProposed, When инвестор-участник его подтверждает, Then событие с ChangeKind = Confirmed, ConfirmedByInvestor = true; notifications уведомляет исполнителя.
  • Given уже подтверждённое намерение, When приходит повторное подтверждение, Then это no-op — второе Confirmed не публикуется.
  • Given Active-намерение, When его снимают (инвестор или агент), Then событие с ChangeKind = Removed; допускается новое активное намерение на тот же объект.
  • Given событие с Version не больше уже применённого, When потребитель обрабатывает, Then оно пропускается (устаревшая доставка).
  • Given повторная доставка одного и того же перехода (IntentId + ChangeKind уже обработаны), When потребитель обрабатывает, Then второе уведомление/карточка не создаются (идемпотентность).
  • Given неизвестный ChangeKind / Source, When потребитель обрабатывает, Then значение сохраняется строкой + warning, реакция fail-safe (актуализация по IntentId без адресного разведения сторон).
  • Given breaking-изменение payload, When новый контракт, Then pin.crm.lead-booking-intent-changed.v2 + dual-publish + sunset .v1.

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

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

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