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

UC-CAL-011. Переназначение открытых дедлайнов и встреч при смене исполнителя

Назначение

Описывает системный (событийный) сценарий: когда в сделке CRM меняется исполнитель или из сделки уходит агент (в том числе при увольнении через offboarding), открытые обязательства этого агента в календаре — дедлайны брони и рабочие встречи/задачи сделки — не должны осиротеть. Консюмер deal-событий календаря (crm-deal-events.md) переназначает владельца (OwnerUserId) таких открытых событий на нового исполнителя в пределах того же агентства и фиксирует факт штатным событием pin.calendar.event-owner-changed.v1. Календарь не читает таблицы CRM и не управляет составом сделки: интеграция только событиями; новый исполнитель определяется по реплике участников сделки. Записи инвесторов на слот (Origin = Appointment) при этом не переназначаются — они привязаны к конкретному слоту и живут по жизненному циклу записи. Связан с контекстом calendar.

Metadata

ПолеЗначение
IDUC-CAL-011
Контекстcalendar (см. docs/06-services/calendar/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольSystem (консюмер deal-событий CRM); бенефициары — новый исполнитель (получает открытые обязательства в свой календарь) и ушедший агент (его календарь освобождается от чужих обязательств)
Permissions / scopesystem-scope консюмера; просмотр итогового календаря агентами — calendar.events.view
TenantПереназначение — в пределах одного tenant агентства (AgencyCompanyId из реплики сделки); TenantId события не меняется, ADR-0052
Record-levelOwnerUserId открытых deal-привязанных событий меняется с прежнего владельца на нового ExecutorAgentUserId

Предусловие

  • Реплика «сделка → участники» наполнена deal-событиями CRM (crm-deal-events.md): для сделки известны AgencyCompanyId (tenant), множество активных агентов AgentUserIds и явный маркер текущего исполнителя ExecutorAgentUserId; сделка не терминальна.
  • У сделки есть открытые (Planned/InProgress) события в календарях её агентов: дедлайны брони (Kind = BookingDeadline, Origin = CrmBooking, UC-CAL-007) и/или рабочие встречи/задачи, созданные вручную и привязанные к сделке (Origin = Manual, DealId задан).

Триггер

ПолеЗначение
ИсточникKafka event
Триггерящий элементКонсюмер ../consumers/crm-deal-events.md (топик pin.crm.deal-members-changed.v1, key = DealId; сигнал ChangeType = ExecutorChanged или Removed)

Основной Поток

  1. В CRM меняется состав сделки: назначен новый исполнитель (ExecutorChanged) либо агент выведен из сделки (Removed, в т.ч. увольнение через agent-offboarding.md). CRM публикует pin.crm.deal-members-changed.v1 с полным активным составом (ActiveMembers), дельтой изменений (Members с ChangeType) и монотонной Version.
  2. Консюмер календаря сверяет монотонную версию с уже применённой: устаревшая/повторная доставка (Version не больше применённой) → пропуск без изменений (A3). Иначе запоминает прежние ExecutorAgentUserId и AgentUserIds, затем пересчитывает их из ActiveMembers.
  3. Консюмер вычисляет необходимость переназначения: исполнитель сменился (ExecutorAgentUserId ≠ прежнего, либо в дельте есть ExecutorChanged) или из сделки ушли агенты (departed = прежние AgentUserIds − новые AgentUserIds). Если ни того ни другого — no-op (A4).
  4. Консюмер находит открытые (Planned/InProgress) события сделки по логической связке DealId и переназначает их владельца на нового ExecutorAgentUserId (INV-CAL-8):
    • дедлайны брони (Kind = BookingDeadline, Origin = CrmBooking) — всегда следуют за исполнителем: у всех открытых дедлайнов сделки OwnerUserId ← новый исполнитель;
    • встречи и прочие рабочие события ушедших агентов (DealId = сделка, OwnerUserId ∈ departed, Origin = Manual, Kind ∈ {Meeting, Showing, Consultation, Task}) — OwnerUserId ← новый исполнитель;
    • события Origin = Appointment не трогаются (INV-CAL-3, A2); TenantId не меняется (новый исполнитель — тот же AgencyCompanyId).
  5. По каждому фактически переназначенному событию публикуется штатное pin.calendar.event-owner-changed.v1 (PreviousOwnerUserId → новый OwnerUserId). Напоминания события не пересоздаются: моменты DueAt не меняются — сместился только владелец; уведомления о напоминаниях уйдут новому владельцу.
  6. Обновление реплики участников, переназначение владельцев, публикация событий и отметка идемпотентности — в одном согласованном изменении; факт применения версии сохраняется (защита от повторов).
  7. Итог: открытые обязательства сделки видны в календаре нового исполнителя (list-events), исчезли из календаря ушедшего агента; notifications уведомляет нового владельца по event-owner-changed.

Альтернативные Потоки

A1. Новый исполнитель не распознан

  1. В ActiveMembers нет распознанного участника с ролью исполнителя (ExecutorAgentUserId = null) → переназначение пропускается (fail-safe): открытые события остаются у прежних владельцев, пишется warn-лог. В норме CRM гарантирует ровно одного исполнителя активной сделки; ситуация — сигнал рассинхрона, а не потеря обязательств.

A2. Запись инвестора на слот не переназначается

  1. Среди открытых событий сделки есть событие Origin = Appointment (порождено записью инвестора на слот конкретного агента). Оно не переназначается (INV-CAL-3): привязано к слоту и управляется жизненным циклом записи. По нему event-owner-changed не публикуется; переназначаются только дедлайны и Origin = Manual встречи/задачи.

A3. Устаревшая / повторная доставка (version-guard)

  1. deal-members-changed с Version не больше уже применённой (out-of-order или повтор Kafka) → пропуск как skipped-stale, состояние не меняется, event-owner-changed не публикуется. Переназначение идемпотентно и по цели: если OwnerUserId уже равен текущему исполнителю — по такому событию no-op.

A4. Состав изменился без смены исполнителя и без ухода агентов

  1. Изменение затронуло не-агентскую роль (например, добавлен инвестор) или сменилась роль без выхода агента: departed пусто и исполнитель тот же → переназначения нет (no-op), реплика участников всё равно актуализируется. Ack.

A5. Событие обогнало реплику сделки

  1. deal-members-changed пришёл раньше deal-created (топики взаимно не упорядочены) — реплика сделки не найдена → retriable-ожидание с backoff; после прихода deal-created повтор успешен. Окно eventual consistency (штатно < 1 мин, alert по lag).

A6. Терминальная сделка

  1. Если к моменту обработки сделка уже терминальна (deal-completed/deal-cancelled пришло раньше) — открытые дедлайны уже отменены терминальным guard-ом (UC-CAL-007, шаг 7); переназначать нечего. Пересечение обработок идемпотентно: кто успел первым, второй получает no-op.

Постусловие

  • Все открытые (Planned/InProgress) дедлайны брони сделки принадлежат текущему исполнителю (ExecutorAgentUserId); открытые рабочие встречи/задачи ушедших агентов (Origin = Manual, DealId) переназначены на текущего исполнителя.
  • События Origin = Appointment сохранили своих владельцев (INV-CAL-3).
  • По каждому переназначенному событию опубликовано pin.calendar.event-owner-changed.v1; notifications уведомляет нового владельца. TenantId событий не изменён.
  • Календарь ушедшего агента не содержит открытых обязательств этой сделки.

Возможные Ошибки

Сценарий системный (консюмер) — пользовательских HTTP-ошибок нет. Ошибки консюмера (retriable/non-retriable, DLQ) — не HTTP; политика — в ../consumers/crm-deal-events.md:

УсловиеКлассПоведение
Реплика сделки не найдена (событие обогнало deal-created, A5)retriablebackoff-ретрай до появления реплики; после исчерпания — DLQ + алерт
Отсутствуют обязательные поля / пустой ActiveMembersnon-retriableDLQ + алерт; разбор контракта с producer-ом crm
Гонка с консюмером booking-событий за одно дедлайн-событиеконкурентнаянемедленный повтор (перечитать состояние); ожидаемый исход — no-op у проигравшего

Прямая правка переназначаемого события агентом (в обход консюмера) отклоняется доменом: CAL_EVENT_MANAGED_BY_CRM_BOOKING (дедлайн) / CAL_EVENT_MANAGED_BY_APPOINTMENT (запись) — переназначение владельца выполняет только санкционированный автоматический канал (INV-CAL-8).

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
CalendarEventOwnerChangedEvent.v1pin.calendar.event-owner-changed.v1По каждому переназначенному открытому событию (дедлайн/встреча/задача) при смене исполнителя или уходе агента (шаг 5)notifications (CalendarEventOwnerChanged)

Потребляемое событие — контракт CRM: deal-members-changed.md (ChangeType, ActiveMembers, Version).

Acceptance Criteria

#КритерийПроверка
AC1Смена исполнителя (ExecutorChanged на B) переназначает OwnerUserId всех открытых дедлайнов брони сделки на B, публикует event-owner-changed по каждомуинтеграционный тест консюмера (шаг 4)
AC2Уход со-агента C (Removed) переназначает его открытые встречи сделки (Origin = Manual, DealId) на текущего исполнителя; дедлайны уже принадлежащие исполнителю не трогаютсятест ухода агента (шаг 4, departed)
AC3Событие Origin = Appointment не переназначается — остаётся у своего агента; event-owner-changed по нему не публикуется (A2, INV-CAL-3)негативный тест защиты записи
AC4Устаревшая доставка (Version ≤ применённой) → skipped-stale, OwnerUserId не меняется, событие не публикуется (A3)тест идемпотентности/out-of-order
AC5ExecutorAgentUserId не распознан → переназначение пропущено, события остаются у прежних владельцев, warn-лог (A1)негативный тест fail-safe
AC6Переназначение меняет только OwnerUserId в пределах того же TenantId; напоминания не пересоздаются (DueAt не меняется)тест инварианта переназначения
AC7Терминальная сделка: открытые дедлайны уже отменены — переназначения нет, пересечение обработок no-op (A6)тест терминала + гонки

Наблюдаемость

  • Логи (без PII). executor-changed (DealId, прежний/новый ExecutorAgentUserId, число переназначенных событий), agents-departed (DealId, число ушедших), event-owner-reassigned (eventId, dealId, previousOwnerUserId, ownerUserId); содержимое событий в логи не пишется.
  • Метрики. calendar_crm_deal_sync_events_reassigned_total; lag консюмера deal-событий (< 1 мин штатно, alert > 5 мин); DLQ консюмера (alert при непустом).

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

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

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