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

Event Contract: pin.calendar.appointment-confirmed (AppointmentConfirmedEvent)

Версионируемый контракт интеграционного события «Запись подтверждена» — встреча инвестора и агента состоится. Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — entity: AppointmentEntity.

Назначение

Фиксирует подтверждение записи: агент принял Pending-запись (confirm-appointment) либо запись создана сразу подтверждённой (слот с AutoConfirm = true). Публичное: crm пополняет ленту лида фактом подтверждённой встречи, notifications уведомляет инвестора «встреча подтверждена» — без синхронного запроса в calendar.

Metadata

ПолеЗначение
Event typepin.calendar.appointment-confirmed
Topicpin.calendar.appointment-confirmed.v1
Partitions / key3 (ADR-0056) / key = AppointmentId (порядок событий одной записи)
ContractVersion1
Producercalendar: подтверждение агентом (../api/agent/confirm-appointment.md, permission calendar.appointments.decide, до PendingExpiresAt); auto-confirm при бронировании (../api/public/book-slot.md, слот с AutoConfirm = true)
Статусdraft

Событие публикуется атомарно с переходом записи → Confirmed (для auto-confirm — атомарно со всей связкой создания, вместе с appointment-created): пока изменение не зафиксировано, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once, transactional outbox), ключ = AppointmentId.

Payload AppointmentConfirmedEvent

Дом полей — entity: AppointmentEntity (раздел «Доменные и интеграционные события»); здесь перечислено только то, что реально в событии. Контракт самодостаточен: несёт участников и время встречи — консюмеру не нужно состояние из ранее обработанного appointment-created.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
AppointmentIdGuidДаAppointmentEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаAppointmentEntity.TenantIdImmutableКомпания агента (ADR-0052).
AgentUserIdGuidДаAppointmentEntity.AgentUserIdImmutableАгент/консультант.
InvestorUserIdGuidДаAppointmentEntity.InvestorUserIdImmutableИнвестор — получатель уведомления о подтверждении.
Kindstring (enum EventKind)ДаAppointmentEntity.KindAdditive only (новые значения)Вид встречи: Showing / Consultation.
StartAtDateTimeOffsetДаAppointmentEntity.StartAtImmutable (снапшот слота)Начало встречи, UTC.
TimeZonestringДаAppointmentEntity.TimeZoneAdditive onlyIANA-таймзона слота — для отображения локального времени в уведомлении.
ConfirmedAtDateTimeOffsetДаAppointmentEntity.ConfirmedAtImmutableМомент подтверждения (для auto-confirm совпадает с созданием записи).
AutoConfirmedboolДапроизводное: подтверждение выполнено при бронировании (AutoConfirm слота), а не решением агентаImmutabletrue — auto-confirm; false — агент подтвердил вручную.

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

УсловиеПереход состоянияПубликация
Агент подтвердил Pending-запись до PendingExpiresAtPending → Confirmedатомарно с переходом записи (AutoConfirmed = false)
Бронирование слота с AutoConfirm = true[*] → Confirmedатомарно со всей связкой создания; параллельно публикуется appointment-created (AutoConfirmed = true)

Слот и календарное событие при подтверждении не меняются (остаются Booked / Planned — таблица каскадов в appointment.md).

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

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
crmЗапись в ленту лида «встреча подтверждена» (вид, время, AutoConfirmed); get-or-create клиента/лида — как в appointment-created (контракт самодостаточен на случай обработки раньше appointment-created). Консюмер — ../../crm/consumers/calendar-appointments.mdMessageId (unique-ключ записи ленты) + business key AppointmentId< 1 мин
notificationsNotificationKind = AppointmentConfirmed: инвестору — «встреча подтверждена» с локальным временем (StartAt + TimeZone); агенту уведомление не строится (ручное подтверждение — его собственное действие, при auto-confirm агента уведомил appointment-created). Консюмер — ../../notifications/consumers/calendar-events.mdMessageId< 5 мин

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

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

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

ПолеКласс данныхПравило
AppointmentId, TenantId, AgentUserId, InvestorUserId, Kind, StartAt, TimeZone, ConfirmedAt, AutoConfirmedInternalinclude (только идентификаторы, время и флаг)
  • PII (ФИО/телефоны) в событии отсутствует по дизайну: display-данные консюмеры берут из своей реплики pin.identity.users.v1; свободнотекстовых полей в контракте нет.
  • Доступ к топику — m2m ACL Kafka (crm, notifications).

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

  • Гарантия. At-least-once после атомарной публикации из outbox; ключ = AppointmentId — порядок событий одной записи в топике сохранён. Дубликаты возможны — консюмеры идемпотентны по MessageId.
  • Retry. Транзиентный сбой консюмера → повтор с backoff; партиция не продвигается дальше застрявшего оффсета, порядок по ключу сохраняется.
  • DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток — в DLQ с алертом; записи других инвесторов не блокируются.
  • Кросс-топик порядок. С appointment-created (auto-confirm) и appointment-cancelled межтопиковый порядок не гарантирован: контракт самодостаточен, а устаревшее подтверждение поверх уже обработанной отмены/исхода консюмер применяет по бизнес-состоянию (no-op, warning) — финальную картину дают appointment-cancelled / appointment-completed.
  • Промежуточное состояние. Подтверждение в календаре агента и в «моих записях» инвестора видно сразу; уведомление и лента CRM — после обработки событий (обычно < 1–5 мин).

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

  • Ручное подтверждение. Дано: Pending-запись подтверждена агентом. Когда notifications обрабатывает событие. Тогда инвестору уходит AppointmentConfirmed с локальным временем встречи; AutoConfirmed = false.
  • Auto-confirm. Дано: слот с AutoConfirm = true забронирован. Когда опубликованы оба события создания и подтверждения. Тогда AutoConfirmed = true, ConfirmedAt совпадает с созданием записи; уведомление агенту строится только по appointment-created (дублей нет).
  • crm — лента. Дано: событие обработано crm. Тогда в ленте лида — запись «встреча подтверждена»; повторная доставка дубля не создаёт.
  • Порядок топиков. Дано: appointment-confirmed обработан раньше appointment-created (auto-confirm). Тогда get-or-create клиента/лида выполняется по данным этого события, обработка appointment-created следом дополняет ленту без дублей.
  • Несовместимый payload. Дано: сообщение без обязательного поля. Когда консюмер повторно не смог его обработать. Тогда сообщение уходит в DLQ, остальные записи обрабатываются.

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

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

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