UC-CRM-002. Агент создаёт сделку из лида (выбор объекта, гейт вовлечения застройщика)
Назначение
Агент из активного лида создаёт сделку DealEntity по объекту недвижимости. Сделка попадает в один из двух
статусов по гейту вовлечения застройщика: если для пары «инвестор × застройщик» есть одобренная фиксация
(../domain/fixation-request.md) и объект — подтверждённое инвестором
намерение (../domain/lead.md) — сразу Application
(застройщик вовлечён, SellManager в составе); иначе — Draft (сделка агента с инвестором, застройщик ещё не
вовлечён). Одобрение фиксации сделку не создаёт, а авторизует заводить сколько угодно сделок по объектам
застройщика (связь фиксация→сделки 1:N). Дом полей сделки — ../domain/deal.md; здесь
описаны только поток и его дельта.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-CRM-002 |
| Контекст | crm (см. docs/06-services/crm/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Агент (владелец лида) или Менеджер ПИН |
| Permissions / scope | crm.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, enumLeadStatus; 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 и её довод до Application
— Flow B в альтернативных потоках A1.
- Контекст и доступ. Агент открывает карточку лида (
Status = Active) и запускает создание сделки по объекту, к которому инвестор ранее добавил и подтвердил намерение (../domain/lead.md,Source = InvestorAdded). - Ввод. Форма: выбор объекта
PropertyObjectId(витрина застройщикаchessboard/catalog) и способа оплатыPaymentMethod; приInstallment— выборInstallmentPresetId. Модель запросаCreateDealRequest { LeadId, PropertyObjectId, PaymentMethod?, InstallmentPresetId?, Title? }. - Проверка входа.
LeadId/PropertyObjectIdзаданы; приInstallmentзаданInstallmentPresetId— иначеCRM_DEAL_INSTALLMENT_PRESET_REQUIRED;Title≤ 256. - Проверки состояния и связей (сначала лид-основание для инварианта):
- Лид существует и
Status = Active— иначеCRM_LEAD_NOT_ACTIVE; record-level: актор — активный участник-исполнитель лида либо Менеджер ПИН. - Из
chessboard-реплики поPropertyObjectId— владелецDeveloperCompanyId(→TenantId),ProjectId,BuildingId?, цена (snapshotMoney), статус; статусПродан→CRM_PROPERTY_OBJECT_NOT_AVAILABLE. - Активное намерение инвестора по объекту (пара
LeadId × PropertyObjectId) и одобренная фиксация пары «инвестор × застройщик». - При
Installment— пресет существует и принадлежитTenantIdзастройщика. - Живой SEED-этап группы
Applicationтого же застройщика — иначеCRM_DEAL_PIPELINE_NOT_CONFIGURED.
- Лид существует и
- Вычисление гейта вовлечения застройщика (INV-DEAL-11). Гейт выполнен, когда одновременно: объект —
подтверждённое инвестором намерение (
ConfirmedByInvestor = true) И для пары «инвестор × застройщик» есть одобренная фиксация. В Flow A гейт выполнен → целевой статусApplication, и тогдаPaymentMethodобязателен (иначеCRM_DEAL_PAYMENT_METHOD_REQUIRED, INV-DEAL-1). - Бизнес-правила и маппинг. Создаётся сделка:
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. - Что меняется. Создаётся сделка (+ дочерние
DealMemberEntity), пишется таймлайнDealTimelineEventEntity(DealCreated)и запись ленты лидаLeadEventEntity(DealCreated)(../domain/lead.md, payloadLeadDealRefPayload), обновляетсяLastEventAt. Всё — в одном согласованном изменении вместе с записью в outbox. - Публикация. Интеграционное событие
DealCreatedEvent(топикpin.crm.deal-created.v1) собирается из полей агрегата и активного составаMembers, публикуется через outbox. - Результат. Возвращаются
DealId,Status = Application,StepId,Title, состав участников. Фронт переходит на карточку сделки; сделка появляется на канбане/crmзастройщика в колонке группыApplication. - Асинхронно. Консюмер
messengerпоpin.crm.deal-created.v1создаёт чат сделки и синкает участников;Deal.ChatStatus:Pending → Provisioning → Ready(проекция, ADR-0055). Сделка функциональна без чата: при временном сбое provisioningmessengerретраит (eventual; при исчерпании — DLQ + алерт). Событие идемпотентно поDealId— повторная доставка не плодит второй чат.
Диаграмма
Альтернативные Потоки
A1. Flow B — агент создаёт сделку по незапрошенному объекту (Draft → вовлечение застройщика)
Агент создаёт сделку по объекту, который инвестор не запрашивал (или намерение ещё не подтверждено), и/или одобренной фиксации пары «инвестор × застройщик» пока нет. Гейт (шаг 5) не выполнен.
- Создание в
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 консюмеры игнорируют. - Инвестор подтверждает намерение. Инвестор в своём ЛК подтверждает предложенный объект
(
confirm-booking-intent,ConfirmedByInvestor = true,LeadEventType.BookingIntentConfirmed). - Одобренная фиксация. Для пары «инвестор × застройщик» есть одобренная фиксация (получена ранее или агент отправляет заявку и застройщик её одобряет, UC-FIX-002).
- Вовлечение застройщика (
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
- Проверка находит
Status ∈ {Expired, Archived}. CRM_LEAD_NOT_ACTIVE(409); сделка не создаётся.
Возврат: агент продлевает/реактивирует лид (INV-LEAD-3) или создаёт новый — затем повтор.
A3. Гейт выполнен, но способ оплаты не выбран (или Installment без пресета)
- Сделка идёт сразу в
Application(гейт выполнен), ноPaymentMethod == null—CRM_DEAL_PAYMENT_METHOD_REQUIRED(422); либоInstallmentбезInstallmentPresetId—CRM_DEAL_INSTALLMENT_PRESET_REQUIRED(422). - Для создания в
Draft(гейт не выполнен)PaymentMethodпри создании не требуется — требование откладывается до вовлечения застройщика.
Возврат: форма требует выбрать способ оплаты (и пресет для рассрочки); повтор.
Постусловие
- Flow A / успешное вовлечение (Flow B): создана/переведена
DealEntity(Status = Application,StepGroup = Application,TenantId = DeveloperCompanyIdобъекта,PaymentMethodзадан,Price— snapshot изchessboard,IntentId/IntentConfirmedAt/FixationRequestIdзаполнены) с участникамиInvestorPrimaryExecutor+SellManager+PinManager; сделка видна застройщику.
- Flow B до вовлечения:
DealEntityвDraft, составInvestorPrimary+Executor+PinManager(безSellManager),PaymentMethodможет бытьnull, сделка невидима застройщику (Status != Draft). - В таймлайне сделки — событие
DealCreated(иStageChanged/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_ACTIVE | 409 | Лид не Active (Expired/Archived) (INV-DEAL-1) | Предложить продлить/реактивировать лид или создать новый |
CRM_PROPERTY_OBJECT_NOT_AVAILABLE | 409 | Статус объекта Продан | Выбрать другой объект |
CRM_DEAL_PAYMENT_METHOD_REQUIRED | 422 | Гейт выполнен (сделка идёт в Application), но PaymentMethod не задан (INV-DEAL-1/11) | Форма требует выбрать способ оплаты |
CRM_DEAL_INSTALLMENT_PRESET_REQUIRED | 422 | Installment без InstallmentPresetId | Форма требует выбрать программу рассрочки |
CRM_DEAL_PIPELINE_NOT_CONFIGURED | 409 | Нет живого этапа группы Application у застройщика | Обратиться в поддержку; сделку создать нельзя |
CRM_DEAL_INTENT_NOT_CONFIRMED | 409 | При вовлечении: намерение ConfirmedByInvestor = false (INV-DEAL-11) | Дождаться подтверждения инвестором |
CRM_DEAL_FIXATION_REQUIRED | 409 | При вовлечении: нет одобренной фиксации пары «инвестор × застройщик» (INV-DEAL-11) | Дождаться одобрения фиксации застройщиком |
CRM_DEAL_NOT_DRAFT | 409 | Вовлечение над не-Draft сделкой (застройщик уже вовлечён/терминал) | Перечитать карточку |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
DealCreated | Создание сделки (Draft или Application) | Тот же обработчик: запись DealTimelineEventEntity(DealCreated) и LeadEventEntity(DealCreated) |
DealMemberAdded | Добавление InvestorPrimary/Executor/PinManager (и SellManager при Application), INV-DEAL-2 | Внутренние подписчики агрегата (аудит состава) |
DealStatusChanged | Гейт Draft → Application (вовлечение застройщика) | Внутренний обработчик — таймлайн StageChanged, сборка DealStageChangedEvent |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
DealCreatedEvent | pin.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 |
DealStageChangedEvent | pin.crm.deal-stage-changed.v1 | Вовлечение застройщика (Draft → Application) | messenger, notifications; developer-facing проекции — контракт ../events/deal-stage-changed.md |
DealMembersChangedEvent | pin.crm.deal-members-changed.v1 | Вовлечение застройщика (добавлен SellManager) | messenger (синк участников чата), notifications — контракт ../events/deal-members-changed.md |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Flow A: лид Active, подтверждённое намерение, одобренная фиксация, задан PaymentMethod → сделка Application на канбане группы Application, состав InvestorPrimary+Executor+SellManager+PinManager, IntentId/IntentConfirmedAt/FixationRequestId заполнены | Интеграционный тест POST /api/agent/crm/deals → 201 + Status = Application |
| AC2 | TenantId сделки = DeveloperCompanyId владельца объекта из chessboard-реплики | Тест: Deal.TenantId совпадает с владельцем PropertyObjectId |
| AC3 | Flow B: нет подтверждённого намерения и/или одобренной фиксации → сделка Draft, состав InvestorPrimary+Executor+PinManager (без SellManager), PaymentMethod может отсутствовать, сделка невидима застройщику | Тест: 201 + Status = Draft, developer-facing выборка не видит сделку |
| AC4 | Flow B вовлечение: намерение подтверждено + одобренная фиксация + PaymentMethod → engage-developer переводит в Application, добавлен SellManager, проставлен IntentConfirmedAt | Тест POST /engage-developer → 200 + Status = Application |
| AC5 | Вовлечение при ConfirmedByInvestor = false → CRM_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/Archived → CRM_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 | Тест таймлайна и ленты; статус лида без изменений |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (11): UC-BKG-001. Инвестор инициирует бронирование с витрины, UC-BKG-002. Бронирование Резерв → Бронь, UC-CRM-003. Движение сделки по канбан-доске, UC-CRM-004. Документы сделки: запрос → загрузка → проверка → статусы, UC-CRM-005. Оформление сделки: договоры → эскроу → Росреестр → завершение, UC-CRM-014. Агент выбирает активное агентство перед созданием лида/сделки, UC-CRM-015. Явное назначение исполнителя для разрешения спора о комиссии, UC-CRM-016. Инвестор добавляет намерения бронирования (много объектов), UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика, UC-CRM-018. Несколько сделок из одной одобренной фиксации (по разным объектам застройщика), UC-FIX-002. Застройщик одобряет фиксацию (авторизация работы с клиентом, без создания сделки)
- События (1): Event Contract: pin.crm.lead-booking-intent-changed (LeadBookingIntentChangedEvent)
- scenarios (2): UC-OVR-004. От реферальной ссылки до объекта у собственника, UC-OVR-005. От лида до сделки (4 фазы)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API методы: create-deal.md (создание,
Draft/Applicationпо гейту), engage-developer.md (Draft → Application); предпросмотр графика рассрочки — ../api/public/get-payment-schedule.md. - Domain entity: ../domain/deal.md (дом полей,
DealStatus.Draft, INV-DEAL-1/2/10/11), ../domain/lead.md (намерение по объекту,ConfirmedByInvestor,InvestorAdded/AgentProposed), ../domain/lead.md (LeadEventType.DealCreated/BookingIntentAdded/Confirmed), ../domain/purchase-program.md (enumPaymentMethod, пресеты), ../domain/pipeline-step.md (группаApplication). - Kafka события: ../events/deal-created.md, ../events/deal-stage-changed.md, ../events/deal-members-changed.md.
- Авторизация работы с клиентом: UC-FIX-002 (одобрение фиксации — без авто-создания сделки); US-реестр — user-stories-deal.md (US-AGT-01).
- ADR: 0052 tenancy, 0055 мини-мессенджер, 0056 simplicity-first, 0071 company-ownership и участники.