UC-OVR-011. Самозапись инвестора: от слота до исхода встречи и лида в воронке
Назначение
Сквозной сценарий самозаписи: инвестор (или аноним, регистрируемый по телефону на лету) выбирает на витрине
свободный слот агента и бронирует показ/консультацию — в calendar возникает запись (appointment), а её
жизненный цикл (подтверждение агентом / перенос / отмена / просрочка / исход «состоялась» или «не пришёл»)
событийно проецируется в crm как лента лида и активный лид в воронке конкретного агента, откуда лид
доводится до сделки. Сценарий связывает calendar (владелец слотов, записей, календарных событий и
напоминаний), crm (воронка: клиент, лид, лента), identity (lazy-регистрация) и notifications
(уведомления сторон). Объединяет сервисные UC самозаписи и жизненного цикла записи
(UC-CAL-003,
UC-CAL-004,
UC-CAL-005,
UC-CAL-006) и является слотовым
входом воронки из UC-OVR-001.
Metadata
| Поле | Значение |
|---|---|
| Статус | draft |
| Owning team | calendar |
| Primary capability | self-booking-to-lead |
| Risk level | medium |
Scope И Участники
| Участник | Роль |
|---|---|
| Инвестор / Гость | Актор: выбирает и бронирует свободный слот; отменяет/переносит свою запись (в пределах cutoff) |
| Агент (или консультант ПИН) | Владелец слота: подтверждает/отклоняет Pending-запись, отменяет запись, фиксирует исход встречи |
calendar | Владелец слотов, записей, календарных событий и напоминаний; издатель жизненного цикла записи |
identity (internal) | Lazy-регистрация анонима по телефону → InvestorUserId |
crm | Consumer записей: get-or-create клиента/лида пары «агент ↔ инвестор» + append-only лента лида; довод лида до сделки |
notifications | Уведомления агенту (новая запись) и инвестору (статусы записи) |
System (worker calendar) | Таймаут решения по Pending-записи (авто-Expired) |
Preconditions
- Агент опубликовал слоты самозаписи (UC-CAL-002);
интересующий слот
Available, до его начала большеMinLeadTimeMinutes. - Витрина слотов доступна анонимно; у анонима есть телефон для lazy-регистрации, авторизованный инвестор
действует под своим JWT;
identityдоступен. - Привязка агента задаётся выбранным слотом (
AgentUserId/TenantIdиз записи) — правила реферальной привязки здесь не применяются: агента инвестор выбрал сам.
Основной Сценарий
- Выбор и бронирование слота (
calendar). Инвестор открывает витрину свободных слотов агента (фильтры: агент, проект, объект, вид встречиShowing/Consultation, диапазон дат) и бронирует слот, указав телефон и ФИО (аноним) и опционально комментарий. Для анонима выполняется lazy-регистрация по телефону вidentity. В одном согласованном изменении: слотAvailable → Booked(условный переход — выигрывает ровно один запрос), создаётся запись (ConfirmedприAutoConfirmслота, иначеPendingс дедлайном решения агентаPendingExpiresAt) со снапшотом времени/вида, календарное событие агента (Planned) и напоминания; публикуетсяpin.calendar.appointment-created.v1(при auto-confirm — такжеappointment-confirmed) — UC-CAL-003. - Проекция в воронку (
crm). Поappointment-createdcrm(consumer записей календаря) делает get-or-create карточки клиента и активного лида пары «агент ↔ инвестор» (ФИО/телефон — из собственной display-репликиidentity), пишет в ленту лида факт записи (ShowingAppointmentCreated/ConsultationAppointmentCreatedс комментарием и привязкой к проекту/объекту); при создании нового лида агент, изначально приведший запись, становится иOrigin, иExecutor, публикуетсяpin.crm.lead-created.v1.notificationsуведомляет агента о новой записи — calendar-appointments.md. - Решение агента по
Pending-записи (calendar). Для слота без auto-confirm агент доPendingExpiresAtподтверждает встречу (Pending → Confirmed, слот остаётсяBooked) или отклоняет с причиной (Pending → Declined, слот освобождается/истекает, событиеCancelled, напоминания гасятся); непринятое вовремя решение закрывает запись worker-ом (Pending → Expired). По каждому переходу публикуетсяappointment-confirmed/appointment-cancelled— UC-CAL-004.crmдописывает в ленту лидаAppointmentConfirmedлибоAppointmentCancelled(с причинойDeclinedByAgent/Expired). - Отмена/перенос инвестором (
calendar, опционально). До cutoff инвестор отменяет запись (Cancelled(Investor), каскад освобождения слота и события) или переносит её — атомарная пара «отмена текущей + бронирование нового слота» со взаимными ссылкамиRescheduledFrom/To; агент может отменить запись со своей стороны с причиной. По переходам публикуютсяappointment-cancelled(+appointment-createdпри переносе) — UC-CAL-005.crmотражает отмену/перенос в ленте (перенос помечается как продолжение, новый лид не создаётся). - Исход встречи (
calendar). После наступления времени агент фиксирует исход подтверждённой записи —Completed(встреча состоялась) илиNoShow(клиент не пришёл); запись терминальна, слотArchived, календарное событиеCompleted, напоминания погашены; публикуетсяpin.calendar.appointment-completed.v1с исходом — UC-CAL-006.crmдописывает в лентуAppointmentCompleted/AppointmentNoShow. - Интерес к объекту → намерение бронирования (
crm). Если инвестор на витрине/по ходу показа выражает интерес к конкретному объекту (жмёт «Забронировать» — UC-BKG-001), в лиде заводитсяLeadBookingIntentEntityна пару (LeadId,PropertyObjectId):Source = InvestorAdded,ConfirmedByInvestor = true(инвестор сам инициировал). Если объект предлагает агент по итогам встречи —Source = AgentProposed,ConfirmedByInvestor = falseдо подтверждения инвестором. Намерение — не сделка и не бронь: оно лишь фиксирует желаемый объект. - Довод лида до сделки через фиксацию (
crm). Активный лид в воронке агента с лентой встреч и намерениями работается дальше. Создание сделки для объекта требует двух условий: подтверждённое инвестором намерение (ConfirmedByInvestor = true) и одобренная фиксация на пару (инвестор × застройщик объекта) — фиксация авторизует агента работать с клиентом по проектам застройщика. Оба выполнены → сделка создаётся сразу вApplication(Flow A). Если агент создаёт сделку по объекту, ещё не подтверждённому инвестором, — она заводится вDraft(агент+инвестор, без застройщика, скрыта из канбана застройщика); после подтверждения намерения инвестором и одобренной фиксации срабатывает гейтDraft → Application(Flow B). Состоявшийся показ — основание для квалификации, фиксации и сделки (UC-OVR-005);NoShow— сигнал повторного контакта. Слотовый и no-slot входы сходятся в одном клиенте/лиде (get-or-create по телефону), поэтому повторные касания того же инвестора не плодят вторую воронку.
Sequence
Постусловие
После полного прохода сценария гарантированно:
- в
calendarслот прошёлAvailable → Booked → Archived(или вернулся вAvailable/Expiredпри отмене/отклонении/просрочке), запись достигла терминального статуса (Completed/NoShow/Cancelled/Declined/Expired) со снапшотом времени/вида, календарное событие агента отражает факт (Completed) или отмену (Cancelled), непосланные напоминания погашены (Skipped); - аноним получил учётную запись инвестора (lazy-регистрация идемпотентна по телефону); повторное бронирование тем же телефоном использует существующего пользователя;
- в
crmсуществует ровно один активныйLeadпары «агент ↔ инвестор» сOrigin = Executorпри создании,Source = Appointment, привязкой к компании выбранного агента; лента лида несёт упорядоченную по времени цепочку фактов записи (создание → подтверждение/перенос/отмена → исход), включая комментарий инвестора и привязку к проекту/объекту; для нового лида опубликованоpin.crm.lead-created.v1ровно один раз; - перенос отражён как продолжение (взаимные ссылки
RescheduledFrom/To), второй лид не создан; - агент и инвестор уведомлены на ключевых переходах; лид доступен для доведения до сделки (UC-OVR-005).
Промежуточные состояния, наблюдаемые пользователями: (1) запись создана — «ожидает решения агента»
(Pending) или «встреча подтверждена» (Confirmed); лид у агента появляется асинхронно (< 1 мин); (2)
решение агента — запись подтверждена/отклонена/просрочена; (3) после времени — исход зафиксирован, запись
терминальна.
Альтернативные Сценарии И Отказы
| Ситуация | Поведение |
|---|---|
| Гонка за один слот | Условный переход Available → Booked выигрывает ровно один запрос; проигравший получает SLOT_ALREADY_BOOKED, вторая запись не создаётся даже частично |
| Антиспам-лимиты инвестора | Не более 1 активной записи на пару «инвестор + агент + вид» и ≤ 3 активных суммарно; превышение → APPOINTMENT_DUPLICATE (дубль-детект окна DUPLICATE_PHONE no-slot-входа здесь не применяется — антиспам живёт в календаре) |
| Слот стал недоступен между просмотром и бронированием | CAL_SLOT_NOT_AVAILABLE; до начала меньше MinLeadTimeMinutes → CAL_SLOT_LEAD_TIME_VIOLATION; витрина обновляется |
identity недоступен при lazy-регистрации анонима | IDENTITY_UNAVAILABLE (503); бронирование отклонено целиком, слот остаётся Available; витрина работает read-only |
Агент не принял решение до PendingExpiresAt | Worker переводит запись Pending → Expired (слот освобождён/истёк, событие Cancelled); повторный проход — no-op; лента лида получает AppointmentCancelled (Expired) |
| Отмена инвестором после cutoff | APPOINTMENT_CUTOFF_PASSED; самостоятельная отмена невозможна — отменить может агент (без ограничения cutoff, до фиксации исхода) |
| Перенос: новый слот занят/истёк | SLOT_ALREADY_BOOKED/CAL_SLOT_NOT_AVAILABLE: ничего не меняется, старая запись и её слот не тронуты (всё-или-ничего) |
| Фиксация исхода раньше времени | APPOINTMENT_NOT_STARTED (now < StartAt); до начала возможна отмена, но не исход |
| Повторная фиксация исхода / решение по неактуальной записи | APPOINTMENT_INVALID_STATE: Completed/NoShow терминальны и иммутабельны; ошибочный исход исправляется организационно (новая запись), не переписыванием |
| Гонка «агент ↔ инвестор ↔ worker» | Побеждает первая зафиксированная мутация (оптимистическая блокировка), проигравший получает APPOINTMENT_INVALID_STATE; каскад на слот/событие выполняется ровно один раз |
display-реплика identity в crm отстаёт | Consumer записей — CRM_CALENDAR_SYNC_USER_REPLICA_NOT_FOUND (retriable): лид создаётся после прихода user-upserted; порядок между сервисами не гарантирован |
Событие обработано crm раньше appointment-created (out-of-order топиков) | Контракты самодостаточны: get-or-create по данным пришедшего события, лента дополняется задним числом по времени факта без дублей клиента/лида |
| Инвестору не подошёл ни один слот | No-slot вход «время обсудим» (UC-OVR-001); оба входа сходятся в одном клиенте/лиде по телефону |
Согласованность И Идемпотентность
- Событийная интеграция без общих таблиц.
calendar— единственный владелец слотов, записей, календарных событий и напоминаний;crmведёт только воронку (клиент, лид, лента) и узнаёт о записи исключительно по событиям — синхронно вcalendarне ходит, статус записи не реплицирует (лента append-only). - Атомарность в
calendar. Каждая мутация записи (создание, подтверждение, отмена, перенос, исход) каскадно и атомарно затрагивает слот, календарное событие и напоминания; интеграционное событие уходит только после фиксации перехода. Ключ-идентификатор записи упорядочивает её переходы на партиции; кросс-топик порядок междуappointment-*не гарантирован — контракты самодостаточны. - Идемпотентность воронки. Consumer записей дедуплицирует по идентификатору сообщения и по паре «запись + тип ленты» (не более одной записи ленты на факт); get-or-create клиента/лида идемпотентен по unique-индексам (пара «tenant + инвестор», «tenant + телефон», активный лид на пару). Полный replay всех топиков безопасен — сходится к той же воронке без дублей.
- Единство воронки. Инвариант «один активный лид на пару агент ↔ инвестор» сводит слотовый и no-slot входы,
а также повторные записи, к одному лиду;
lead-createdпубликуется только на новый лид (повторные касания — без интеграционного события). - Окно eventual-consistency. Запись в календаре агента видна сразу; лид в
crmи уведомления — асинхронно (< 1 мин штатно). В этом окне инвестор уже видит подтверждение записи, агент видит её в календаре, а карточка лида в CRM догоняется — это лаг проекции, не ошибка. - Иммутабельные снапшоты и атрибуция. Время/вид записи фиксируются снапшотом (архивация/правка батча слотов
их не меняет); агент, приведший запись, фиксируется в лиде как
Originи начальныйExecutor— историческая атрибуция переживает последующую передачу лида.
Acceptance Criteria (Given/When/Then)
- Happy (слот → лид → исход). Дано: агент опубликовал слоты, активного лида пары нет. Когда: инвестор
бронирует свободный слот, агент подтверждает запись и фиксирует
Completed. Тогда: слот прошёлBooked → Archived, записьConfirmed → Completed, в воронке создан активный лид (Source = Appointment,Origin = Executor) с лентой «создана → подтверждена → состоялась», агент и инвестор уведомлены. - Самозапись анонима. Дано: аноним с новым телефоном. Когда: бронирует слот. Тогда: lazy-регистрация создаёт инвестора, запись привязана к нему; повторное бронирование тем же телефоном использует существующего пользователя и тот же лид.
- Решение агента и таймаут. Дано:
Pending-запись без auto-confirm. Когда: агент подтверждает / отклоняет / не отвечает доPendingExpiresAt. Тогда: записьConfirmed/Declined/Expiredс корректным каскадом слота и события; лента лида получает соответствующий факт; повторный проход worker-а не дублирует событий. - Отмена и перенос. Дано: активная запись. Когда: инвестор отменяет (в пределах cutoff) или переносит на
другой слот. Тогда: отмена атомарно освобождает слот и событие; перенос — атомарная пара со ссылками
RescheduledFrom/To, число активных записей не растёт, в ленте перенос помечен как продолжение (новый лид не создан); занятость нового слота откатывает всю пару. - Исход и терминальность. Дано:
Confirmed-запись,now ≥ StartAt. Когда: агент фиксируетNoShow. Тогда: записьNoShow(терминальна), слотArchived, событиеCompleted, лента лида получаетAppointmentNoShowкак сигнал повторного контакта; повторная фиксация →APPOINTMENT_INVALID_STATE. - Единая воронка. Дано: инвестор записался на слот, затем оставил no-slot заявку тем же телефоном. Когда: оба входа обработаны. Тогда: клиент и активный лид — одни и те же, второй лид не создаётся.
Source Of Truth И Данные
| Данные | Владелец | Consistency rule |
|---|---|---|
| Слот / запись / календарное событие / напоминания | calendar | Атомарный каскад; снапшот времени/вида иммутабелен; переходы — только событиями |
UserEntity инвестора | identity | Lazy-регистрация идемпотентна по телефону |
Client / Lead / лента лида | crm | Get-or-create по unique-индексам; один активный лид на пару; лента append-only |
| Привязка агента (tenant лида/клиента) | calendar (через событие) | Из записи (компания выбранного агента); правила реферала не применяются |
Контракты И Зависимости
- UC сервисов: UC-CAL-003, UC-CAL-004, UC-CAL-005, UC-CAL-006, UC-CAL-002 (публикация слотов).
- Consumer: calendar-appointments.md (маппинг «событие записи → лента лида», идемпотентность, retry/DLQ).
- Events: appointment-created.md, appointment-confirmed.md, appointment-cancelled.md, appointment-completed.md, lead-created.md.
- Смежные сценарии: UC-OVR-001 (слотовый vs no-slot вход воронки), UC-OVR-005 (лид → сделка), UC-BKG-001 (интерес к объекту → намерение бронирования).
- ADR: 0064 (контекст календаря), 0052 (tenancy, инвестор вне tenant), 0056 (простота, polling-worker таймаута).
Наблюдаемость
- critical metrics: конверсия «слот → запись → состоявшаяся встреча»; доля
SLOT_ALREADY_BOOKED(интенсивность гонок),NoShow(качество записей), просроченныхPending(неотвечающие агенты); лаг CRM-проекции и уведомлений (< 1 мин). - trace path: витрина →
calendar(бронирование/жизненный цикл) → шина событий →crm(воронка) /notifications; корреляция сквозного трейса из метаданных событий. - alerts: рост зависших
Pending; лаг consumer записей > 5 мин; ростCRM_CALENDAR_SYNC_USER_REPLICA_NOT_FOUND(рассинхрон с синком identity); DLQ consumer записей > 0.
Риски И Guardrails
- Риск: двойное бронирование одного слота. Control: условный переход
Available → Booked— выигрывает ровно один запрос; на слот не более одной активной записи. - Риск: две воронки на одного инвестора (слотовый + no-slot вход). Control: get-or-create по телефону, один активный лид на пару агент ↔ инвестор.
- Риск: PII формы записи в логах/событиях. Control: в payload
appointment-*PII нет (ФИО/телефон — из реплики identity), телефон маской+7***NNNN, комментарий — класс Internal, в логи не пишется. - Риск: потеря/переупорядочивание фактов ленты при out-of-order доставке. Control: самодостаточные контракты, business-guard «запись + тип ленты», упорядочивание ленты по времени факта.
Обратные ссылки
Автоссылки: где используется этот документ.
- scenarios (1): Кросс-сервисные сценарии PIN (OVR)
- registries (1): Реестр use cases и user stories PIN