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

Event Contract: pin.crm.booking-created (BookingCreatedEvent)

Версионируемый контракт интеграционного события «Бронирование создано (Резерв)». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — entity: BookingEntity; условия программы — booking-program.md.

Назначение

Фиксирует старт процесса бронирования: у сделки создан агрегат BookingEntity в статусе Reserved, объект временно закрепляется за клиентом на ProgramTerms.ReserveDurationMinutes (по умолчанию 1 час). Публичное: даёт downstream-контекстам факт и дедлайн резерва без синхронного запроса в crmchessboard переводит объект недвижимости в статус «Резерв» и ставит таймер (границы контекстов: CRM чужие таблицы не пишет, service-boundaries), messenger рисует карточку брони в чате сделки (ADR-0055).

Metadata

ПолеЗначение
Event typepin.crm.booking-created
Topicpin.crm.booking-created.v1
Partitions / key3 (ADR-0056, 1–10k активных броней) / key = AggregateId = BookingId (порядок событий одной брони)
ContractVersion1
Producercrm: операция create-booking (старт брони; InitiatedBy = инвестор с витрины / агент из сделки / Менеджер ПИН)
Статусdraft

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

Payload BookingCreatedEvent

Дом полей — entity: BookingEntity (раздел «Доменные и интеграционные события»); здесь перечислено только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
BookingIdGuidДаBookingEntity.IdImmutableКлюч агрегата (= Kafka key, = AggregateId).
TenantIdGuidДаBookingEntity.TenantIdImmutableЗастройщик объекта (DeveloperCompanyId, ADR-0052).
DealIdGuidДаBookingEntity.DealIdImmutableСделка-владелец процесса (deal.md).
PropertyObjectIdGuidДаBookingEntity.PropertyObjectIdImmutableОбъект в шахматке (chessboard); по нему выставляется статус «Резерв».
InitiatedBystring (enum BookingInitiator)ДаBookingEntity.InitiatedByAdditive only (новые значения)Точка входа: Investor / Agent / PinManager.
ReserveExpiresAtDateTimeOffsetДаBookingEntity.ReserveExpiresAtImmutableДедлайн резерва (CreatedAt + ProgramTerms.ReserveDurationMinutes); chessboard ставит таймер по нему.
ProgramTermsBookingProgramTermsSnapshotДаBookingEntity.ProgramTermsAdditive onlyИммутабельный снимок условий программы бронирования на момент создания.
CreatedAtDateTimeOffsetДаBookingEntity.CreatedAtImmutableМомент старта резерва (= создание заявки на бронирование).

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

Snapshot условий; дом полей — booking.md (VO BookingProgramTerms) → booking-program.md. RefundPolicy в событие не входит (нужен только UI CRM, читается синхронно).

ПолеТипОбязательностьОграниченияОписание
NamestringДа≤ 128Название программы («Бесплатная бронь 4 часа», «Платная бронь 10 дней»).
IsPaidboolДаПлатная ли бронь (US-INV-06/21).
PriceAmountdecimal?Нет (обязателен при IsPaid)numeric(18,2) > 0Стоимость бронирования.
PriceCurrencyIdshortДассылка на справочник валют handbook; RUB-only (ADR-0048)Валюта стоимости бронирования.
PriceCurrencyCurrencySnapshotДаполя — snapshots.md, не переописываютсяDisplay-снимок валюты на момент публикации (код/имя/знак).
ReserveDurationMinutesintДа> 0 (default 60)Длительность фазы «Резерв» — по ней вычислен ReserveExpiresAt.
BookingDurationHoursintДа> 0Длительность будущей фазы «Бронь» (после подтверждения).
Без персональных данных

Payload НЕ содержит ФИО/телефон клиента, RequisiteSetId, суммы оплаты по чеку — только факт и условия резерва. Персональные данные остаются в identity/lead.md; реквизиты — в profile.

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

УсловиеПереход состоянияПубликация
Операция create-booking: сделка Status ∈ {Application, Booking} (INV-BKG-2), объект недвижимости свободен (INV-BKG-001), выбрана программа[*] → Reserved (агрегат); Deal.OnBookingCreated() (Application → Booking)атомарно со сменой состояния агрегата и сделки

Последующие переходы брони публикуются отдельными событиями того же семейства: подтверждение — booking-confirmed.md, оплата — booking-paid.md, истечение — booking-expired.md, отмена — booking-cancelled.md.

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

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
chessboardОбъект недвижимости PropertyObjectId → статус «Резерв» + таймер до ReserveExpiresAt (проекция статуса объекта; CRM чужие таблицы не пишет)MessageId + per-property-object guard< 1 мин (визуальная блокировка объекта недвижимости)
messengerКарточка «бронирование создано (Резерв до ReserveExpiresAt)» в чат сделки DealId (ADR-0055)MessageId< 5 мин

Оба консюмера идемпотентны по MessageId: повторная доставка не создаёт дублей проекции/карточки.

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

  • ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию по конвенции суффиксом .v1.
  • Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
  • Breaking (удаление/переименование/смена типа или смысла) — ContractVersion = 2 + новый топик pin.crm.booking-created.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum InitiatedBy — fallback: сохранить строку как есть, warning; проекция статуса объекта недвижимости от значения InitiatedBy не зависит (fail safe).

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

ПолеКласс данныхПравило
BookingId, TenantId, DealId, PropertyObjectId, InitiatedBy, ReserveExpiresAt, CreatedAtInternalinclude
ProgramTerms.* (Name/IsPaid/PriceAmount/PriceCurrencyId/PriceCurrency/ReserveDurationMinutes/BookingDurationHours)Internalinclude (бизнес-условия программы, не платёжные реквизиты)
  • PII (ФИО/телефоны) в событии отсутствует по дизайну — она не покидает identity/lead; консюмеры, которым нужно отобразить клиента, берут его из своей реплики pin.identity.users.v1. Если ФИО/телефон всё же попадёт в логи producer/consumer при отладке — маскирование обязательно (+7***1234, инициалы).
  • Доступ к топику — m2m ACL Kafka (chessboard/messenger/notifications).
  • Событие НЕ содержит secrets, tokens, raw credentials, документов, платёжных данных по чеку.

Доставка, retry/DLQ и восстановление

  • Гарантия. At-least-once после атомарной публикации из outbox; ключ = BookingId — порядок событий одной брони в топике сохранён. Дубликаты возможны (переотправка) — консюмеры обязаны быть идемпотентны.
  • Идемпотентность консюмеров. chessboard — по MessageId + per-property-object guard (повторная доставка не переставляет статус); messenger — по MessageId (карточка брони в чат не дублируется).
  • Retry. Транзиентный сбой консюмера (недоступность своей БД/зависимости) → повтор с backoff, порядок по ключу сохраняется (партиция не продвигается дальше застрявшего оффсета одной брони). Бизнес-«уже применено» (объект недвижимости уже «Резерв» для этой брони) — не ошибка, а no-op.
  • DLQ. Неразбираемое/несовместимое сообщение (ошибка десериализации, отсутствуют обязательные поля) после конечного числа попыток уходит в DLQ с алертом; лайв-сообщения других броней не блокируются.
Кросс-топик порядок не гарантирован

Между booking-created и более поздними booking-confirmed/-expired/-cancelled одной брони межтопиковый порядок не гарантирован; chessboard применяет статус по бизнес-состоянию: если для этого BookingId уже обработана более поздняя фаза (объект недвижимости освобождён/«Бронь»), устаревший Резерв из booking-created не применяется (fail-safe, warning в лог).

Промежуточное состояние

До обработки события витрина/шахматка показывают объект недвижимости «Свободен» (< 1 мин); двойного бронирования это не создаёт — единственность держит источник crm (INV-BKG-001), а не проекция шахматки.

Acceptance-критерии (контракт потребления)

  • chessboard — резерв. Дано: опубликован BookingCreatedEvent, объект недвижимости свободен. Когда chessboard обрабатывает событие. Тогда объект недвижимости → «Резерв» + таймер до ReserveExpiresAt.
  • chessboard — идемпотентность. Дано: событие доставлено дважды. Когда обрабатывается повторно. Тогда статус объекта недвижимости не меняется повторно (no-op).
  • chessboard — устаревшее событие. Дано: для этой брони уже обработана отмена/истечение. Когда приходит запоздавший booking-created. Тогда «Резерв» не ставится (fail-safe).
  • messenger — карточка. Дано: событие опубликовано. Когда messenger обрабатывает. Тогда в чате сделки DealId появляется карточка «бронирование создано (Резерв до ReserveExpiresAt)».
  • Несовместимый payload. Дано: сообщение без обязательного поля. Когда консюмер повторно не смог его обработать. Тогда сообщение уходит в DLQ, остальные брони обрабатываются.

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

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

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