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

Event Contract: pin.calendar.event-rescheduled (CalendarEventRescheduledEvent)

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

Назначение

Фиксирует перенос события: у события в Planned/InProgress изменены StartAt/EndAt с сохранением длительности (перенос — не переход статуса). Публичное: notifications уведомляет владельца и клиента о новом времени (NotificationKind = CalendarEventRescheduled) без синхронного запроса в calendar. Напоминания при переносе пересоздаются внутри calendar (event-reminder.md, правило 2) — это не забота консюмеров.

Metadata

ПолеЗначение
Event typepin.calendar.event-rescheduled
Topicpin.calendar.event-rescheduled.v1
Partitions / key3 (ADR-0056) / key = EventId (порядок событий одного календарного события)
ContractVersion1
Producercalendar: операция переноса владельцем (../api/agent/reschedule-event.md, только Origin = Manual); consumer booking-событий CRM (../consumers/crm-booking-events.md — перенос дедлайна при продлении брони, Origin = CrmBooking)
Статусdraft

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

События Origin = Appointment не переносятся: перенос записи инвестором — это атомарная пара «отмена старой записи + новая запись» (appointment.md, INV-APT-5), которая порождает appointment-cancelled + appointment-created и новое календарное событие, а не reschedule существующего.

Payload CalendarEventRescheduledEvent

Дом полей — entity: CalendarEventEntity; здесь перечислено только то, что реально в событии. StartAt/EndAtновые значения после переноса.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
EventIdGuidДаCalendarEventEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаCalendarEventEntity.TenantIdImmutableКомпания владельца календаря (ADR-0052).
OwnerUserIdGuidДаCalendarEventEntity.OwnerUserIdImmutableВладелец календаря.
Kindstring (enum EventKind)ДаCalendarEventEntity.KindAdditive only (новые значения)Вид события.
Originstring (enum EventOrigin)ДаCalendarEventEntity.OriginAdditive only (новые значения)Manual (перенос владельцем) / CrmBooking (продление дедлайна брони); Appointment не встречается (см. выше).
ClientUserIdGuid?НетCalendarEventEntity.ClientUserIdAdditive onlyКлиент-инвестор — получатель уведомления о новом времени; display-данные — из реплики identity.
StartAtDateTimeOffsetДаCalendarEventEntity.StartAtImmutable (значение на момент публикации)Новое начало, UTC.
EndAtDateTimeOffset?НетCalendarEventEntity.EndAtImmutable (значение на момент публикации)Новый конец, UTC; null — точечное событие.
TimeZonestringДаCalendarEventEntity.TimeZoneAdditive onlyIANA-таймзона события — для отображения локального времени в уведомлении.

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

УсловиеПереход состоянияПубликация
Владелец перенёс событие Origin = Manual (Planned/InProgress; длительность сохраняется)статус не меняется; StartAt/EndAt изменены, Pending-напоминания пересозданыатомарно с изменением агрегата
Consumer booking-событий перенёс дедлайн Origin = CrmBooking (бронь продлена — новый BookingExpiresAt)статус не меняется; StartAt = новый дедлайнатомарно с изменением агрегата

Каждый фактический перенос публикуется отдельным сообщением; при нескольких переносах подряд консюмер применяет последнее по порядку в партиции (ключ = EventId — порядок гарантирован).

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

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
notificationsУведомление о новом времени (NotificationKind = CalendarEventRescheduled): владельцу — при переносе consumer-ом (Origin = CrmBooking); клиенту ClientUserId (если задан) — при переносе владельцем. Консюмер — ../../notifications/consumers/calendar-events.mdMessageId< 5 мин

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

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

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

ПолеКласс данныхПравило
EventId, TenantId, OwnerUserId, Kind, Origin, StartAt, EndAt, TimeZoneInternalinclude
ClientUserIdInternalinclude (только идентификатор)
  • PII (ФИО/телефоны) в событии отсутствует по дизайну — display-данные консюмеры берут из своей реплики pin.identity.users.v1; Title/Description/Location в payload не входят.
  • Доступ к топику — m2m ACL Kafka (notifications).

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

  • Гарантия. At-least-once после атомарной публикации из outbox; ключ = EventId — несколько переносов одного события приходят по порядку. Дубликаты возможны — консюмер идемпотентен по MessageId.
  • Retry. Транзиентный сбой консюмера → повтор с backoff; порядок по ключу сохраняется.
  • DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток — в DLQ с алертом; остальные события не блокируются.
  • Кросс-топик порядок. С event-status-changed порядок между топиками не гарантирован: если событие уже отменено/завершено на момент обработки переноса, уведомление о новом времени не отправляется (fail-safe по бизнес-состоянию, warning); финальную картину даёт уведомление об отмене/завершении.
  • Промежуточное состояние. Новое время в календаре владельца видно сразу; уведомление клиенту — после обработки события (обычно < 5 мин).

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

  • Перенос владельцем. Дано: событие Consultation (Origin = Manual) с ClientUserId перенесено на день позже. Когда notifications обрабатывает событие. Тогда клиенту уходит уведомление CalendarEventRescheduled с новым временем в таймзоне TimeZone.
  • Продление дедлайна. Дано: бронь продлена, consumer перенёс BookingDeadline. Когда notifications обрабатывает событие. Тогда владелец получает уведомление о новом дедлайне.
  • Двойной перенос. Дано: событие перенесено дважды подряд. Когда консюмер обрабатывает оба сообщения по порядку. Тогда итоговое уведомление отражает последнее время (каждое сообщение самодостаточно).
  • Идемпотентность. Дано: сообщение доставлено дважды. Тогда дубль уведомления не создаётся.

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

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

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