UC-CAL-011. Переназначение открытых дедлайнов и встреч при смене исполнителя
Назначение
Описывает системный (событийный) сценарий: когда в сделке CRM меняется исполнитель или из сделки уходит агент
(в том числе при увольнении через offboarding), открытые обязательства этого агента в календаре — дедлайны
брони и рабочие встречи/задачи сделки — не должны осиротеть. Консюмер deal-событий календаря
(crm-deal-events.md) переназначает владельца (OwnerUserId) таких открытых
событий на нового исполнителя в пределах того же агентства и фиксирует факт штатным событием
pin.calendar.event-owner-changed.v1. Календарь не читает таблицы CRM и не управляет составом сделки:
интеграция только событиями; новый исполнитель определяется по реплике участников сделки. Записи инвесторов
на слот (Origin = Appointment) при этом не переназначаются — они привязаны к конкретному слоту и живут
по жизненному циклу записи. Связан с контекстом calendar.
| Поле | Значение |
|---|
| ID | UC-CAL-011 |
| Контекст | calendar (см. docs/06-services/calendar/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|
| Роль | System (консюмер deal-событий CRM); бенефициары — новый исполнитель (получает открытые обязательства в свой календарь) и ушедший агент (его календарь освобождается от чужих обязательств) |
| Permissions / scope | system-scope консюмера; просмотр итогового календаря агентами — calendar.events.view |
| Tenant | Переназначение — в пределах одного tenant агентства (AgencyCompanyId из реплики сделки); TenantId события не меняется, ADR-0052 |
| Record-level | OwnerUserId открытых 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) |
Основной Поток
- В CRM меняется состав сделки: назначен новый исполнитель (
ExecutorChanged) либо агент выведен из сделки
(Removed, в т.ч. увольнение через agent-offboarding.md). CRM
публикует pin.crm.deal-members-changed.v1 с полным активным составом (ActiveMembers), дельтой изменений
(Members с ChangeType) и монотонной Version.
- Консюмер календаря сверяет монотонную версию с уже применённой: устаревшая/повторная доставка (
Version
не больше применённой) → пропуск без изменений (A3). Иначе запоминает прежние ExecutorAgentUserId и
AgentUserIds, затем пересчитывает их из ActiveMembers.
- Консюмер вычисляет необходимость переназначения: исполнитель сменился (
ExecutorAgentUserId ≠ прежнего,
либо в дельте есть ExecutorChanged) или из сделки ушли агенты (departed = прежние AgentUserIds − новые AgentUserIds). Если ни того ни другого — no-op (A4).
- Консюмер находит открытые (
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).
- По каждому фактически переназначенному событию публикуется штатное
pin.calendar.event-owner-changed.v1
(PreviousOwnerUserId → новый OwnerUserId). Напоминания события не пересоздаются: моменты DueAt не
меняются — сместился только владелец; уведомления о напоминаниях уйдут новому владельцу.
- Обновление реплики участников, переназначение владельцев, публикация событий и отметка идемпотентности —
в одном согласованном изменении; факт применения версии сохраняется (защита от повторов).
- Итог: открытые обязательства сделки видны в календаре нового исполнителя (list-events),
исчезли из календаря ушедшего агента;
notifications уведомляет нового владельца по
event-owner-changed.
Альтернативные Потоки
A1. Новый исполнитель не распознан
- В
ActiveMembers нет распознанного участника с ролью исполнителя (ExecutorAgentUserId = null) →
переназначение пропускается (fail-safe): открытые события остаются у прежних владельцев, пишется
warn-лог. В норме CRM гарантирует ровно одного исполнителя активной сделки; ситуация — сигнал рассинхрона,
а не потеря обязательств.
A2. Запись инвестора на слот не переназначается
- Среди открытых событий сделки есть событие
Origin = Appointment (порождено записью инвестора на слот
конкретного агента). Оно не переназначается (INV-CAL-3): привязано к слоту и управляется жизненным
циклом записи. По нему event-owner-changed не публикуется; переназначаются только дедлайны и
Origin = Manual встречи/задачи.
A3. Устаревшая / повторная доставка (version-guard)
deal-members-changed с Version не больше уже применённой (out-of-order или повтор Kafka) → пропуск как
skipped-stale, состояние не меняется, event-owner-changed не публикуется. Переназначение идемпотентно и
по цели: если OwnerUserId уже равен текущему исполнителю — по такому событию no-op.
A4. Состав изменился без смены исполнителя и без ухода агентов
- Изменение затронуло не-агентскую роль (например, добавлен инвестор) или сменилась роль без выхода агента:
departed пусто и исполнитель тот же → переназначения нет (no-op), реплика участников всё равно
актуализируется. Ack.
A5. Событие обогнало реплику сделки
deal-members-changed пришёл раньше deal-created (топики взаимно не упорядочены) — реплика сделки не
найдена → retriable-ожидание с backoff; после прихода deal-created повтор успешен. Окно eventual
consistency (штатно < 1 мин, alert по lag).
A6. Терминальная сделка
- Если к моменту обработки сделка уже терминальна (
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) | retriable | backoff-ретрай до появления реплики; после исчерпания — DLQ + алерт |
Отсутствуют обязательные поля / пустой ActiveMembers | non-retriable | DLQ + алерт; разбор контракта с producer-ом crm |
| Гонка с консюмером booking-событий за одно дедлайн-событие | конкурентная | немедленный повтор (перечитать состояние); ожидаемый исход — no-op у проигравшего |
Прямая правка переназначаемого события агентом (в обход консюмера) отклоняется доменом:
CAL_EVENT_MANAGED_BY_CRM_BOOKING (дедлайн) / CAL_EVENT_MANAGED_BY_APPOINTMENT (запись) — переназначение
владельца выполняет только санкционированный автоматический канал (INV-CAL-8).
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|
CalendarEventOwnerChangedEvent.v1 | pin.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 |
| AC5 | ExecutorAgentUserId не распознан → переназначение пропущено, события остаются у прежних владельцев, 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 при непустом).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные Документы
- Consumers (дом алгоритма, шаг 4a): crm-deal-events.md (реплика участников,
переназначение владельца, терминальный guard); дедлайны брони — crm-booking-events.md.
- Domain: calendar-event.md (INV-CAL-3/4/8,
ix_calendar_events_deal_id,
event-owner-changed), event-reminder.md.
- CRM-сторона: deal-members-changed.md (
ChangeType, ActiveMembers),
deal.md, agent-offboarding.md
(увольнение агента).
- Событие календаря: event-owner-changed.md.
- Связанные UC: UC-CAL-007 (дедлайны брони, их закрытие на
терминале), UC-CAL-001 (рабочие события агента),
UC-CAL-008 (напоминания).
- ADR: 0064 контекст calendar (событийная
интеграция с CRM), 0052 tenancy,
0056 simplicity-first.