Event Contract: pin.crm.booking-cancelled (BookingCancelledEvent)
Версионируемый контракт интеграционного события «бронирование отменено». Consumer-документы ссылаются сюда,
не переописывая payload. Дом полей агрегата — entity: BookingEntity.
Назначение
Фиксирует ручную отмену процесса брони до его истечения: инвестор передумал, агент/менеджер меняет объект недвижимости,
сделка отклонена. Переход агрегата BookingEntity из активного статуса (Reserved / AwaitingPayment /
Booked) в терминальный Cancelled.
Событие публичное: даёт chessboard сигнал освободить объект недвижимости (Резерв/Бронь → Свободен) без синхронного
запроса в crm — сменой статуса объекта в шахматке владеет и выполняет chessboard как консюмер booking-событий
(правила границ, service-boundaries; решение 5 контекста).
Отличается от booking-expired: то — истечение таймера проверкой, это — явная отмена актором.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.crm.booking-cancelled |
| Topic | pin.crm.booking-cancelled.v1 |
| Partitions / key | 3 (низкий объём booking-событий, ADR-0056) / key = BookingId (= AggregateId) |
| ContractVersion | 1 |
| Producer | crm: отмена брони (актор инвестор/агент/менеджер застройщика/Менеджер ПИН) и каскад отмены сделки (deal-cancelled) |
| Статус | draft |
Событие публикуется атомарно с переходом агрегата: пока отмена не зафиксирована, событие не отправляется;
после фиксации — доставляется гарантированно (at-least-once), ключ = BookingId.
Payload BookingCancelledEvent
Дом полей — entity: BookingEntity (раздел «Доменные и интеграционные события»);
здесь перечислено только то, что реально в событии. Snapshot-полей условий программы событие не несёт
(отмена не зависит от условий).
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
BookingId | Guid | Да | BookingEntity.Id | Immutable | Ключ агрегата (= Kafka key, AggregateId). |
DealId | Guid | Да | BookingEntity.DealId | Immutable | Сделка-владелец процесса (deal.md). |
TenantId | Guid | Да | BookingEntity.TenantId | Immutable | Tenant boundary — застройщик объекта (DeveloperCompanyId). |
PropertyObjectId | Guid | Да | BookingEntity.PropertyObjectId | Immutable | Объект в chessboard, который освобождается (→ Свободен). |
CancelledByUserId | Guid | Да | BookingEntity.UpdatedByUserId (актор отмены) | Immutable | Кто отменил (инвестор/агент/менеджер застройщика/Менеджер ПИН). Отмена всегда инициируется пользователем — не проверкой по таймеру. |
CancelledAt | DateTimeOffset | Да | BookingEntity.CancelledAt | Immutable | Момент отмены (иммутабельный факт фазы). |
CancelReason | string | Да | BookingEntity.CancelReason | Additive only | Причина отмены (обязательна при Status = Cancelled, ≤ 1000). Свободный текст — см. правило безопасности. |
Событие НЕ несёт RequisiteSetId, PaymentAmount, документов брони, ФИО/телефона клиента (эти данные
живут в lead.md / identity / profile, консюмерам отмены не нужны).
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
| Явная отмена активной брони актором (INV-BKG-8: терминальные статусы иммутабельны) | Reserved / AwaitingPayment / Booked → Cancelled | атомарно со сменой состояния агрегата |
| Каскад при отмене сделки: отмена сделки отменяет активную бронь тем же изменением | тот же переход → Cancelled | та же |
Истечение таймера (Reserved/Booked → ReserveExpired/BookingExpired проверкой) публикуется отдельным
событием booking-expired, а не этим — оба, попадая на консюмер chessboard, освобождают объект недвижимости.
Потребители (consumers)
Сверено с booking.md («Доменные и интеграционные события») и README («Events И Consumers»).
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
chessboard | Проекция статуса объекта недвижимости PropertyObjectId: Резерв/Бронь → Свободен | MessageId + per-property-object упорядочение (key BookingId, guard по статусу объекта недвижимости) | < 1 мин (актуальность шахматки) |
notifications | Уведомление участникам сделки об отмене брони | MessageId | < 5 мин |
chessboard идемпотентен и fail-safe: если объект недвижимости уже Свободен/Продан (гонка с booking-expired или
завершением сделки) — переход не применяется (no-op), warning в лог.
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию по конвенции — суффикс.v1.- Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
- Breaking (удаление/переименование/смена типа/смысла) — новый
ContractVersion = 2+ новый топикpin.crm.booking-cancelled.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Порядок per-booking гарантирован key-ом
BookingId:booking-created.v1→booking-cancelled.v1одного агрегата упорядочены. Кросс-топик порядок (cancelledvsexpiredодного объекта недвижимости) — не гарантирован; консюмерchessboardразрешает конфликт по идемпотентности (целевой статусСвободендля обоих).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
BookingId, DealId, TenantId, PropertyObjectId, CancelledByUserId | Internal | include |
CancelledAt | Internal | include |
CancelReason | Internal (свободный текст) | include; в логах producer/consumer — не выводить полностью (bounded ≤ 1000, потенциально содержит имена/детали → маскирование в логах) |
Событие не содержит ФИО, телефонов, платёжных реквизитов, документов, secrets, tokens — PII клиента
(ФИО/телефон) в этом контракте отсутствует, поэтому маскирование payload не требуется; телефоны/ФИО живут в
identity/lead.md и в booking-событиях не публикуются. Единственное правило — не логировать
CancelReason целиком (free-text). Доступ к топику — m2m ACL Kafka (chessboard, notifications).
Доставка, retry/DLQ и восстановление
- Гарантия. At-least-once после атомарной публикации; ключ =
BookingId— per-booking порядок сохранён. Дубликаты возможны — консюмеры идемпотентны. - Идемпотентность.
chessboard—MessageId+ guard по статусу объекта недвижимости (освобождение уже свободного/проданного = no-op);notifications— поMessageId. - Retry. Транзиентный сбой консюмера → повтор с backoff без продвижения оффсета этой брони; «объект недвижимости уже Свободен» — no-op, не ошибка.
- DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток → DLQ + алерт; другие брони не блокируются.
- Кросс-топик порядок с
booking-expired. Отмена и истечение одного объекта недвижимости целятся в один статус «Свободен»; порядок между топиками не гарантирован, но результат один —chessboardидемпотентен и fail-safe (повторное освобождение = no-op). Если раньше уже обработано завершение сделки (объект недвижимости «Продан») — отмена брони не возвращает объект недвижимости в «Свободен» (guard по статусу). - Каскад отмены сделки. Когда бронь отменяется каскадом отмены сделки,
booking-cancelledиdeal-cancelled— отдельные события;chessboardосвобождает объект недвижимости по любому из них (идемпотентно), не завися от порядка их доставки. - Промежуточное состояние. В карточке сделки отмена видна сразу (данные
crm); освобождение объекта недвижимости в шахматке и уведомление участникам — с задержкой (< 1–5 мин).
Acceptance-критерии (контракт потребления)
- chessboard — освобождение. Дано: опубликован
BookingCancelledEvent, объект недвижимости в «Резерв»/ «Бронь». Когдаchessboardобрабатывает. Тогда объект недвижимости → «Свободен». - Идемпотентность / no-op. Дано: объект недвижимости уже «Свободен»/«Продан» (гонка с
-expired/ завершением сделки). Когда приходит отмена. Тогда переход не применяется (no-op, warning в лог). - notifications. Дано: событие опубликовано. Когда
notificationsобрабатывает. Тогда участники сделки получают уведомление об отмене брони. - Каскад отмены сделки. Дано: сделка отменена, активная бронь была. Когда доставлены
booking-cancelledиdeal-cancelledв любом порядке. Тогда объект недвижимости освобождён ровно один раз (идемпотентно). - Несовместимый payload. Дано: сообщение без обязательного поля. Когда повтор не помог. Тогда DLQ + алерт.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): POST /api/agent/crm/bookings/{bookingId}/cancel — Отменить бронь, PUT /api/agent/crm/deals/{dealId}/property-object — Сменить объект сделки
- Use Cases (3): UC-BKG-002. Бронирование Резерв → Бронь, UC-BKG-003. Истечение резерва/брони и продление, UC-CAL-007. Дедлайны брони CRM в календаре агента
- События (5): Event Contract: pin.crm.booking-confirmed (BookingConfirmedEvent), Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.booking-expired (BookingExpiredEvent), Event Contract: pin.crm.booking-paid (BookingPaidEvent), Event Contract: pin.crm.deal-cancelled (DealCancelledEvent)
- Консюмеры (1): Consumer: Calendar CRM Booking Sync (calendar-crm-booking-events)
- Саги (1): Saga «Booking» (Резерв → Бронь → передача в продажу)
- index.md (1): Calendar Service
- registries (1): Реестр событий PIN
Связанные документы
- Сущность (дом полей): booking.md; родительский процесс: deal.md.
- Соседние контракты booking-топиков: booking-created.md, booking-confirmed.md, booking-paid.md, booking-expired.md; связанное каскадом: deal-cancelled.md.
- Источник (операция): команда отмены брони
crm(INV-BKG-8). - Консюмер статуса объекта недвижимости —
docs/06-services/chessboard/(будущий путь по конвенции).