UC-FIX-001. Агент отправляет заявку на фиксацию
Назначение
Агент из активного лида выбирает застройщика и отправляет заявку на фиксацию клиента с опциональным override хотелок. Дом полей агрегата — ../domain/fixation-request.md; здесь описан только операционный поток.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-FIX-001 |
| Контекст | crm (см. docs/06-services/crm/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Агент (владелец лида) или Менеджер ПИН от имени агента |
| Permissions / scope | crm.fixations.manage (агентская сторона создания); scope: организация (агентство) |
| Tenant | actor 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) |
Основной Поток
- Ввод. Агент открывает лид
Active, выбирает застройщика (DeveloperCompanyId) и, опционально, правит «шапку хотелок» в форме — этоWishesOverride(лид не мутируется). Модель запросаCreateFixationRequestCommand { LeadId, DeveloperCompanyId, WishesOverride: LeadWishes? }. - Проверка входа. Заданы
LeadId/DeveloperCompanyId; при заданномWishesOverride— валидные диапазоны бюджета VOLeadWishes(иначеVALIDATION_ERROR). - Проверка состояния и связей. Лид существует (../domain/lead.md),
lead.Status == Active(INV-FIX-2 →CRM_LEAD_NOT_ACTIVE) и record-level: актор — активный участник лида (обычноExecutor:lead.ExecutorAgentUserId == actor) илиPinManager(→CRM_LEAD_FORBIDDEN); карточка клиента загружается для snapshot. - Дубль-детект (авто, подсветка). По активным фиксациям застройщика (
TenantId = DeveloperCompanyId,Status ∈ {Pending, FixedUnique, FixedNonUnique}, ../domain/fixation-request.md): точное совпадение по телефону клиента + fuzzy-совпадение ФИО — через поисковую проекциюFixationRequestSearchDocument. Лучшее совпадение →Uniqueness = HasDuplicates+DuplicateOfFixationId+DuplicateMatchKind(Phone|Name|PhoneAndName); иначеUniqueness = NoDuplicates. Решение уникальности — за застройщиком. - Инвариант INV-FIX-001. Проверяется отсутствие активной фиксации на тройку (
OriginAgentUserId,InvestorUserId,DeveloperCompanyId); тройка строится на иммутабельном агенте-инициаторе (OriginAgentUserId), не на текущем исполнителе — гарантия — уникальный индекс активной тройкиux_fixation_active_triple. При наличии →CRM_FIXATION_ACTIVE_EXISTS. - Правила и маппинг. Создаётся
FixationRequestEntity:Status = Pending;ClientName/ClientPhoneNumber— snapshot из клиента (иммутабельны);WishesOverride— snapshot формы (null→ застройщику видны актуальныеLead.Wishes);OriginAgencyCompanyId/OriginAgentUserId/ExecutorAgentUserId(=OriginAgentUserIdпри создании)/InvestorUserId/ClientId/LeadId— из лида;Uniquenessи поля дубля — из шага 4. СобытиеFixationRequested. - Событие ленты лида. В ленту лида добавляется
FixationCreated(INV-LEAD-2, лидActive) с payloadLeadFixationRefPayload { FixationRequestId, DeveloperCompanyId }(../domain/lead.md). СобытиеLeadEventAdded. - Согласованность и результат. Заявка + событие ленты фиксируются атомарно в одном согласованном
изменении; Kafka-событие на создании не публикуется (см. раздел Kafka). Возвращаются
{ Id, Status = Pending, Uniqueness, DuplicateOfFixationId? }→ фронт показываетPendingс подсветкой дублей. Далее — рассмотрение застройщиком (UC-FIX-002 одобрение / UC-FIX-003 отклонение).
Альтернативные Потоки
Uniqueness = HasDuplicates)- Шаг 4 нашёл активную фиксацию с совпадением телефона/ФИО.
- Заявка всё равно создаётся в
Pending; в ответеDuplicateOfFixationId+DuplicateMatchKind, UI подсвечивает дубль.
Возвращается к основному потоку на шаг 6 (создание не блокируется — решение уникальности за застройщиком).
- Шаг 5: активная тройка (
ux_fixation_active_triple) уже занята. CRM_FIXATION_ACTIVE_EXISTS; заявка не создаётся.
Завершается ошибкой (клиент показывает существующую активную фиксацию).
Active / актор не владелец- Шаг 3:
lead.Status != Active→CRM_LEAD_NOT_ACTIVE; либо актор не активный участник лида (ExecutorAgentUserId != actorи не руководитель агентства-владельца/Менеджер ПИН) →CRM_LEAD_FORBIDDEN.
Завершается ошибкой без мутаций.
- Две команды на одну тройку проходят проверку шага 5 одновременно.
ux_fixation_active_tripleотклоняет второе сохранение →CRM_FIXATION_ACTIVE_EXISTS(клиент перечитывает состояние).
Постусловие
- Создан
FixationRequestEntityв статусеPending(TenantId = DeveloperCompanyId) с иммутабельным snapshotClientName/ClientPhoneNumberи вычисленнымUniqueness(+DuplicateOfFixationIdпри дубле). - В ленту лида добавлено событие
FixationCreated; лид не мутирован (Wishesбез изменений). - Инвестор о фиксации не знает и не уведомляется; интеграционное Kafka-событие на создании не публикуется.
- Заявка ожидает решения застройщика (UC-FIX-002/003).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
VALIDATION_ERROR | 422 | Пустой LeadId/DeveloperCompanyId или неверные диапазоны WishesOverride | Показать ошибки формы |
CRM_LEAD_NOT_ACTIVE | 409 | Лид не в статусе Active (INV-FIX-2) | Предложить продлить/выбрать активный лид |
CRM_LEAD_FORBIDDEN | 403 | Актор не активный участник лида (не Executor), не руководитель агентства-владельца и не Менеджер ПИН | Скрыть действие |
CRM_FIXATION_ACTIVE_EXISTS | 409 | Активная фиксация на тройку уже есть (INV-FIX-001) | Открыть существующую фиксацию |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
FixationRequested | Создание заявки (шаг 6) | внутренние (audit); диспетчеризуются при коммите |
LeadEventAdded | Добавление FixationCreated в ленту (шаг 7) | лента лида (../domain/lead.md) |
Kafka Интеграционные События
Создание заявки Kafka-событие не публикует: фиксация внутренняя до решения застройщика, инвестор о ней не
знает, топика fixation-created не существует. Downstream-события публикуются позже, при решении застройщика:
| Событие | Topic | Когда публикуется | Документ |
|---|---|---|---|
FixationApprovedEvent.v1 | pin.crm.fixation-approved.v1 | Одобрение (UC-FIX-002) | ../events/fixation-approved.md |
FixationRejectedEvent.v1 | pin.crm.fixation-rejected.v1 | Отклонение (UC-FIX-003) | ../events/fixation-rejected.md |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Из лида Active агент создаёт заявку с выбранным застройщиком → FixationRequest в Pending | Интеграционный тест команды |
| AC2 | WishesOverride сохраняется snapshot-ом; Lead.Wishes не меняется | Сравнение лида до/после |
| AC3 | ClientName/ClientPhoneNumber — snapshot из ClientEntity на момент подачи | Изменить клиента после подачи — заявка неизменна |
| AC4 | При совпадении телефона/ФИО с активной фиксацией застройщика → Uniqueness = HasDuplicates + DuplicateOfFixationId | Seed дубля + проверка ответа |
| 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.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (4): UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика, UC-FIX-002. Застройщик одобряет фиксацию (авторизация работы с клиентом, без создания сделки), UC-FIX-003. Застройщик отклоняет фиксацию, US-реестр блока «Сделка» (US-INV / US-AGT / US-DEV / US-MGR)
- scenarios (2): UC-OVR-004. От реферальной ссылки до объекта у собственника, UC-OVR-005. От лида до сделки (4 фазы)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- Domain (дом полей): ../domain/fixation-request.md, ../domain/lead.md, ../domain/client.md.
- API метод (путь по конвенции):
../api/agent-create-fixation.md; отмена —../api/agent-cancel-fixation.md. - Kafka-контракты решения: ../events/fixation-approved.md, ../events/fixation-rejected.md.
- Соседние use-cases: user-stories-deal.md (US-AGT), UC-FIX-002 (одобрение) / UC-FIX-003 (отклонение).
- README контекста: ../README.md.
- ADR: 0052 tenancy, 0056 simplicity-first.