UC-CRM-011. Лид с несколькими инвесторами: назначение основного инвестора
Назначение
Агент назначает основным инвестором (InvestorPrimary) одного из участников лида — когда лид ведут
несколько инвесторов (созаёмщики/соинвесторы — семейная пара, партнёры по покупке), нужен ровно один
«главный». Основной инвестор ровно один (INV-LEAD-7): он определяет уникальность активной пары «исполнитель ↔
основной инвестор» (INV-CRM-001) и является «шапкой» лида. Операция «поднимает» выбранного соинвестора
(InvestorSecondary) в InvestorPrimary, а прежний основной становится InvestorSecondary; производная лида
PrimaryInvestorUserId пересчитывается в том же изменении. Связан с контекстом crm. Дом полей —
lead (раздел «Участники лида»). Добавление/удаление самих соинвесторов —
UC-CRM-012.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-CRM-011 |
| Контекст | crm (см. docs/06-services/crm/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Агент-исполнитель / куратор лида (агентская роль в составе) / администратор-руководитель агентства / Менеджер ПИН |
| Permissions / scope | crm.leads.manage; scope — организация (агентство), record-level по членству (ADR-0071); руководитель — весь TenantId (ADR-0025); Менеджер ПИН — платформа |
| Tenant | TenantId = AgencyCompanyId; lead.TenantId обязан совпасть |
| Record-level | доступ = актор ∈ активные участники лида агентской роли (Executor/Responsible) или руководитель компании-владельца или Менеджер ПИН |
Предусловие
- Лид существует в
TenantIdактора иStatus = Active(INV-LEAD-2). - Целевой
UserId— активный инвестор-участник лида (InvestorPrimaryилиInvestorSecondary); если он ещё не в составе — сначала добавить его через UC-CRM-012. - Актор имеет доступ по членству/руководству/платформе (record-level, ADR-0071).
- У пары (текущий исполнитель, новый основной) нет другого активного лида (INV-CRM-001).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI-действие → вызов API |
| Триггерящий элемент | Действие «Сделать основным» у соинвестора в составе лида → POST /api/agent/crm/leads/{leadId}/primary-investor |
Основной Поток
- Контекст и доступ. Агент открывает карточку активного лида, где в составе несколько инвесторов
(
InvestorPrimary+ один или несколькоInvestorSecondary), и выбирает нового основного. - Ввод. Запрос несёт
UserId— целевого инвестора-участника. - Проверка входа.
LeadIdиUserIdзаданы (не пусто) — иначеVALIDATION_FAILED. - Проверки состояния, доступа и состава.
- Лид существует в
TenantId— иначеCRM_LEAD_NOT_FOUND. Status = Active(INV-LEAD-2) — иначеCRM_LEAD_NOT_ACTIVE.- Record-level (членство/руководство/Менеджер ПИН) — иначе
CRM_LEAD_FORBIDDEN. UserId— активный инвестор-участник лида (InvestorPrimary/InvestorSecondary) — иначеCRM_LEAD_PARTICIPANT_NOT_FOUND(не инвестор/не участник → сначала добавить).
- Лид существует в
- Идемпотентность цели. Если
UserIdуже активныйInvestorPrimary— no-op: состав и производные не меняются, событие не публикуется, возвращается текущий состав. - Guard активной пары (INV-CRM-001). Смена основного меняет пару (
ExecutorAgentUserId, новыйPrimaryInvestorUserId). Если у этой пары уже есть другой активный лид —CRM_LEAD_ACTIVE_PAIR_EXISTS(нельзя задвоить активную воронку пары). - Доменное решение (INV-LEAD-7; ровно один
InvestorPrimary). Атомарно: прежний активныйInvestorPrimary→ рольInvestorSecondary(IsPrimary = false); целевой участник →InvestorPrimary(IsPrimary = true). Пересчитывается производнаяPrimaryInvestorUserId = UserId. Исполнитель (ExecutorAgentUserId), куратор (ResponsibleAgentUserId) и агентский состав не меняются; статус лидаActive → Active; лид ни на миг не остаётся без основного инвестора. - Что меняется. Роли двух инвесторских участий, производная
PrimaryInvestorUserId, версия лида — в одном согласованном изменении вместе с записью в outbox. Членство инвесторов в лиде не меняется (менялась только роль). - Публикация. Интеграционное событие
pin.crm.lead-participants-changed.v1(ChangeType = RoleChangedдля обоих инвесторов) с полным активным составом и дельтой. - Результат. Возвращается актуальный состав лида с обновлённым основным (
PrimaryInvestorUserId = UserId). «Шапка» лида в списках и карточке обновляется сразу. - Асинхронно.
messengerреконсилирует состав чата лида (членство инвесторов не меняется — только их роль);notificationsуведомляет о смене основного клиента.
Диаграмма
Альтернативные Потоки
UserIdуже активныйInvestorPrimary.- Состав и производные не меняются, событие не публикуется; возвращается текущий состав
200.
UserIdне входит в активных инвесторов лида (агент, посторонний либо снятый участник).CRM_LEAD_PARTICIPANT_NOT_FOUND(404).
Возврат: сначала добавить инвестора в состав (UC-CRM-012), затем повторить.
- У пары (текущий исполнитель, новый основной) уже есть другой активный лид.
CRM_LEAD_ACTIVE_PAIR_EXISTS(409); состав не меняется.
Возврат: работать с существующим активным лидом пары либо выбрать иного основного.
- Лид
Expired/Archived. CRM_LEAD_NOT_ACTIVE(409); менять состав нельзя.
Возврат: продлить/реактивировать лид (INV-LEAD-3), затем повторить.
- Параллельная смена состава/передача ведения изменила версию лида.
CRM_LEAD_CONCURRENCY(409).
Возврат: перечитать лид и повторить.
Постусловие
- Целевой участник —
InvestorPrimary(IsPrimary = true); прежний основной —InvestorSecondary(IsPrimary = false); среди активных ровно одинInvestorPrimary(INV-LEAD-7). - Производная
PrimaryInvestorUserId = UserId; исполнитель и куратор не изменены; версия лида +1; статус остаётсяActive. - Членство инвесторов в лиде не менялось (менялась только роль); заявки на фиксацию и сделки лида не изменены; Origin-атрибуция не тронута.
- Опубликован
pin.crm.lead-participants-changed.v1(RoleChanged) с полным активным составом — кроме no-op.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
VALIDATION_FAILED | 400 | LeadId/UserId пусто | Показать ошибки полей |
CRM_LEAD_NOT_FOUND | 404 | Лид не найден по LeadId в tenant | Сообщить, что лид не найден |
CRM_LEAD_PARTICIPANT_NOT_FOUND | 404 | UserId — не активный инвестор-участник лида | Предложить сначала добавить инвестора в состав |
CRM_LEAD_NOT_ACTIVE | 409 | Лид Expired/Archived (INV-LEAD-2) | Предложить продлить/реактивировать лид |
CRM_LEAD_FORBIDDEN | 403 | Актор не участник агентской роли / не руководитель / не Менеджер ПИН | Скрыть действие «сделать основным» |
CRM_LEAD_ACTIVE_PAIR_EXISTS | 409 | У пары (исполнитель, новый основной) уже есть активный лид (INV-CRM-001) | Сообщить, что для пары уже есть активный лид |
CRM_LEAD_CONCURRENCY | 409 | Конфликт версии (параллельная смена состава/передача ведения) | Перечитать и повторить |
CRM_PRIMARY_INVESTOR_REQUIRED (INV-LEAD-7) — здесь не возникаетЭту ошибку операция поддерживает (всегда назначает единственного основного) — она возникает не здесь, а
при попытке снять активного InvestorPrimary без замены
(UC-CRM-012).
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
LeadParticipantsChanged | Смена ролей пары инвесторов (не в no-op) | Внутренний диспетчер → сборка LeadParticipantsChangedEvent |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
LeadParticipantsChangedEvent | pin.crm.lead-participants-changed.v1 | Смена основного инвестора (ChangeType = RoleChanged), через outbox атомарно | messenger (реконсиляция состава чата лида ChatKind.Lead — членство инвесторов не меняется), notifications — контракт lead-participants-changed |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Лид с InvestorPrimary A и InvestorSecondary B; назначают основным B → B — InvestorPrimary, A — InvestorSecondary, PrimaryInvestorUserId = B, версия +1, событие RoleChanged | Тест POST .../primary-investor → 200; проверить роли и производную |
| AC2 | Ровно один активный InvestorPrimary на всём протяжении (INV-LEAD-7) | Тест инварианта состава после смены |
| AC3 | Повтор с уже основным UserId → 200, состав без изменений, событие не публикуется | Тест идемпотентного no-op |
| AC4 | UserId не в составе инвесторов (агент/посторонний) → CRM_LEAD_PARTICIPANT_NOT_FOUND (404) | Негативный тест |
| AC5 | У пары (исполнитель, новый основной) уже есть активный лид → CRM_LEAD_ACTIVE_PAIR_EXISTS (409), состав без изменений | Негативный тест INV-CRM-001 |
| AC6 | Лид Expired/Archived → CRM_LEAD_NOT_ACTIVE; чужой актор → CRM_LEAD_FORBIDDEN | Негативные тесты состояния и доступа |
| AC7 | Исполнитель, куратор и членство инвесторов не изменены — менялась только роль | Тест: ExecutorAgentUserId/ResponsibleAgentUserId без изменений, строки участий те же |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (2): UC-CRM-010. Назначение/снятие ответственного (куратора) процесса — ответственный ≠ исполнитель, UC-CRM-012. Управление составом участников лида (добавление/снятие: второй агент, супруг/юрист инвестора)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: set-primary-investor; добавление/снятие соинвесторов — add-lead-participant, remove-lead-participant.
- Domain entity: lead (
LeadParticipantEntity,ParticipantRole, INV-LEAD-7, INV-CRM-001). - Kafka событие: lead-participants-changed.
- Смежные UC: UC-CRM-012, UC-CRM-010, UC-CRM-001; US-реестр — user-stories-deal.
- ADR: 0071 crm-ownership-and-participant-model, 0025 scoped-rbac, 0052 tenancy.