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

UC-OVR-001. Запрос консультации у агента недвижимости

Назначение

Cross-service сценарий: инвестор с публичной витрины (или по реферальной ссылке агента) просит консультацию/ презентацию, и заявка превращается в лид в воронке конкретного агента. Оркеструет crm (публичный вход), identity (lazy-регистрация), referral (привязка агента), catalog/selections (контекст объекта), notifications (уведомление агенту). Альтернативный вход — самозапись на конкретный свободный слот агента через calendar (см. альтернативный сценарий ниже): лид создаётся асинхронно по событию pin.calendar.appointment-created.v1.

Metadata

ПолеЗначение
Статусdraft
Owning teamcrm
Primary capabilityconsultation-lead-intake
Risk levelmedium

Scope И Участники

УчастникРоль
Инвестор / ГостьАктор: отправляет форму «ФИО + телефон» + опц. контекст (подборка/объект/время)
catalog / selectionsИсточник контекста заявки (PropertyObjectId, SelectionId) — подсказки для агента
crm (public)Оркестратор: приём заявки, get-or-create Client/Lead, событие ленты ConsultationRequested
identity (internal)Lazy-регистрация инвестора по телефону → InvestorUserId
referralПравила привязки: реф-ссылка агента → AgentUserId + TenantId = AgencyCompanyId
calendarАльтернативный вход: витрина свободных слотов агента и бронирование (UC-CAL-003) — лид создаётся по событию appointment-created
notificationsConsumer lead-created → пуш/СМС агенту «новый лид»

Preconditions

  • Витрина доступна анонимно или под JWT инвестора; при переходе по реф-ссылке в cookie лежит referralUrl.
  • У агента есть активная реф-ссылка (см. UC-REF-001).
  • Для слотового входа: агент опубликовал слоты самозаписи (UC-CAL-002).

Основной Сценарий

  1. Инвестор на карточке объекта/подборки жмёт «Получить консультацию» и заполняет форму ФИО + телефон.
  2. Фронт вызывает POST /api/public/crm/consultation-requests (create-consultation-request.md), передаёт опц. SelectionId/PropertyObjectId/PreferredAt и cookie referralUrl.
  3. crm выполняет дубль-детект по телефону в окне (при повторе в окне — 409 DUPLICATE_PHONE).
  4. crmidentity internal «get-or-create user by phone» (lazy-регистрация) → InvestorUserId.
  5. crm определяет агентскую сторону через правила привязки referral: валидная реф-ссылка агента → AgentUserId = владелец, TenantId = AgencyCompanyId. (Ссылки нет / ссылка инвестора → маршрут UC-OVR-002.)
  6. get-or-create Client (уникальность по паре tenant + телефон) и активного Lead (инвариант «один активный лид на пару агент↔инвестор»); лид фиксирует событие ленты ConsultationRequested — см. UC-CRM-001.
  7. В одном согласованном изменении сохраняются клиент, лид и событие ленты; для нового лида публикуется pin.crm.lead-created.v1 (lead-created.md).
  8. notifications (consumer lead-created) уведомляет агента; агент видит лид в CRM и берёт в работу.

Sequence

Постусловие

После успешного сценария (нового лида) гарантированно:

  • в identity существует UserEntity инвестора (InvestorUserId), созданный/найденный lazy-регистрацией по телефону;
  • в crm есть Client (уникален по паре tenant + телефон) и ровно один активный Lead пары агент↔инвестор со снапшотами AgentUserId = владелец реф-ссылки и TenantId = AgencyCompanyId (иммутабельны);
  • зафиксировано событие ленты ConsultationRequested в истории лида;
  • опубликовано pin.crm.lead-created.v1 (Source = ConsultationRequest, key = LeadId) — ровно один раз на новый лид; агент получит уведомление в пределах окна обработки notifications (< 5 мин);
  • инвестору немедленно возвращён 201 {requestId, receivedAt} — заявка принята.

Если активный лид пары уже был — новые сущности не создаются, интеграционное событие не публикуется; добавляется только новое событие ленты ConsultationRequested (повторное обращение того же инвестора).

Альтернативные Сценарии И Отказы

СитуацияПоведение
Реф-ссылки нет или ссылка инвестораМаршрут переходит в UC-OVR-002 (агент ПИН)
Реф-cookie просрочена/невалидна (код не найден в referral)Привязка не даётся → fallback на ПИН-маршрут (UC-OVR-002); подделка cookie не должна давать чужую атрибуцию
Пустые/некорректные ФИО или телефон (формат)400 VALIDATION до дубль-детекта и lazy-регистрации; сущности не создаются
Повторная заявка тем же телефоном в окне409 DUPLICATE_PHONE (дедуп по телефону в окне); повтор идемпотентен для пользователя — новых лидов/событий нет
identity недоступен503 IDENTITY_UNAVAILABLE, форма предлагает повтор; лид не создаётся (нет InvestorUserId)
Активный лид пары агент↔инвестор уже естьПереиспользуется (один активный лид на пару агент↔инвестор), интеграционных событий нет — новое событие ленты
Указанный SelectionId/PropertyObjectId не существует/снятКонтекст-подсказка отбрасывается (деградация), заявка принимается без контекста — не блокирует лид
Инвестор хочет конкретное время (не «время обсудим»)Слотовый вход: бронирование свободного слота агента — см. альтернативный сценарий ниже

Альтернативный Сценарий: Инвестор Выбирает Свободный Слот Агента

Тот же бизнес-результат — лид в воронке конкретного агента — достигается самозаписью на конкретное время вместо формы «время обсудим»:

  1. Инвестор на странице агента / проекта / объекта открывает витрину свободных слотов (list-available-slots, доступно анониму) и выбирает слот нужного вида (Showing / Consultation).
  2. Бронирование слота (book-slot; детально — UC-CAL-003): lazy-регистрация анонима по телефону через identity (как в основном сценарии), слот Available → Booked, запись (Pending или Confirmed по AutoConfirm) и календарное событие агента — в одном согласованном изменении; публикуется pin.calendar.appointment-created.v1 (appointment-created.md).
  3. crm (consumer docs/06-services/crm/consumers/calendar-appointments.md) по appointment-created асинхронно (< 1 мин) делает get-or-create Client/Lead в tenant компании агента и пишет ленту лида (вид/время встречи, Comment инвестора); для нового лида публикуется pin.crm.lead-created.v1 — дальше как шаг 8 основного сценария.
  4. Привязка агента задаётся выбранным слотом (AgentUserId/TenantId из события) — правила referral не участвуют. Дубль-детект окна DUPLICATE_PHONE не применяется: антиспам живёт на стороне календаря (не более 1 активной записи на пару «инвестор + агент + вид», ≤ 3 активных записей суммарно — APPOINTMENT_DUPLICATE).
  5. Pending-запись ждёт решения агента (UC-CAL-004). Оба входа воронки — слотовый и no-slot — сходятся в одном клиенте/лиде (get-or-create по телефону инвестора).

Согласованность И Идемпотентность

  • Атомарность записи. Клиент, лид и событие ленты сохраняются в одном согласованном изменении; событие lead-created уходит только после фиксации (иначе не уходит совсем).
  • Идемпотентность входа. Lazy-регистрация в identity идемпотентна по телефону; дубль-детект в окне отсекает повторные формы (409 DUPLICATE_PHONE). Инвариант «один активный лид на пару агент↔инвестор» делает повторную заявку no-op для интеграционных потребителей.
  • Порядок и доставка. pin.crm.lead-created.v1: key = LeadId, at-least-once. Потребитель notifications дедуплицирует по MessageId; событие append-only (version-guard не требуется).
  • Окно eventual-consistency. Между 201 инвестору и появлением лида у агента — задержка обработки notifications (< 5 мин). В этом окне инвестор уже видит «заявка принята», агент — ещё нет.
  • Слотовый вход — событийный. При бронировании слота лид появляется в CRM асинхронно по appointment-created (окно < 1 мин); запись в календаре агента видна сразу. Идемпотентность консюмера — MessageId + business key AppointmentId; инвариант «один активный лид на пару агент↔инвестор» сводит слотовый и no-slot входы к одному лиду.

Acceptance Criteria (Given/When/Then)

  • Happy (новый лид у агента). Дано: анонимный инвестор пришёл по валидной реф-ссылке агента (cookie referralUrl), активного лида пары нет. Когда: отправляет форму ФИО + телефон с карточки объекта. Тогда: создаются Client и активный Lead (AgentUserId = владелец ссылки, TenantId = AgencyCompanyId), фиксируется ConsultationRequested, публикуется pin.crm.lead-created.v1, агент получает уведомление, инвестор — 201.
  • Повторное обращение (реюз лида). Дано: активный лид этой пары уже есть. Когда: инвестор снова отправляет форму. Тогда: новый лид не создаётся, интеграционное событие не публикуется, добавляется только событие ленты ConsultationRequested; ответ 201.
  • Дубль по телефону. Дано: заявка с тем же телефоном в окне дедупликации. Когда: форма отправлена повторно. Тогда: 409 DUPLICATE_PHONE, новые сущности и события не создаются.
  • Нет привязки к агенту. Дано: реф-cookie отсутствует/невалидна/указывает на инвестора. Когда: форма отправлена. Тогда: сценарий переходит на ПИН-маршрут (UC-OVR-002).
  • identity недоступен. Дано: сервис identity не отвечает. Когда: форма отправлена. Тогда: 503 IDENTITY_UNAVAILABLE, лид не создан, форма предлагает повтор.
  • Слотовый вход (самозапись). Дано: агент опубликовал слоты, активного лида пары нет. Когда: инвестор бронирует свободный слот агента с витрины. Тогда: создана запись и календарное событие агента, по appointment-created CRM создаёт Client и активный Lead в tenant компании агента с событием ленты о встрече; последующая no-slot заявка того же телефона переиспользует этот лид.

Source Of Truth И Данные

ДанныеВладелецConsistency rule
Client / Lead / LeadEventcrmВ одном согласованном изменении; уникальность клиента по паре tenant + телефон; один активный лид на пару агент↔инвестор
UserEntity (инвестор)identityLazy-регистрация идемпотентна по телефону
Привязка агентаreferralПравила R1/R2/R6 (attach-rules.md)

Контракты И Зависимости

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

  • critical metrics: crm_consultation_requests_total{result}, доля привязанных к агенту vs ПИН.
  • trace path: витрина → crm.publicidentity.internal → шина событий → notifications.
  • alerts: рост 409 DUPLICATE_PHONE / 503 IDENTITY_UNAVAILABLE.

Риски И Guardrails

  • Риск: PII формы в логах. Control: телефон маской +7***NNNN, ФИО не логируется.
  • Риск: неверная привязка агента при поддельной реф-cookie. Control: валидация кода в referral, fallback на ПИН.

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

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