DELETE /api/agent/crm/leads/{leadId}/participants/{userId} — Снять участника лида
Soft-удаление участия в активном составе лида (LeadParticipantEntity.RemovedAt, ADR-0071): убирает
со-агента, ассистента, юриста, куратора или созаёмщика из ведения. Участие снимается «мягко» — строка
остаётся историей для чата лида и аудита, из активного состава (RemovedAt is null) участник исчезает,
доступ к лиду и его чату (ChatKind.Lead, messenger) снимается. Дом полей — lead.md
(раздел «Участники лида»).
Назначение
Дать агенту-исполнителю / куратору / руководителю агентства (или Менеджеру ПИН) сузить команду ведения лида.
Снятие публикует pin.crm.lead-participants-changed.v1 (ChangeType = Removed) с дельтой и полным активным
составом.
Операция не оставляет лид без ключевых ролей: единственного активного Executor снять нельзя (передача
ведения — атомарной сменой исполнителя, INV-LEAD-8), активного InvestorPrimary снять нельзя (сначала
назначить нового основного, set-primary-investor.md).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent (JWT агента/куратора/руководителя агентства/Менеджера ПИН) |
| Feature group | leads |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/remove-lead-participant.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент-исполнитель / куратор лида) / User (администратор-руководитель агентства) / User (Менеджер ПИН, platform-scope) |
| Auth policy / Permissions | crm.leads.manage |
| Scope (RBAC) | организация (агентство) — record-level по членству (ADR-0071); руководитель — весь TenantId (ADR-0025); Менеджер ПИН — платформа |
| Record-level | доступ = актор ∈ активные участники лида агентской роли (Executor/Responsible) или администратор/руководитель компании-владельца или Менеджер ПИН; иначе CRM_LEAD_FORBIDDEN |
| Tenant isolation | TenantId = AgencyCompanyId в каждом запросе; lead.TenantId обязан совпасть |
| Audit | снятие участия (RemovedByUserId = actor, RemovedAt), Version лида |
Выполнить может только активный участник лида агентской роли (Executor/Responsible), администратор/
руководитель компании-владельца лида (AgencyCompanyId = TenantId) или Менеджер ПИН — иначе
CRM_LEAD_FORBIDDEN.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | DELETE |
| Route | /api/agent/crm/leads/{leadId}/participants/{userId} |
| Success status | 200 (участие снято, возвращается актуальный состав) |
| Idempotency header | не применяется — DELETE семантически идемпотентен; повтор для уже снятого участника → CRM_LEAD_PARTICIPANT_NOT_FOUND |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required; = AgencyCompanyId | Tenant isolation |
ActorUserId | identity claims | Guid | Да | Required | Record-level (членство) |
LeadId | route | Guid | Да | != Guid.Empty; существующий лид | — |
UserId | route | Guid | Да | != Guid.Empty; активный участник лида | — |
Модель ответа LeadParticipantsModel
Актуальный активный состав лида после снятия (та же проекция, что у add-lead-participant.md).
| Поле | Тип | Обязательность | Источник | Описание |
|---|---|---|---|---|
LeadId | Guid | Да | lead.Id | Идентификатор лида. |
Participants | List<LeadParticipantModel> | Да | активные LeadParticipantEntity (RemovedAt is null) | Полный активный состав после снятия. |
ExecutorAgentUserId | Guid | Да | lead.ExecutorAgentUserId | Текущий исполнитель (не меняется этой операцией). |
ResponsibleAgentUserId | Guid? | Нет | lead.ResponsibleAgentUserId | Куратор (сбрасывается в null, если снят Responsible). |
PrimaryInvestorUserId | Guid | Да | lead.PrimaryInvestorUserId | Основной инвестор (не меняется этой операцией). |
Version | long | Да | lead.Version | Для оптимистичной блокировки на клиенте. |
LeadParticipantModel — { UserId, Role, IsPrimary } (см. add-lead-participant.md).
Алгоритм
- Контекст и доступ. Операцию выполняет участник лида агентской роли / руководитель агентства / Менеджер
ПИН (permission
crm.leads.manage) в рамкахTenantId. - Проверка входа и предусловий.
- формат:
LeadId != Guid.Empty,UserId != Guid.Empty; - лид существует в
TenantId— иначеCRM_LEAD_NOT_FOUND; - состояние:
lead.Status == Active(INV-LEAD-2) — иначеCRM_LEAD_NOT_ACTIVE; - record-level (членство, ADR-0071) — иначе
CRM_LEAD_FORBIDDEN; UserId— активный участник лида (RemovedAt is null) — иначеCRM_LEAD_PARTICIPANT_NOT_FOUND.
- формат:
- Guard ключевых ролей (INV-LEAD-7/8).
- снимаемый участник — активный
Executor→ отказCRM_LEAD_EXECUTOR_REQUIRED: лид нельзя оставить без исполнителя, замена — только атомарной передачей ведения; - снимаемый участник — активный
InvestorPrimary→ отказCRM_PRIMARY_INVESTOR_REQUIRED: сначала назначить нового основного инвестора (set-primary-investor.md), затем снять.
- снимаемый участник — активный
- Что читаем. Лид с активным составом (строка лида блокируется от гонки со сменой состава/передачей ведения).
- Бизнес-правила и переходы. У активного участия проставляются
RemovedAt = now,RemovedByUserId = actor(soft-remove). Производные лида не меняются, кромеResponsibleAgentUserId(при снятии активногоResponsible→null). Статус лида:Active → Active. Заявки на фиксацию и сделки лида не затрагиваются. - Что меняется. Участие переходит в снятое (
RemovedAt != null), обновляютсяUpdatedAt/Versionлида (иResponsibleAgentUserIdпри снятии куратора) — атомарно;RowVersion→ при конфликтеCRM_LEAD_CONCURRENCY. - События. Публикуется
pin.crm.lead-participants-changed.v1(ChangeType = Removed) атомарно: дельта снятого участника + полный активный состав. Консюмеры:messenger(убирает участника из чата лида, история ему сохраняется),notifications(уведомляет о смене состава). - Результат. Актуальный состав лида → HTTP
200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | pin.crm.lead-participants-changed.v1 (ChangeType = Removed) — контракт lead-participants-changed.md |
| Внешние вызовы | none |
| Проекции / поиск | обновление членства → пересчёт видимости лида (доступ снятого участника закрывается) |
| Уведомления | notifications уведомляет о смене состава (по интеграционному событию) |
Ошибки и failure modes
Главный гейт — INV-LEAD-7/8: снять единственного активного Executor или активного InvestorPrimary
нельзя. Для исполнителя — только атомарная передача ведения; для основного инвестора — сначала назначить
нового (set-primary-investor.md), затем снимать прежнего.
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | LeadId/UserId = Guid.Empty | «Проверьте корректность полей» | no retry |
CRM_LEAD_NOT_FOUND | 404 | лид не найден по LeadId в tenant | «Лид не найден» | no retry |
CRM_LEAD_PARTICIPANT_NOT_FOUND | 404 | UserId не активный участник лида (нет/уже снят) | «Участник не найден» | no retry |
CRM_LEAD_NOT_ACTIVE | 409 | Status != Active (INV-LEAD-2) | «Менять состав можно только у активного лида» | no retry |
CRM_LEAD_FORBIDDEN | 403 | актор не участник агентской роли / не руководитель / не Менеджер ПИН | «Недостаточно прав на изменение состава лида» | no retry |
CRM_LEAD_EXECUTOR_REQUIRED | 409 | снимается единственный активный Executor (INV-LEAD-8) | «Нельзя оставить лид без исполнителя — передайте ведение» | no retry (использовать передачу ведения) |
CRM_PRIMARY_INVESTOR_REQUIRED | 409 | снимается активный InvestorPrimary (INV-LEAD-7) | «Сначала назначьте другого основного инвестора» | no retry (назначить основного) |
CRM_LEAD_CONCURRENCY | 409 | конфликт RowVersion (параллельная смена состава/передача ведения) | «Лид изменён, обновите и повторите» | retry (перечитать) |
DELETE идемпотентен семантически: повторный вызов для уже снятого (или никогда не бывшего) участника —
не ошибка данных, а 404 CRM_LEAD_PARTICIPANT_NOT_FOUND (состав уже в целевом состоянии).
Предусловия и постусловия
Предусловия (истинны до сценария):
- актор авторизован, имеет permission
crm.leads.manage, является участником агентской роли лида / руководителем агентства / Менеджером ПИН; - лид существует в
TenantIdактора и находится в статусеActive(INV-LEAD-2); UserId— активный участник лида, не являющийся единственнымExecutorи не являющийсяInvestorPrimary.
Постусловия (гарантированы после успеха):
- участие
UserIdпереведено в снятое (RemovedAt != null,RemovedByUserId = actor), из активного состава исчезло; строка сохранена как история (чат/аудит); - при снятии активного
ResponsibleпроизводнаяResponsibleAgentUserId=null;ExecutorAgentUserIdиPrimaryInvestorUserIdне изменены; Versionлида инкрементирован, статус остаётсяActive;- опубликован
pin.crm.lead-participants-changed.v1(Removed) с полным активным составом;messengerснимает доступ бывшего участника (история ему сохраняется),notificationsуведомляет о смене состава; - заявки на фиксацию и сделки лида не изменены; Origin-атрибуция не тронута.
Acceptance Criteria
- Happy path. Дано: активный лид с со-агентом, актор — исполнитель. Когда: снимает со-агента. Тогда:
200, участие в снятом состоянии,Version+1, опубликованlead-participants-changed(Removed), доступ со-агента к лиду и чату снят. - Снятие куратора. Дано: активный
Responsible. Когда: его снимают. Тогда:200,ResponsibleAgentUserId = null. - Нельзя снять исполнителя. Дано: единственный активный
Executor. Когда: попытка снять. Тогда:409 CRM_LEAD_EXECUTOR_REQUIRED, состав не меняется (снятие — только атомарной передачей ведения). - Нельзя снять основного инвестора. Дано: активный
InvestorPrimary. Когда: попытка снять. Тогда:409 CRM_PRIMARY_INVESTOR_REQUIRED; сначала назначить нового основного, затем снять. - Уже снят / не участник. Дано:
UserIdне в активном составе. Когда: снятие. Тогда:404 CRM_LEAD_PARTICIPANT_NOT_FOUND(идемпотентность повторного вызова). - Чужой лид. Дано: актор не участник агентской роли / не руководитель / не Менеджер ПИН. Когда: снятие.
Тогда:
403 CRM_LEAD_FORBIDDEN. - Лид не активен. Дано: лид
Expired/Archived. Когда: снятие. Тогда:409 CRM_LEAD_NOT_ACTIVE. - Конкурентная смена состава. Дано: состав параллельно изменён. Когда: отправка с устаревшей версией.
Тогда:
409 CRM_LEAD_CONCURRENCY.
Совместимость и наблюдаемость
- Soft-remove сохраняет историю участия для чата и аудита; активный состав считается по
RemovedAt is null.ParticipantRole— additive (неизвестное значение у потребителя — строкой + warning). - Logs:
lead-participant removed(LeadId, tenant, actor,Role;UserId— только id); metrics:crm_lead_participant_remove_total{result,role}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): POST /api/agent/crm/leads/{leadId}/participants — Добавить участника лида, POST /api/agent/crm/leads/{leadId}/primary-investor — Назначить основного инвестора лида
- Use Cases (3): UC-CRM-011. Лид с несколькими инвесторами: назначение основного инвестора, UC-CRM-012. Управление составом участников лида (добавление/снятие: второй агент, супруг/юрист инвестора), US-реестр «Участники и владение процессами» (US-CRM-PART / US-AG-ADMIN / US-AGT-MULTI / US-INV-CO)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность-дом: lead.md (
LeadParticipantEntity, INV-LEAD-7/8). - Смежные операции состава: add-lead-participant.md, set-primary-investor.md.
- Событие: lead-participants-changed.md.
- ADR-0071 (владение процессами, видимость по составу), ADR-0072 (каскад увольнения агента — массовый soft-remove участий), ADR-0055 (чат лида).