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

UC-FIX-001. Агент отправляет заявку на фиксацию

Назначение

Агент из активного лида выбирает застройщика и отправляет заявку на фиксацию клиента с опциональным override хотелок. Дом полей агрегата — ../domain/fixation-request.md; здесь описан только операционный поток.

Metadata

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

Актор

ПолеЗначение
РольАгент (владелец лида) или Менеджер ПИН от имени агента
Permissions / scopecrm.fixations.manage (агентская сторона создания); scope: организация (агентство)
Tenantactor tenant = AgencyCompanyId; создаваемая заявка — TenantId = DeveloperCompanyId (выбранный застройщик, ADR-0052)
Record-levelактор — активный участник лида (обычно Executor: Lead.ExecutorAgentUserId = actor); руководитель/админ агентства-владельца — по TenantId лида (= AgencyCompanyId); Менеджер ПИН — platform-scope

Предусловие

  • Существует лид пары «агент ↔ инвестор» в статусе Active (../domain/lead.md, INV-CRM-001).
  • У лида есть карточка клиента ClientEntity с заполненными Name (FullName) и PhoneNumber (../domain/client.md) — источник snapshot заявки.
  • Актор — активный участник лида (обычно Executor: ExecutorAgentUserId = actor) либо Менеджер ПИН; выбранный застройщик существует.

Триггер

ПолеЗначение
ИсточникВызов API (UI-действие «Отправить фиксацию»)
Триггерящий элементформа отправки фиксации на карточке лида /crm (agent area)

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

  1. Ввод. Агент открывает лид Active, выбирает застройщика (DeveloperCompanyId) и, опционально, правит «шапку хотелок» в форме — это WishesOverride (лид не мутируется). Модель запроса CreateFixationRequestCommand { LeadId, DeveloperCompanyId, WishesOverride: LeadWishes? }.
  2. Проверка входа. Заданы LeadId/DeveloperCompanyId; при заданном WishesOverride — валидные диапазоны бюджета VO LeadWishes (иначе VALIDATION_ERROR).
  3. Проверка состояния и связей. Лид существует (../domain/lead.md), lead.Status == Active (INV-FIX-2 → CRM_LEAD_NOT_ACTIVE) и record-level: актор — активный участник лида (обычно Executor: lead.ExecutorAgentUserId == actor) или PinManager (→ CRM_LEAD_FORBIDDEN); карточка клиента загружается для snapshot.
  4. Дубль-детект (авто, подсветка). По активным фиксациям застройщика (TenantId = DeveloperCompanyId, Status ∈ {Pending, FixedUnique, FixedNonUnique}, ../domain/fixation-request.md): точное совпадение по телефону клиента + fuzzy-совпадение ФИО — через поисковую проекцию FixationRequestSearchDocument. Лучшее совпадение → Uniqueness = HasDuplicates + DuplicateOfFixationId + DuplicateMatchKind (Phone|Name|PhoneAndName); иначе Uniqueness = NoDuplicates. Решение уникальности — за застройщиком.
  5. Инвариант INV-FIX-001. Проверяется отсутствие активной фиксации на тройку (OriginAgentUserId, InvestorUserId, DeveloperCompanyId); тройка строится на иммутабельном агенте-инициаторе (OriginAgentUserId), не на текущем исполнителе — гарантия — уникальный индекс активной тройки ux_fixation_active_triple. При наличии → CRM_FIXATION_ACTIVE_EXISTS.
  6. Правила и маппинг. Создаётся FixationRequestEntity: Status = Pending; ClientName/ ClientPhoneNumber — snapshot из клиента (иммутабельны); WishesOverride — snapshot формы (null → застройщику видны актуальные Lead.Wishes); OriginAgencyCompanyId/OriginAgentUserId/ ExecutorAgentUserId (= OriginAgentUserId при создании)/InvestorUserId/ClientId/LeadId — из лида; Uniqueness и поля дубля — из шага 4. Событие FixationRequested.
  7. Событие ленты лида. В ленту лида добавляется FixationCreated (INV-LEAD-2, лид Active) с payload LeadFixationRefPayload { FixationRequestId, DeveloperCompanyId } (../domain/lead.md). Событие LeadEventAdded.
  8. Согласованность и результат. Заявка + событие ленты фиксируются атомарно в одном согласованном изменении; Kafka-событие на создании не публикуется (см. раздел Kafka). Возвращаются { Id, Status = Pending, Uniqueness, DuplicateOfFixationId? } → фронт показывает Pending с подсветкой дублей. Далее — рассмотрение застройщиком (UC-FIX-002 одобрение / UC-FIX-003 отклонение).

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

примечание
A1. Найден дубль (Uniqueness = HasDuplicates)
  1. Шаг 4 нашёл активную фиксацию с совпадением телефона/ФИО.
  2. Заявка всё равно создаётся в Pending; в ответе DuplicateOfFixationId + DuplicateMatchKind, UI подсвечивает дубль.

Возвращается к основному потоку на шаг 6 (создание не блокируется — решение уникальности за застройщиком).

A2. Уже есть активная фиксация на тройку (INV-FIX-001)
  1. Шаг 5: активная тройка (ux_fixation_active_triple) уже занята.
  2. CRM_FIXATION_ACTIVE_EXISTS; заявка не создаётся.

Завершается ошибкой (клиент показывает существующую активную фиксацию).

warning
A3. Лид не Active / актор не владелец
  1. Шаг 3: lead.Status != ActiveCRM_LEAD_NOT_ACTIVE; либо актор не активный участник лида (ExecutorAgentUserId != actor и не руководитель агентства-владельца/Менеджер ПИН) → CRM_LEAD_FORBIDDEN.

Завершается ошибкой без мутаций.

A4. Гонка «агент создаёт / параллельная фиксация»
  1. Две команды на одну тройку проходят проверку шага 5 одновременно.
  2. ux_fixation_active_triple отклоняет второе сохранение → CRM_FIXATION_ACTIVE_EXISTS (клиент перечитывает состояние).

Постусловие

  • Создан FixationRequestEntity в статусе Pending (TenantId = DeveloperCompanyId) с иммутабельным snapshot ClientName/ClientPhoneNumber и вычисленным Uniqueness (+ DuplicateOfFixationId при дубле).
  • В ленту лида добавлено событие FixationCreated; лид не мутирован (Wishes без изменений).
  • Инвестор о фиксации не знает и не уведомляется; интеграционное Kafka-событие на создании не публикуется.
  • Заявка ожидает решения застройщика (UC-FIX-002/003).

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

КодHTTPУсловиеПоведение клиента
VALIDATION_ERROR422Пустой LeadId/DeveloperCompanyId или неверные диапазоны WishesOverrideПоказать ошибки формы
CRM_LEAD_NOT_ACTIVE409Лид не в статусе Active (INV-FIX-2)Предложить продлить/выбрать активный лид
CRM_LEAD_FORBIDDEN403Актор не активный участник лида (не Executor), не руководитель агентства-владельца и не Менеджер ПИНСкрыть действие
CRM_FIXATION_ACTIVE_EXISTS409Активная фиксация на тройку уже есть (INV-FIX-001)Открыть существующую фиксацию

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

СобытиеКогда поднимаетсяSubscribers
FixationRequestedСоздание заявки (шаг 6)внутренние (audit); диспетчеризуются при коммите
LeadEventAddedДобавление FixationCreated в ленту (шаг 7)лента лида (../domain/lead.md)

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

Создание заявки Kafka-событие не публикует: фиксация внутренняя до решения застройщика, инвестор о ней не знает, топика fixation-created не существует. Downstream-события публикуются позже, при решении застройщика:

СобытиеTopicКогда публикуетсяДокумент
FixationApprovedEvent.v1pin.crm.fixation-approved.v1Одобрение (UC-FIX-002)../events/fixation-approved.md
FixationRejectedEvent.v1pin.crm.fixation-rejected.v1Отклонение (UC-FIX-003)../events/fixation-rejected.md

Acceptance Criteria

#КритерийПроверка
AC1Из лида Active агент создаёт заявку с выбранным застройщиком → FixationRequest в PendingИнтеграционный тест команды
AC2WishesOverride сохраняется snapshot-ом; Lead.Wishes не меняетсяСравнение лида до/после
AC3ClientName/ClientPhoneNumber — snapshot из ClientEntity на момент подачиИзменить клиента после подачи — заявка неизменна
AC4При совпадении телефона/ФИО с активной фиксацией застройщика → Uniqueness = HasDuplicates + DuplicateOfFixationIdSeed дубля + проверка ответа
AC5Вторая активная фиксация на ту же тройку → CRM_FIXATION_ACTIVE_EXISTSПовторная команда
AC6На создании нет outbox-записи pin.crm.fixation-*; инвестор не уведомлёнПроверка таблицы outbox
AC7В ленту лида добавлено событие FixationCreated с FixationRequestId/DeveloperCompanyIdЧтение lead_events

Кросс-сервисные ожидания и гарантии

Создание фиксации — полностью синхронная операция внутри crm: интеграционного Kafka-события на подаче нет (фиксация внутренняя до решения застройщика, инвестор о ней не знает). Downstream-события появляются позже — при решении застройщика (UC-FIX-002/003).

Что crm предоставляет: на подаче — ничего наружу; создаётся FixationRequestEntity(Pending) и запись в ленте лида (FixationCreated). Право на комиссию и уведомления возникают только при одобрении.

Что операция ожидает (внутренние зависимости и их согласованность):

  • Лид и карточка клиента — из собственного хранилища crm (строгая согласованность): Lead.Status = Active, Client.Name/Client.PhoneNumber заданы (источник snapshot заявки).
  • Поисковая проекция FixationRequestSearchDocument (Elasticsearch, ADR-0059) — eventually consistent: дубль-детект (шаг 4) по ней — best-effort подсветка, а не гейт. Только что созданная параллельная заявка может ещё не попасть в проекцию — тогда Uniqueness первой оценки не отражает дубль; повторная авто-проверка выполняется при открытии карточки застройщиком (../domain/fixation-request.md, «Дубль-детект»). Жёсткая гарантия единственности — синхронный уникальный индекс активной тройки ux_fixation_active_triple, а не проекция.
  • Выбранный застройщик (DeveloperCompanyId) существует — заявка адресуется его TenantId (ADR-0052).

Промежуточное состояние: фронт сразу показывает Pending с подсветкой дублей из ответа; никаких внешних эффектов и уведомлений до решения застройщика.

Идемпотентность, конкурентность и восстановление

  • Идемпотентность. Естественного идемпотентного ключа нет; повторная подача той же тройки не создаёт вторую активную заявку — INV-FIX-001 (ux_fixation_active_triple) отклоняет её CRM_FIXATION_ACTIVE_EXISTS. Разные застройщики для одной пары агент↔инвестор — независимые тройки (несколько активных фиксаций допустимо).
  • Конкурентность (A4). Две команды на одну тройку, прошедшие проверку шага 5 одновременно: уникальный индекс отклоняет второе сохранение → проигравший получает CRM_FIXATION_ACTIVE_EXISTS и перечитывает состояние. Дубль-детект здесь ни при чём (он про других клиентов у застройщика, а не про ту же тройку).
  • Восстановление. Мутаций при любой ветке отказа (A2/A3/A4) нет; лента лида и заявка фиксируются атомарно — частичной записи (заявка без события ленты или наоборот) быть не может. DLQ/ретраи на подаче неприменимы (нет исходящих событий).

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

  • Логи (без PII): LeadId, DeveloperCompanyId, FixationRequestId, Uniqueness, DuplicateMatchKind, actor и исход (успех / код ошибки); ФИО/телефон клиента в логи не пишутся (только Id).
  • Метрики: число поданных заявок; доля с Uniqueness = HasDuplicates (качество дубль-детекта); доля CRM_FIXATION_ACTIVE_EXISTS (повторы/гонки); доля CRM_LEAD_NOT_ACTIVE/CRM_LEAD_FORBIDDEN; латентность запроса к поисковой проекции.
  • Латентность/нагрузка: интерактивная форма — P95 в пределах сотен мс; дубль-детект — один запрос к Elasticsearch (низкая нагрузка, ADR-0056).
  • Доступ: crm.fixations.manage в scope агентства; record-level — актор как активный участник лида (обычно Executor: Lead.ExecutorAgentUserId = actor), руководитель агентства-владельца либо Менеджер ПИН (platform-scope); нарушение — CRM_LEAD_FORBIDDEN/403.

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

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

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