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

UC-CRM-001. Создание лида (заявка / реферал / вручную)

Назначение

Сценарий появления первой активной связи «инвестор ↔ агент»: создаётся лид LeadEntity и, при необходимости, его родительская карточка клиента ClientEntity. Три точки входа — заявка с витрины (консультация/бронирование), реферальная привязка и ручное создание агентом. Дом полей — lead.md и client.md; контракт события — lead-created.md.

Metadata

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

Актор

РольPermissions / scopeTenantRecord-level
Гость (витрина)нет (публичный гейт Public)null (ADR-0052)не применяется
Инвестор (витрина)авторизован; своя активностьnull (инвестор без tenant)свои заявки
Агент (вручную)crm.leads.manageAgencyCompanyIdсвои клиенты (ExecutorAgentUserId = actor)
System (реферальная привязка / lazy-reg)system-actorscope записи = AgencyCompanyId/ПИНне применяется

Предусловие

  • Для заявки с витрины: заполнена форма (ФИО + телефон; для бронирования — обязателен PropertyObjectId).
  • Для ручного создания: агент авторизован и принадлежит агентству (AgencyCompanyId).
  • identity доступен (lazy-регистрация инвестора по телефону, RegistrationSource = LazyRegistration); для заявки на бронирование доступен chessboard (валидность PropertyObjectId).

Триггер

ПолеЗначение
ИсточникUI-действие (витрина / ЛК агента) → вызов API; либо событие шины (реферал)
Триггерящий элементПубличная заявка на консультацию/бронирование, ручное создание лида агентом, событие pin.referral.lead-attachments.v1

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

Основная нить — публичная заявка с витрины (гость/инвестор). Ветки «реферал» и «вручную» — в альтернативах A2/A3; общий хвост (get-or-create клиента и лида, лента, публикация события) одинаков для всех точек входа.

  1. Контекст и доступ. Публичный гейт разрешает анонимный вызов. Тип заявки — консультация (Source = ConsultationRequest) либо бронирование (Source = BookingRequest).
  2. Проверка входа. ФИО не пустое, PhoneNumber — формат E.164; для бронирования — задан PropertyObjectId. Нарушение → VALIDATION_ERROR. Повторная публичная заявка тем же телефоном в окне дедупликации → DUPLICATE_PHONE (окно — 24 ч, для booking-заявок сверяется по паре телефон+объект, см. UC-BKG-001). Ручное создание агентом (A2) дедуп-окно не проходит — агент осознанно заводит клиента.
  3. Lazy-регистрация инвестора. Резолв InvestorUserId в identity по нормализованному телефону: существующий пользователь — reuse; иначе создаётся lazy-профиль (RegistrationSource = LazyRegistration, identity/domain/user.md). Недоступность identityIDENTITY_UNAVAILABLE.
  4. Определение агента-владельца. Реферальная привязка даёт ExecutorAgentUserId и TenantId = AgencyCompanyId; при её отсутствии — компания ПИН + случайный сотрудник ПИН (A4).
  5. get-or-create клиента. Ищем живую карточку ClientEntity того же tenant по инвестору (INV-CLI-3), резерв — по телефону (INV-CLI-1). Найдена → reuse (при необходимости привязываем инвестора); иначе создаётся новая карточка со Status = Active (событие ClientCreated).
  6. Проверка INV-CRM-001. Ищем активный лид на пару (ExecutorAgentUserId, PrimaryInvestorUserId) со статусом LeadStatus.Active; параллельные заявки сериализуются. Найден → A1 (событие пишется в существующий лид). Гонка на уникальном индексе активной пары ux_leads_active_pairCRM_LEAD_ACTIVE_PAIR_EXISTS (перечитать и завершиться идемпотентно).
  7. Создание лида. Фабрика лида проверяет INV-CRM-001 и создаёт LeadEntity: TenantId = AgencyCompanyId (или ПИН), Status = Active, ExpiresAt = CreatedAt + 1 месяц, Wishes = LeadWishes.Empty (публичная заявка; форму квалификации агент заполняет позже — INV-LEAD-5); событие LeadCreated. Клиенту проставляется денормализованный ActiveLeadId (INV-CLI-2).
  8. Событие ленты. В ленту лида добавляется LeadEventEntity: тип по Source (ConsultationRequested / BookingRequested), типизированный payload (LeadConsultationRequestedPayload / LeadBookingRequestedPayload, дом — lead.md), обновляется Lead.LastEventAt. Событие LeadEventAdded.
  9. Согласованность. Клиент + лид + событие ленты + запись в outbox фиксируются атомарно в одном согласованном изменении.
  10. Публикация. Интеграционное событие LeadCreatedEvent (топик pin.crm.lead-created.v1, AggregateId = LeadId) публикуется через outbox — только при создании нового лида (в A1 — no-op). Потребители: notifications (уведомление агенту), referral (подтверждение привязки при Source = Referral).
  11. Результат. Возвращаются LeadId, ClientId, Status, ExpiresAt.

Диаграмма

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

A1. У пары уже есть активный лид (INV-CRM-001)

  1. Шаг 6 нашёл активный лид (Status = Active) на пару (ExecutorAgentUserId, PrimaryInvestorUserId).
  2. Новый LeadEntity не создаётся; событие пишется в существующую ленту, обновляется LastEventAt.
  3. LeadCreatedEvent не публикуется (не новый лид — идемпотентный no-op). При гонке на ux_leads_active_pair — перечитать активный лид и завершиться идемпотентно.

Завершается без создания лида (создан только LeadEventEntity).

A2. Ручное создание агентом (Source = Manual)

  1. Актор — агент (crm.leads.manage), TenantId = AgencyCompanyId.
  2. Клиент выбирается из базы агента или создаётся (InvestorUserId может быть null до регистрации); lazy-регистрация (шаг 3) не выполняется.
  3. Wishes заполняется из формы квалификации агента (не Empty). Далее — общий хвост шагов 6–11.

A3. Реферальная привязка (Source = Referral)

  1. Событие pin.referral.lead-attachments.v1 обрабатывает реферальный консюмер (system-actor) — весь get-or-create + INV-CRM-001 + добавление события ленты описаны в referral-registered.md (не дублируется здесь).
  2. ReferralLinkId заполнен; LeadCreatedEvent несёт Source = Referralreferral подтверждает привязку.

Возвращается к общему хвосту (шаги 7–11) внутри обработки события.

A4. Реферальной привязки нет (UC-2)

  1. Шаг 4 не нашёл реферальную привязку/агента-владельца.
  2. TenantId = CompanyId компании ПИН; ExecutorAgentUserId = случайный сотрудник ПИН.
  3. Далее — общий хвост; лид «живёт» в компании ПИН, уведомление уходит выбранному сотруднику.

Постусловие

  • Создана карточка клиента (ClientEntity, если не было) со Status = Active.
  • Создан лид (LeadEntity, Status = Active, ExpiresAt = CreatedAt + 1 месяц) — кроме A1, где лид переиспользуется.
  • В ленту добавлено событие (LeadEventEntity) соответствующего типа; обновлён Lead.LastEventAt.
  • Опубликован pin.crm.lead-created.v1только при создании нового лида (не в A1).

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

КодHTTPУсловиеПоведение клиента
VALIDATION_ERROR400Пустое ФИО / неверный телефон / бронирование без PropertyObjectIdПоказать ошибки формы
DUPLICATE_PHONE409Повторная публичная заявка тем же телефоном в окне дедупликацииСообщить, что заявка уже принята
CRM_CLIENT_PHONE_DUPLICATE409Телефон занят другой живой карточкой агентства (INV-CLI-1)Ретрай / ручной merge (забота)
CRM_LEAD_ACTIVE_PAIR_EXISTS— (internal)Гонка ux_leads_active_pair при созданииПеречитать и завершиться идемпотентно (A1)
IDENTITY_UNAVAILABLE503identity недоступен при lazy-регистрацииРетрай позже
CHESSBOARD_UNAVAILABLE503chessboard недоступен при валидации PropertyObjectId (бронирование)Ретрай позже

Доменные События

СобытиеКогда поднимаетсяSubscribers
ClientCreatedСоздание новой карточки клиентавнутренний диспетчер
LeadCreatedСоздание нового лида ([*] → Active)внутренний диспетчер → сборка LeadCreatedEvent
LeadEventAddedДобавление LeadEventEntity в лентувнутренний диспетчер

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

СобытиеTopicКогда публикуетсяConsumers
LeadCreatedEventpin.crm.lead-created.v1Только при создании нового лида ([*] → Active); A1 — no-opnotifications (уведомление агенту), referral (подтверждение привязки при Source = Referral)

Контракт/payload — lead-created.md (PII осознанно исключён). Публикация — через outbox, атомарно с созданием агрегата.

Acceptance Criteria

#КритерийПроверка
AC1Публичная заявка «консультация» создаёт клиент + лид + событие ConsultationRequestedIntegration-тест публичного метода; проверить 3 записи + outbox
AC2Заявка на бронирование создаёт лид Source = BookingRequest, событие BookingRequested с PropertyObjectIdТест заявки на бронирование; проверить payload события ленты
AC3У пары уже есть Active-лид → новый лид не создаётся, событие в существующий, lead-created не публикуетсяТест A1: два вызова подряд, одна строка leads, две — lead_events
AC4Нет реф-привязки → TenantId = ПИН, ExecutorAgentUserId = сотрудник ПИНТест A4: проверить tenant/agent лида
AC5ExpiresAt = CreatedAt + 1 месяцAssert на созданном лиде
AC6Клиент + лид + событие + outbox атомарны; сбой outbox откатывает лидТест с инъекцией сбоя транзакции
AC7Публичная заявка тем же телефоном в окне даёт 409 DUPLICATE_PHONEТест повторной заявки тем же телефоном

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

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

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