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 type | pin.crm.lead-booking-intent-changed |
| Topic | pin.crm.lead-booking-intent-changed.v1 |
| Partitions / key | 3 (crm — низкообъёмный, 1–10k активных лидов, ADR-0056) / key = LeadId (порядок изменений намерений одного лида на одной партиции) |
| ContractVersion | 1 |
| Producer | crm: добавление намерения (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 | Описание |
|---|---|---|---|---|---|
LeadId | Guid | Да | LeadBookingIntentEntity.LeadId (= LeadEntity.Id) | Immutable | Ключ агрегата (= Kafka key). Родительский лид намерения; чат лида в messenger (ChatKind.Lead). |
TenantId | Guid | Да | LeadBookingIntentEntity.TenantId | Immutable | AgencyCompanyId агентства-владельца лида (ADR-0052, ADR-0071). |
IntentId | Guid | Да | LeadBookingIntentEntity.Id | Immutable | Идентификатор намерения — бизнес-ключ идемпотентности у потребителя (не более одной реакции на пару IntentId + ChangeKind). |
PropertyObjectId | Guid | Да | LeadBookingIntentEntity.PropertyObjectId | Immutable | Объект недвижимости намерения (логическая ссылка на chessboard). |
ProjectId | Guid | Да | LeadBookingIntentEntity.ProjectId | Immutable | Проект застройщика объекта (контекст; логическая ссылка на chessboard). |
DeveloperCompanyId | Guid | Да | LeadBookingIntentEntity.DeveloperCompanyId | Immutable | Застройщик — владелец объекта; пара (инвестор × застройщик) определяет требуемую фиксацию для гейта сделки. |
Source | string (enum LeadBookingIntentSource) | Да | LeadBookingIntentEntity.Source | Additive only | Кто инициировал намерение: InvestorAdded (Flow A) / AgentProposed (Flow B). Иммутабельно (модель ниже). |
ChangeKind | string (enum BookingIntentChangeKind) | Да | характер операции | Additive only | Что произошло с намерением: Added / Confirmed / Removed (модель ниже). |
ConfirmedByInvestor | bool | Да | LeadBookingIntentEntity.ConfirmedByInvestor | Additive only | Подтверждено инвестором. InvestorAdded → true сразу; AgentProposed → false до подтверждения (Flow B). |
ChangedByUserId | Guid? | Нет | актор операции (AddedByUserId / подтвердивший инвестор / RemovedByUserId) | Additive only | Кто изменил намерение; null — система (консюмер заявки / backfill). Разводит инициатора и контрагента при адресации уведомления. |
ChangedAt | DateTimeOffset | Да | момент мутации (CreatedAt / ConfirmedAt / RemovedAt) | Additive only | Время изменения намерения. |
Version | long | Да | LeadEntity.Version | Immutable 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 проставлен) |
Событие не содержит ФИО/телефонов: отображаемые имена/контакты сторон потребители резолвят из
собственных реплик 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) |
|---|---|---|---|
notifications | Added + 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, Version | Internal | include |
PropertyObjectId, ProjectId, DeveloperCompanyId | Internal (идентификатор, не PII) | include |
ChangedByUserId | Internal (идентификатор, не PII) | include |
Source, ChangeKind, ConfirmedByInvestor | Internal | include |
ChangedAt | Internal | include |
- Событие не переносит raw PII: ФИО, телефоны, бюджет и хотелки инвестора в payload отсутствуют — только
идентификаторы. Отображаемые имена сторон
notifications/messengerберут из своих репликpin.identity.users.v1; при логировании producer/consumer ФИО/телефоны не фигурируют. - Клиентская база агентства — внутренние данные: застройщик о намерениях лида не знает,
DeveloperCompanyIdв payload — лишь контекст объекта, не адресат события. Доступ к топику ограничен m2m ACL Kafka (producercrm, consumersnotifications,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, consumersnotifications,messenger); PII в событие не входит.
Acceptance Criteria
- Given
Active-лид без активного намерения на объект, When инвестор добавляет объект в намерения (Flow A), ThenVersion++и ровно одно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.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (2): UC-CRM-016. Инвестор добавляет намерения бронирования (много объектов), UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика
- registries (1): Реестр событий PIN
Связанные документы
- Сущность (дом полей
LeadBookingIntentEntity+ enumLeadBookingIntentSource/LeadBookingIntentStatus, инвариантыINV-INTENT-*): lead.md. - Соседние события лида: lead-created.md, lead-participants-changed.md, lead-expired.md.
- Порождение сделки из подтверждённого намерения: deal.md (гейт
Draft → Application), deal-created.md. - Потребители:
notifications— crm-events.md; чат лидаmessenger(ChatKind.Lead) — lead-participants.md, ADR-0055. - Реплика имён сторон у потребителей: user-upserted.md.
- Use-cases намерений: UC-CRM-016-investor-adds-booking-intents.md, UC-CRM-002-agent-creates-deal-from-lead.md.
- ADR: ADR-0052 (
TenantId = AgencyCompanyId), ADR-0056 (партиции/простота), ADR-0071 (владение процессами компанией, состав участников).