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

Event Contract: pin.calendar.appointment-completed (AppointmentCompletedEvent)

Версионируемый контракт интеграционного события «Исход встречи зафиксирован» — агент отметил, что встреча состоялась (Completed) или клиент не пришёл (NoShow). Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — entity: AppointmentEntity.

Назначение

Фиксирует терминальный исход Confirmed-записи после наступления времени встречи (INV-APT-4). Публичное: crm пополняет ленту лида результатом встречи — Completed двигает работу с клиентом дальше, NoShow — сигнал о потерянном контакте (повторная связь, ре-активация лида). Каскадные эффекты (слот Archived, календарное событие Completed) — внутри calendar и в payload не входят.

Metadata

ПолеЗначение
Event typepin.calendar.appointment-completed
Topicpin.calendar.appointment-completed.v1
Partitions / key3 (ADR-0056) / key = AppointmentId (порядок событий одной записи)
ContractVersion1
Producercalendar: фиксация исхода агентом (../api/agent/complete-appointment.md, permission calendar.appointments.decide, только из Confirmed и только при now ≥ StartAt)
Статусdraft

Событие публикуется атомарно с переходом записи → Completed/→ NoShow и каскадом «слот Archived + календарное событие Completed»: пока изменение не зафиксировано, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once, transactional outbox), ключ = AppointmentId.

Payload AppointmentCompletedEvent

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

ПолеТипОбязательностьИсточник (поле агрегата)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-таймзона слота.
Outcomestring (enum AppointmentOutcome)Дапроизводное от финального статуса записиAdditive only (новые значения)Исход встречи, см. enum ниже.
CompletedAtDateTimeOffsetДаAppointmentEntity.CompletedAtImmutableМомент фиксации исхода агентом.

Enum AppointmentOutcome

ЗначениеФинальный статус записиОписание
CompletedCompletedВстреча состоялась.
NoShowNoShowКлиент не пришёл; календарное событие при этом также Completed — факт встречи в календаре закрыт, исход хранится на записи.

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

УсловиеПереход состоянияПубликация
Агент зафиксировал «встреча состоялась» (now ≥ StartAt, INV-APT-4)Confirmed → Completed (Outcome = Completed; слот Booked → Archived, событие → Completed)атомарно с переходом и каскадом
Агент зафиксировал «клиент не пришёл» (now ≥ StartAt)Confirmed → NoShow (Outcome = NoShow; каскад тот же)атомарно с переходом и каскадом

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

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
crmЗапись в ленту лида с исходом встречи: Completed — «встреча состоялась» (продолжение воронки: подборка, фиксация, сделка); NoShow — «клиент не пришёл» (сигнал повторной связи). Консюмер — ../../crm/consumers/calendar-appointments.mdMessageId (unique-ключ записи ленты) + business key AppointmentId< 1 мин
notificationsВыделенный пользовательский NotificationKind для исхода в v1 не заведён: исход фиксирует сам агент, инвестору результат не рассылается. Консюмер ../../notifications/consumers/calendar-events.md принимает событие и завершает обработку без пользовательских уведомлений (no-op) — уведомления по исходу (например, запрос обратной связи у инвестора при Outcome = Completed) добавляются additive-но без смены контрактаMessageId

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

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

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

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

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

  • Гарантия. At-least-once после атомарной публикации из outbox; ключ = AppointmentId — порядок событий одной записи в топике сохранён. Дубликаты возможны — консюмеры идемпотентны по MessageId.
  • Retry. Транзиентный сбой консюмера → повтор с backoff; партиция не продвигается дальше застрявшего оффсета, порядок по ключу сохраняется.
  • DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток — в DLQ с алертом; записи других инвесторов не блокируются.
  • Кросс-топик порядок. Исход — финальная картина записи (терминальные статусы иммутабельны, INV-APT-6): устаревшие appointment-confirmed, обработанные после исхода, консюмер игнорирует по бизнес-состоянию (no-op, warning). Если appointment-completed обработан раньше appointment-created (редкий случай отставания партиции другого топика) — get-or-create клиента/лида выполняется по данным этого события, лента дополнится задним числом без дублей.
  • Промежуточное состояние. Исход в календаре агента виден сразу; лента CRM — после обработки события (обычно < 1 мин).

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

  • Состоялась. Дано: агент зафиксировал Outcome = Completed. Когда crm обрабатывает событие. Тогда в ленте лида — «встреча состоялась» с видом и временем встречи.
  • Неявка. Дано: агент зафиксировал Outcome = NoShow. Когда crm обрабатывает событие. Тогда в ленте лида — «клиент не пришёл»; инвестору уведомление не отправляется.
  • Идемпотентность. Дано: сообщение доставлено дважды. Тогда запись ленты не дублируется (no-op).
  • Неизвестный Outcome. Дано: сообщение с новым значением Outcome. Когда консюмер обрабатывает. Тогда warning + нейтральная запись ленты «встреча завершена»; сообщение не в DLQ.
  • Несовместимый payload. Дано: сообщение без обязательного поля. Когда консюмер повторно не смог его обработать. Тогда сообщение уходит в DLQ, остальные записи обрабатываются.

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

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

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