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

UC-CRM-002. Агент создаёт сделку из лида (выбор объекта, гейт вовлечения застройщика)

Назначение

Агент из активного лида создаёт сделку DealEntity по объекту недвижимости. Сделка попадает в один из двух статусов по гейту вовлечения застройщика: если для пары «инвестор × застройщик» есть одобренная фиксация (../domain/fixation-request.md) и объект — подтверждённое инвестором намерение (../domain/lead.md) — сразу Application (застройщик вовлечён, SellManager в составе); иначе — Draft (сделка агента с инвестором, застройщик ещё не вовлечён). Одобрение фиксации сделку не создаёт, а авторизует заводить сколько угодно сделок по объектам застройщика (связь фиксация→сделки 1:N). Дом полей сделки — ../domain/deal.md; здесь описаны только поток и его дельта.

Metadata

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

Актор

ПолеЗначение
РольАгент (владелец лида) или Менеджер ПИН
Permissions / scopecrm.deals.manage; агент — record-level (владелец лида, активный участник-исполнитель ExecutorAgentUserId = actor); Менеджер ПИН — platform-scope (кросс-tenant, ADR-0052)
TenantАгент: TenantId = AgencyCompanyId; создаваемая сделка — TenantId = DeveloperCompanyId застройщика объекта (set-once, immutable — INV-DEAL-1/INV-DEAL-10)
Record-levelАгент видит только лид, где он активный участник-исполнитель; Менеджер ПИН — любой

Предусловие

  • Лид существует и Status = Active (../domain/lead.md, enum LeadStatus; INV-LEAD-2).
  • Данные объекта недвижимости (владелец DeveloperCompanyId, ProjectId, цена, статус) доступны в chessboard-реплике; объект не Продан.
  • У застройщика выбранного объекта есть живой этап группы Application (../domain/pipeline-step.md, SEED-default) — стартовая колонка сделки.
  • При PaymentMethod = Installment выбран InstallmentPresetEntity застройщика (../domain/purchase-program.md); FullPayment/Mortgage — по PurchaseProgramEntity.
  • Для создания сразу в Application дополнительно: подтверждённое инвестором намерение по объекту (ConfirmedByInvestor = true) и одобренная фиксация пары «инвестор × застройщик».

Триггер

ПолеЗначение
ИсточникUI-действие → вызов API
Триггерящий элементКнопка «Создать сделку» на карточке лида → POST /api/agent/crm/deals; довод Draft до Application — «Вовлечь застройщика» → POST /api/agent/crm/deals/{dealId}/engage-developer

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

Основной поток описывает Flow A (сделка сразу в Application); ветка Draft и её довод до ApplicationFlow B в альтернативных потоках A1.

  1. Контекст и доступ. Агент открывает карточку лида (Status = Active) и запускает создание сделки по объекту, к которому инвестор ранее добавил и подтвердил намерение (../domain/lead.md, Source = InvestorAdded).
  2. Ввод. Форма: выбор объекта PropertyObjectId (витрина застройщика chessboard/catalog) и способа оплаты PaymentMethod; при Installment — выбор InstallmentPresetId. Модель запроса CreateDealRequest { LeadId, PropertyObjectId, PaymentMethod?, InstallmentPresetId?, Title? }.
  3. Проверка входа. LeadId/PropertyObjectId заданы; при Installment задан InstallmentPresetId — иначе CRM_DEAL_INSTALLMENT_PRESET_REQUIRED; Title ≤ 256.
  4. Проверки состояния и связей (сначала лид-основание для инварианта):
    • Лид существует и Status = Active — иначе CRM_LEAD_NOT_ACTIVE; record-level: актор — активный участник-исполнитель лида либо Менеджер ПИН.
    • Из chessboard-реплики по PropertyObjectId — владелец DeveloperCompanyId (→ TenantId), ProjectId, BuildingId?, цена (snapshot Money), статус; статус ПроданCRM_PROPERTY_OBJECT_NOT_AVAILABLE.
    • Активное намерение инвестора по объекту (пара LeadId × PropertyObjectId) и одобренная фиксация пары «инвестор × застройщик».
    • При Installment — пресет существует и принадлежит TenantId застройщика.
    • Живой SEED-этап группы Application того же застройщика — иначе CRM_DEAL_PIPELINE_NOT_CONFIGURED.
  5. Вычисление гейта вовлечения застройщика (INV-DEAL-11). Гейт выполнен, когда одновременно: объект — подтверждённое инвестором намерение (ConfirmedByInvestor = true) И для пары «инвестор × застройщик» есть одобренная фиксация. В Flow A гейт выполнен → целевой статус Application, и тогда PaymentMethod обязателен (иначе CRM_DEAL_PAYMENT_METHOD_REQUIRED, INV-DEAL-1).
  6. Бизнес-правила и маппинг. Создаётся сделка: Status = Application, StepGroup = Application, StepId = найденный этап, Price = snapshot из chessboard, Title из объекта; сделка привязывается к намерению (IntentId = идентификатор намерения, IntentConfirmedAt = его ConfirmedAt); связь с одобренной фиксацией — FixationRequestId (не уникален: 1 фиксация → N сделок). Состав участников (INV-DEAL-2) — InvestorPrimary (lead.PrimaryInvestorUserId) + Executor (создатель) + SellManager (менеджер продаж застройщика) + авто-PinManager. События DealCreated / DealMemberAdded; CreatedByUserId = actor.
  7. Что меняется. Создаётся сделка (+ дочерние DealMemberEntity), пишется таймлайн DealTimelineEventEntity(DealCreated) и запись ленты лида LeadEventEntity(DealCreated) (../domain/lead.md, payload LeadDealRefPayload), обновляется LastEventAt. Всё — в одном согласованном изменении вместе с записью в outbox.
  8. Публикация. Интеграционное событие DealCreatedEvent (топик pin.crm.deal-created.v1) собирается из полей агрегата и активного состава Members, публикуется через outbox.
  9. Результат. Возвращаются DealId, Status = Application, StepId, Title, состав участников. Фронт переходит на карточку сделки; сделка появляется на канбане /crm застройщика в колонке группы Application.
  10. Асинхронно. Консюмер messenger по pin.crm.deal-created.v1 создаёт чат сделки и синкает участников; Deal.ChatStatus: Pending → Provisioning → Ready (проекция, ADR-0055). Сделка функциональна без чата: при временном сбое provisioning messenger ретраит (eventual; при исчерпании — DLQ + алерт). Событие идемпотентно по DealId — повторная доставка не плодит второй чат.

Диаграмма

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

A1. Flow B — агент создаёт сделку по незапрошенному объекту (Draft → вовлечение застройщика)

Агент создаёт сделку по объекту, который инвестор не запрашивал (или намерение ещё не подтверждено), и/или одобренной фиксации пары «инвестор × застройщик» пока нет. Гейт (шаг 5) не выполнен.

  1. Создание в Draft. POST /api/agent/crm/deals. Если активного намерения по объекту нет — оно добавляется как AgentProposed (ConfirmedByInvestor = false, LeadEventType.BookingIntentAdded); сделка создаётся с Status = Draft, состав InvestorPrimary + Executor + авто-PinManager без SellManager (INV-DEAL-2). PaymentMethod при создании в Draft необязателен. TenantId уже задан (застройщик объекта), но сделка невидима застройщику: канбан/списки/статистика застройщика фильтруют Status != Draft. Публикуется DealCreatedEvent (Status = Draft) — messenger/notifications реагируют (чат агент+инвестор), developer-facing консюмеры игнорируют.
  2. Инвестор подтверждает намерение. Инвестор в своём ЛК подтверждает предложенный объект (confirm-booking-intent, ConfirmedByInvestor = true, LeadEventType.BookingIntentConfirmed).
  3. Одобренная фиксация. Для пары «инвестор × застройщик» есть одобренная фиксация (получена ранее или агент отправляет заявку и застройщик её одобряет, UC-FIX-002).
  4. Вовлечение застройщика (Draft → Application). Агент нажимает «Вовлечь застройщика» → POST /api/agent/crm/deals/{dealId}/engage-developer. Гейт INV-DEAL-11 проверяет одновременно: ConfirmedByInvestor = true (иначе CRM_DEAL_INTENT_NOT_CONFIRMED), одобренную фиксацию пары (иначе CRM_DEAL_FIXATION_REQUIRED), заданный PaymentMethod (на сделке или в запросе; иначе CRM_DEAL_PAYMENT_METHOD_REQUIRED). При успехе: Status: Draft → Application, в состав добавляется SellManager, проставляется IntentConfirmedAt, связывается FixationRequestId; таймлайн StageChanged (Draft → Application) + MemberAdded (SellManager); публикуются DealStageChangedEvent (Draft → Application) и DealMembersChangedEvent. С этого момента сделка видна застройщику и попадает на его канбан.
Обратного перехода нет

Обратного перехода Application → Draft нет. Способ вовлечения — отдельная операция engage-developer.md, не пересоздание сделки.

A2. Лид не в статусе Active

Лид не активен — сделка не создаётся
  1. Проверка находит Status ∈ {Expired, Archived}.
  2. CRM_LEAD_NOT_ACTIVE (409); сделка не создаётся.

Возврат: агент продлевает/реактивирует лид (INV-LEAD-3) или создаёт новый — затем повтор.

A3. Гейт выполнен, но способ оплаты не выбран (или Installment без пресета)

Способ оплаты обязателен только при выполненном гейте
  1. Сделка идёт сразу в Application (гейт выполнен), но PaymentMethod == nullCRM_DEAL_PAYMENT_METHOD_REQUIRED (422); либо Installment без InstallmentPresetIdCRM_DEAL_INSTALLMENT_PRESET_REQUIRED (422).
  2. Для создания в Draft (гейт не выполнен) PaymentMethod при создании не требуется — требование откладывается до вовлечения застройщика.

Возврат: форма требует выбрать способ оплаты (и пресет для рассрочки); повтор.

Постусловие

  • Flow A / успешное вовлечение (Flow B): создана/переведена DealEntity (Status = Application, StepGroup = Application, TenantId = DeveloperCompanyId объекта, PaymentMethod задан, Price — snapshot из chessboard, IntentId/IntentConfirmedAt/FixationRequestId заполнены) с участниками InvestorPrimary
    • Executor + SellManager + PinManager; сделка видна застройщику.
  • Flow B до вовлечения: DealEntity в Draft, состав InvestorPrimary + Executor + PinManager (без SellManager), PaymentMethod может быть null, сделка невидима застройщику (Status != Draft).
  • В таймлайне сделки — событие DealCreatedStageChanged/MemberAdded при вовлечении); в ленте лида — LeadEventEntity(DealCreated) (+ BookingIntentAdded/Confirmed в Flow B); лид остаётся Active.
  • В outbox — pin.crm.deal-created.v1 (создание) и pin.crm.deal-stage-changed.v1 + pin.crm.deal-members-changed.v1 (вовлечение); чат сделки создаётся консюмером messenger (eventual).

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

КодHTTPУсловиеПоведение клиента
CRM_LEAD_NOT_ACTIVE409Лид не Active (Expired/Archived) (INV-DEAL-1)Предложить продлить/реактивировать лид или создать новый
CRM_PROPERTY_OBJECT_NOT_AVAILABLE409Статус объекта ПроданВыбрать другой объект
CRM_DEAL_PAYMENT_METHOD_REQUIRED422Гейт выполнен (сделка идёт в Application), но PaymentMethod не задан (INV-DEAL-1/11)Форма требует выбрать способ оплаты
CRM_DEAL_INSTALLMENT_PRESET_REQUIRED422Installment без InstallmentPresetIdФорма требует выбрать программу рассрочки
CRM_DEAL_PIPELINE_NOT_CONFIGURED409Нет живого этапа группы Application у застройщикаОбратиться в поддержку; сделку создать нельзя
CRM_DEAL_INTENT_NOT_CONFIRMED409При вовлечении: намерение ConfirmedByInvestor = false (INV-DEAL-11)Дождаться подтверждения инвестором
CRM_DEAL_FIXATION_REQUIRED409При вовлечении: нет одобренной фиксации пары «инвестор × застройщик» (INV-DEAL-11)Дождаться одобрения фиксации застройщиком
CRM_DEAL_NOT_DRAFT409Вовлечение над не-Draft сделкой (застройщик уже вовлечён/терминал)Перечитать карточку

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

СобытиеКогда поднимаетсяSubscribers
DealCreatedСоздание сделки (Draft или Application)Тот же обработчик: запись DealTimelineEventEntity(DealCreated) и LeadEventEntity(DealCreated)
DealMemberAddedДобавление InvestorPrimary/Executor/PinManagerSellManager при Application), INV-DEAL-2Внутренние подписчики агрегата (аудит состава)
DealStatusChangedГейт Draft → Application (вовлечение застройщика)Внутренний обработчик — таймлайн StageChanged, сборка DealStageChangedEvent

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

СобытиеTopicКогда публикуетсяConsumers
DealCreatedEventpin.crm.deal-created.v1Создание сделки (Flow A — Application; Flow B — Draft), через outbox атомарноmessenger (создать чат + участники → deal-chat-status-changed), notifications (уведомить участников); developer-facing консюмеры реагируют только на Status != Draft — контракт ../events/deal-created.md
DealStageChangedEventpin.crm.deal-stage-changed.v1Вовлечение застройщика (Draft → Application)messenger, notifications; developer-facing проекции — контракт ../events/deal-stage-changed.md
DealMembersChangedEventpin.crm.deal-members-changed.v1Вовлечение застройщика (добавлен SellManager)messenger (синк участников чата), notifications — контракт ../events/deal-members-changed.md

Acceptance Criteria

#КритерийПроверка
AC1Flow A: лид Active, подтверждённое намерение, одобренная фиксация, задан PaymentMethod → сделка Application на канбане группы Application, состав InvestorPrimary+Executor+SellManager+PinManager, IntentId/IntentConfirmedAt/FixationRequestId заполненыИнтеграционный тест POST /api/agent/crm/deals → 201 + Status = Application
AC2TenantId сделки = DeveloperCompanyId владельца объекта из chessboard-репликиТест: Deal.TenantId совпадает с владельцем PropertyObjectId
AC3Flow B: нет подтверждённого намерения и/или одобренной фиксации → сделка Draft, состав InvestorPrimary+Executor+PinManager (без SellManager), PaymentMethod может отсутствовать, сделка невидима застройщикуТест: 201 + Status = Draft, developer-facing выборка не видит сделку
AC4Flow B вовлечение: намерение подтверждено + одобренная фиксация + PaymentMethodengage-developer переводит в Application, добавлен SellManager, проставлен IntentConfirmedAtТест POST /engage-developer → 200 + Status = Application
AC5Вовлечение при ConfirmedByInvestor = falseCRM_DEAL_INTENT_NOT_CONFIRMED (409); нет одобренной фиксации → CRM_DEAL_FIXATION_REQUIRED (409); статус остаётся DraftНегативные тесты гейта INV-DEAL-11
AC6Гейт выполнен, но PaymentMethod не задан → CRM_DEAL_PAYMENT_METHOD_REQUIRED (422); для Draft при создании PaymentMethod не требуетсяНегативный/позитивный тест валидации
AC7Лид Expired/ArchivedCRM_LEAD_NOT_ACTIVE (409), сделка не создаётсяНегативный тест
AC8Публикуется ровно одно pin.crm.deal-created.v1 с Members-snapshot (Status = Draft или Application); messenger доводит ChatStatus до Ready; при вовлечении — deal-stage-changed + deal-members-changedТест outbox + консюмер-контракт messenger
AC9В ленте лида появляется LeadEventEntity(DealCreated)BookingIntentAdded/Confirmed в Flow B); в таймлайне сделки — DealCreated; лид остаётся ActiveТест таймлайна и ленты; статус лида без изменений

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

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

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