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

DELETE /api/agent/crm/deals/{dealId}/members/{memberId} — Удалить участника сделки

Агент (или руководитель агентства / Менеджер ПИН) снимает участника со сделки: со-агента, ассистента, юриста сопровождения, куратора (Responsible) или соинвестора (InvestorSecondary). Удаление — soft-remove (проставляется RemovedAt), история участия сохраняется для аудита и чата. Дом полей состава и инвариантов — в deal.md (DealMemberEntity, INV-DEAL-3).

Назначение

Дать агентской стороне убрать из сделки участника, чьё участие больше не нужно, не разрушая обязательный каркас сделки. Операция снимает одного активного участника и поднимает событие для реконсиляции участников чата (messenger).

Ограничения

Исполнителя (Executor) удалить нельзя — процесс не может остаться без исполнителя (INV-DEAL-3): смена исполнителя выполняется передачей ведения (change-participant-role.md), а не удалением. Основной инвестор (InvestorPrimary) обязателен и через этот эндпоинт не снимается; стороны застройщика (SellManager) и авто-участник ПИН (PinManager) агентом не удаляются.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent
Feature groupdeals
Статусdraft
Документdocs/06-services/crm/api/agent/remove-deal-participant.md

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

ПроверкаЗначение
Actor typeUser — активный участник сделки (агентская сторона), руководитель компании-владельца или Менеджер ПИН
Auth policy / Permissionscrm.deals.manage (агентская сторона); Менеджер ПИН — platform-scope
Scope (RBAC)организация (агентство-владелец); сделка живёт в tenant застройщика (кросс-tenant по ADR-0052)
Record-levelдоступ = актор ∈ активные участники сделки (любая роль ParticipantRole) ИЛИ администратор/руководитель компании-владельца (агентство активного Executor по scope-иерархии ADR-0025; застройщик — свой TenantId) ИЛИ Менеджер ПИН (platform-scope, обход record-level)
Tenant isolationСделка TenantId = DeveloperCompanyId (владелец — застройщик, ADR-0052); агентская сторона идёт по членству
Auditupdate состава: RemovedAt = now, RemovedByUserId = actor на DealMemberEntity; Version++ сделки

HTTP-контракт

ПолеЗначение
MethodDELETE
Success status204 (участие снято)
Idempotency headerне применяется — повтор по уже снятому участнику → CRM_DEAL_MEMBER_NOT_FOUND (активного участия нет)
Correlationсквозной trace через заголовок traceparent

Route: /api/agent/crm/deals/{dealId}/members/{memberId}.

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
DealIdrouteGuidДа!= Guid.Empty; существующая сделка, доступная актору по членству
MemberIdrouteGuidДа!= Guid.Empty; активное участие (RemovedAt is null) этой сделки
ActorUserIdidentity claimsGuidДаиз JWT
ActorCompanyIdidentity claimsGuidДакомпания актора — record-level предикат
IsCompanyManageridentity claimsboolДаадминистратор/руководитель компании-владельца
IsPinManageridentity claimsboolДаplatform-scope Менеджера ПИН (обход record-level)

Тело запроса пустое (идентификация участия — по MemberId в маршруте).

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

Успех — 204 No Content (тело не возвращается). Актуальный состав клиент перечитывает картой сделки либо получает через событие (чат/уведомления).

Алгоритм

  1. Контекст и доступ. Операцию выполняет активный участник сделки (permission crm.deals.manage), руководитель компании-владельца или Менеджер ПИН (platform-scope).
  2. Проверка входа и предусловий.
    • формат: DealId/MemberId != Guid.Empty — иначе ошибка валидации;
    • сделка существует и доступна актору по членству/руководству/ПИН — иначе CRM_DEAL_NOT_FOUND (404);
    • сделка нетерминальна (Status ∉ {Completed, Cancelled}, INV-DEAL-9) — иначе CRM_DEAL_ALREADY_TERMINAL;
    • участие MemberId существует, принадлежит этой сделке и активно (RemovedAt is null) — иначе CRM_DEAL_MEMBER_NOT_FOUND (404);
    • роль участия допускает снятие через агентский эндпоинт (CoAgent/Assistant/Lawyer/Responsible/ InvestorSecondary) — иначе:
      • активный Executor (единственный) → CRM_LAST_EXECUTOR_REQUIRED (снятие/замена исполнителя — только атомарной передачей ведения, INV-DEAL-3);
      • InvestorPrimary (обязателен, ровно один), SellManager/PinManager (управляются иначе) → CRM_PARTICIPANT_ROLE_FORBIDDEN.
  3. Что читаем. Сделку (доступ/статус) и целевое участие (MemberId) с его ролью и признаком активности.
  4. Бизнес-правила и переходы (INV-DEAL-3). Снятие активного участника не должно нарушить обязательный каркас: ровно один активный Executor и ровно один InvestorPrimary сохраняются (их удаление заблокировано выше). Для допустимых ролей — soft-remove: RemovedAt = now, RemovedByUserId = actor; денормализованные скаляры сделки не затрагиваются (снимаемые роли — не singleton-скаляры сделки). При снятии Responsible денормализованный ResponsibleAgentUserId сбрасывается в null. Version++.
  5. Что меняется. Участие DealMemberEntity помечается снятым (RemovedAt) — в одном согласованном изменении с Version++ сделки (при желании — запись в таймлайн MemberRemoved, deal-timeline-event.md).
  6. События. DealMembersChangedEvent → топик pin.crm.deal-members-changed.v1 (ContractVersion = 1, AggregateId = DealId) с дельтой Members = [{UserId, Role, ChangeType = Removed}] (для снятого ResponsibleResponsibleChanged) и полным активным составом ActiveMembers после изменения — transactional outbox; контракт — deal-members-changed.md.
  7. Результат. HTTP 204 (тело пустое).

Диаграмма

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

Предусловия. Актор — активный участник сделки / руководитель компании-владельца / Менеджер ПИН с crm.deals.manage. Сделка существует, доступна актору и нетерминальна (INV-DEAL-9). MemberId — активное участие этой сделки с ролью, допускающей снятие агентом (не Executor/InvestorPrimary/SellManager/ PinManager).

Постусловие (успех). Участие помечено снятым (RemovedAt = now, RemovedByUserId = actor); при снятии ResponsibleResponsibleAgentUserId = null; Version сделки инкрементирован. Обязательный каркас сохранён (ровно один активный Executor и InvestorPrimary). Опубликован DealMembersChangedEvent (pin.crm.deal-members-changed.v1, key DealId) с ChangeType = Removed/ResponsibleChanged и полным ActiveMembers.

Eventual consistency

Участник исчезает из сделки сразу, из чата (messenger) и уведомлений (notifications) — с лагом консюмеров (< 1 мин).

Постусловие (отказ). Ни снятия участия, ни изменения Version, ни интеграционного события.

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

ТипДетали
Интеграционные событияDealMembersChangedEventpin.crm.deal-members-changed.v1 (ChangeType = Removed + полный ActiveMembers), outbox
Уведомленияопосредованно: консюмер deal-members-changed в notifications; messenger — исключение участника из чата сделки (set-реконсиляция по ActiveMembers)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400DealId/MemberId = Guid.Empty«Проверьте корректность запроса»no retry
CRM_DEAL_NOT_FOUND404сделки нет или она недоступна актору (record-level скрывает существование)«Сделка не найдена»no retry
CRM_DEAL_ALREADY_TERMINAL409сделка Completed/Cancelled (INV-DEAL-9)«Сделка завершена и не редактируется»no retry
CRM_DEAL_MEMBER_NOT_FOUND404участия нет, оно чужой сделки или уже снято (RemovedAt проставлен)«Участник не найден»no retry
CRM_LAST_EXECUTOR_REQUIRED409попытка снять единственного активного Executor (INV-DEAL-3)«Нельзя оставить сделку без исполнителя — передайте ведение»no retry
CRM_PARTICIPANT_ROLE_FORBIDDEN422попытка снять InvestorPrimary/SellManager/PinManager через агентский эндпоинт«Эта роль снимается другим способом»no retry

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

  • Контракт: route → 204. Смена маршрута/семантики удаления — breaking (migration + rollout). Новые значения ParticipantRole — additive (см. deal-members-changed.md).
  • Logs: deal-member removed (DealId, TenantId, MemberId, Role, ActorUserId; ФИО/телефон не пишутся); metrics crm_deal_member_removed_total{role}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path. Given активный участник CoAgent/Assistant/Lawyer/InvestorSecondary; When DELETE; Then 204, участие помечено RemovedAt, Version++, опубликован deal-members-changed (ChangeType = Removed, полный ActiveMembers).
  • Снятие куратора. Given активный Responsible; When DELETE; Then 204, ResponsibleAgentUserId = null, событие с ChangeType = ResponsibleChanged.
  • Нельзя убрать исполнителя. Given единственный активный Executor; When DELETE этого участия; Then 409 CRM_LAST_EXECUTOR_REQUIRED (сменить исполнителя — передачей ведения).
  • Нельзя убрать основного инвестора / системные роли. Given InvestorPrimary/SellManager/PinManager; When DELETE; Then 422 CRM_PARTICIPANT_ROLE_FORBIDDEN.
  • Повторное удаление. Given участие уже снято (RemovedAt проставлен); When DELETE; Then 404 CRM_DEAL_MEMBER_NOT_FOUND.
  • Терминальная сделка. Given сделка Completed/Cancelled; When DELETE; Then 409 CRM_DEAL_ALREADY_TERMINAL.
  • Нет доступа. Given сделка, где актор не участник и не руководитель/ПИН; When DELETE; Then 404 CRM_DEAL_NOT_FOUND.
  • Downstream (eventual). Given успешное снятие; When проходит < 1 мин; Then участник исключается из чата сделки (messenger, set-реконсиляция по ActiveMembers).

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

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

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