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

POST /api/agent/crm/leads/{leadId}/participants — Добавить участника лида

Добавление участника в активный состав лида (LeadParticipantEntity, единый ParticipantRole, ADR-0071): со-агент, ассистент, юрист, куратор (Responsible), созаёмщик/соинвестор (InvestorSecondary). Видимость лида и синк участников чата лида (ChatKind.Lead, messenger) строятся на этом составе, поэтому добавление участника открывает ему доступ к лиду и его чату. Дом полей — lead.md (раздел «Участники лида», LeadParticipantEntity).

Назначение

Дать агенту-исполнителю / куратору / руководителю агентства (или Менеджеру ПИН) расширить команду ведения лида, добавив участника с одной из ролей состава. Responsible — сингулярная опциональная роль: назначается, если куратора ещё нет. Добавление публикует pin.crm.lead-participants-changed.v1 с дельтой и полным активным составом.

Ограничения

Операция не назначает исполнителя и не меняет основного инвестора: сингулярные роли Executor и InvestorPrimary через это добавление не заводятся (передача ведения — атомарной сменой исполнителя; основной инвестор — set-primary-investor.md).

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent (JWT агента/куратора/руководителя агентства/Менеджера ПИН)
Feature groupleads
Статусdraft
Документdocs/06-services/crm/api/agent/add-lead-participant.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (агент-исполнитель / куратор лида) / User (администратор-руководитель агентства) / User (Менеджер ПИН, platform-scope)
Auth policy / Permissionscrm.leads.manage
Scope (RBAC)организация (агентство) — record-level по членству (ADR-0071); руководитель — весь TenantId в scope-иерархии (ADR-0025); Менеджер ПИН — платформа
Record-levelдоступ = актор ∈ активные участники лида агентской роли (Executor/Responsible) или администратор/руководитель компании-владельца (AgencyCompanyId = TenantId) или Менеджер ПИН; иначе CRM_LEAD_FORBIDDEN
Tenant isolationTenantId = AgencyCompanyId в каждом запросе; lead.TenantId обязан совпасть
Auditдобавление участия (AddedByUserId = actor, CreatedAt), Version лида
Доступ

Выполнить может только активный участник лида агентской роли (Executor/Responsible), администратор/ руководитель компании-владельца лида (AgencyCompanyId = TenantId) или Менеджер ПИН — иначе CRM_LEAD_FORBIDDEN.

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/agent/crm/leads/{leadId}/participants
Success status201 (участник добавлен, возвращается актуальный состав)
Idempotency headerне применяется — бизнес-дедуп по паре (LeadId,UserId) среди активных (INV-LEAD-7)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = AgencyCompanyIdTenant isolation
ActorUserIdidentity claimsGuidДаRequiredRecord-level (членство)
LeadIdrouteGuidДа!= Guid.Empty; существующий лид
UserIdbodyGuidДа!= Guid.Empty; существующий (в т.ч. lazy) пользователь identity; не совпадает с активным участником лида
RolebodyParticipantRole (enum-строка)Даодно из значений, применимых к лиду: CoAgent / Assistant / Lawyer / Responsible / InvestorSecondary; Executor/InvestorPrimary/SellManager/PinManager — отклоняются (см. ошибки)

Модель ответа LeadParticipantsModel

Read-проекция активного состава лида после изменения (дом полей — lead.md); здесь — только релевантная операции дельта. PII нет — только id и роли.

ПолеТипОбязательностьИсточникОписание
LeadIdGuidДаlead.IdИдентификатор лида.
ParticipantsList<LeadParticipantModel>Даактивные LeadParticipantEntity (RemovedAt is null)Полный активный состав после добавления.
ExecutorAgentUserIdGuidДаlead.ExecutorAgentUserIdТекущий исполнитель (не меняется этой операцией).
ResponsibleAgentUserIdGuid?Нетlead.ResponsibleAgentUserIdКуратор (может появиться, если добавлен Responsible).
PrimaryInvestorUserIdGuidДаlead.PrimaryInvestorUserIdОсновной инвестор (не меняется этой операцией).
VersionlongДаlead.VersionДля оптимистичной блокировки на клиенте.

LeadParticipantModel

ПолеТипОбязательностьОписание
UserIdGuidДаПользователь-участник.
RoleParticipantRole (enum-строка)ДаРоль в составе.
IsPrimaryboolДаПризнак «основной» в своей роли (для InvestorPrimary).

Алгоритм

  1. Контекст и доступ. Операцию выполняет участник лида агентской роли / руководитель агентства / Менеджер ПИН (permission crm.leads.manage) в рамках TenantId.
  2. Проверка входа и предусловий.
    • формат: LeadId != Guid.Empty, UserId != Guid.Empty; Role — известное значение, применимое к лиду;
    • лид существует в TenantId — иначе CRM_LEAD_NOT_FOUND;
    • состояние: lead.Status == Active (INV-LEAD-2) — иначе CRM_LEAD_NOT_ACTIVE;
    • record-level (членство, ADR-0071): актор ∈ активные участники агентской роли или руководитель TenantId или Менеджер ПИН — иначе CRM_LEAD_FORBIDDEN;
    • роль допустима к добавлению: Executor/InvestorPrimary не заводятся здесь (сингулярные ключевые роли), SellManager/PinManager неприменимы к лиду — иначе CRM_LEAD_PARTICIPANT_ROLE_NOT_ALLOWED.
  3. Проверка состава (INV-LEAD-7).
    • UserId не должен уже быть активным участником лида — иначе CRM_LEAD_PARTICIPANT_DUPLICATE;
    • для сингулярной роли Responsible: если активный Responsible уже есть — CRM_LEAD_PARTICIPANT_ROLE_CONFLICT (сменить куратора — снять текущего и добавить нового).
  4. Что читаем. Лид с активным составом участников (строка лида блокируется на время правки от гонки со сменой состава/передачей ведения). Пользователь identity не подгружается — проверяется по логической ссылке.
  5. Бизнес-правила и переходы. Добавляется активное участие LeadParticipantEntity { UserId, Role, IsPrimary = false, AddedByUserId = actor }. Производные лида не меняются, кроме ResponsibleAgentUserId (при Role = Responsible — назначается добавленным). Статус лида: Active → Active. Заявки на фиксацию и сделки лида не затрагиваются.
  6. Что меняется. Появляется строка crm.lead_participants (активная), обновляются UpdatedAt/Version лида (и ResponsibleAgentUserId при добавлении куратора) — атомарно; RowVersion даёт оптимистичную блокировку → при конфликте CRM_LEAD_CONCURRENCY.
  7. События. Публикуется pin.crm.lead-participants-changed.v1 (ChangeType = Added) атомарно с мутацией состава: дельта добавленного участника + полный активный состав. Консюмеры: messenger (приглашает участника в чат лида ChatKind.Lead), notifications (уведомляет добавленного).
  8. Результат. Актуальный состав лида → HTTP 201.

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияpin.crm.lead-participants-changed.v1 (ChangeType = Added) — контракт lead-participants-changed.md
Внешние вызовыnone (пользователь проверяется по логической ссылке identity, без синхронного запроса)
Проекции / поискобновление членства → пересчёт видимости лида (доступ добавленного участника)
Уведомленияnotifications уведомляет добавленного участника (по интеграционному событию)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400LeadId/UserId = Guid.Empty, неизвестное значение Role«Проверьте корректность полей»no retry
CRM_LEAD_NOT_FOUND404лид не найден по LeadId в tenant«Лид не найден»no retry
CRM_LEAD_NOT_ACTIVE409Status != Active (INV-LEAD-2)«Менять состав можно только у активного лида»no retry
CRM_LEAD_FORBIDDEN403актор не участник агентской роли / не руководитель / не Менеджер ПИН«Недостаточно прав на изменение состава лида»no retry
CRM_LEAD_PARTICIPANT_ROLE_NOT_ALLOWED422роль Executor/InvestorPrimary (сингулярная ключевая) или SellManager/PinManager (неприменима к лиду)«Эту роль нельзя добавить в состав лида»no retry
CRM_LEAD_PARTICIPANT_DUPLICATE409UserId уже активный участник лида (INV-LEAD-7)«Пользователь уже участник лида»no retry
CRM_LEAD_PARTICIPANT_ROLE_CONFLICT409добавляется второй Responsible при существующем активном (INV-LEAD-7)«Куратор уже назначен»no retry
CRM_LEAD_CONCURRENCY409конфликт RowVersion (параллельная смена состава/передача ведения)«Лид изменён, обновите и повторите»retry (перечитать)

Предусловия и постусловия

Предусловия (истинны до сценария):

  • актор авторизован, имеет permission crm.leads.manage, является участником агентской роли лида / руководителем агентства / Менеджером ПИН;
  • лид существует в TenantId актора и находится в статусе Active (INV-LEAD-2);
  • добавляемый UserId — зарегистрированный (в т.ч. lazy) пользователь identity, ещё не активный участник этого лида.

Постусловия (гарантированы после успеха):

  • в crm.lead_participants появилась активная строка {UserId, Role, IsPrimary = false, RemovedAt = null};
  • при Role = Responsible производная ResponsibleAgentUserId = добавленный участник; иные производные лида (ExecutorAgentUserId, PrimaryInvestorUserId) не изменены;
  • Version лида инкрементирован, статус остаётся Active;
  • опубликован pin.crm.lead-participants-changed.v1 (Added) с полным активным составом; messenger добавляет участника в чат лида, notifications уведомляет его;
  • заявки на фиксацию и сделки лида не изменены; Origin-атрибуция (TenantId/OriginAgentUserId) не тронута.

Acceptance Criteria

  • Happy path (со-агент). Дано: активный лид, актор — исполнитель. Когда: добавляет UserId с ролью CoAgent. Тогда: 201, участник активен, Version +1, опубликован lead-participants-changed (Added), участник получил доступ к лиду и его чату.
  • Куратор при отсутствии. Дано: у лида нет активного Responsible. Когда: добавляют Responsible. Тогда: 201, ResponsibleAgentUserId = добавленный.
  • Куратор уже есть. Дано: активный Responsible уже назначен. Когда: добавляют второго Responsible. Тогда: 409 CRM_LEAD_PARTICIPANT_ROLE_CONFLICT, состав не меняется.
  • Дубль участника. Дано: UserId уже активный участник. Когда: повторное добавление. Тогда: 409 CRM_LEAD_PARTICIPANT_DUPLICATE.
  • Запрещённая роль. Дано: попытка добавить Executor или InvestorPrimary. Когда: отправка. Тогда: 422 CRM_LEAD_PARTICIPANT_ROLE_NOT_ALLOWED (исполнитель — через передачу ведения; основной инвестор — через set-primary-investor.md).
  • Чужой лид. Дано: актор не участник агентской роли и не руководитель/Менеджер ПИН. Когда: добавление. Тогда: 403 CRM_LEAD_FORBIDDEN.
  • Лид не активен. Дано: лид Expired/Archived. Когда: добавление. Тогда: 409 CRM_LEAD_NOT_ACTIVE.
  • Конкурентная смена состава. Дано: состав параллельно изменён. Когда: отправка с устаревшей версией. Тогда: 409 CRM_LEAD_CONCURRENCY; клиент перечитывает и повторяет.

Совместимость и наблюдаемость

  • ParticipantRole — additive: новое значение роли у потребителя события трактуется строкой + warning (не падение). Набор ролей, допустимых к добавлению в лид, расширяется additive; сужение — breaking.
  • Logs: lead-participant added (LeadId, tenant, actor, Role; UserId — только id); metrics: crm_lead_participant_add_total{result,role}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

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

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

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

  • Сущность-дом: lead.md (LeadParticipantEntity, ParticipantRole, INV-LEAD-2/7/8).
  • Смежные операции состава: remove-lead-participant.md, set-primary-investor.md.
  • Событие: lead-participants-changed.md; аналог на сделке — deal.md (единая модель участников).
  • ADR-0071 (владение процессами, ParticipantRole, видимость по составу), ADR-0025 (scope-иерархия), ADR-0055 (мессенджер, чат лида).