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

UC-CRM-008. Снятие участника со сделки

Назначение

Описывает сценарий, в котором агентская сторона (агент-исполнитель, руководитель агентства-владельца) или Менеджер ПИН снимает со сделки участника, чьё участие больше не нужно: со-агента, ассистента, юриста сопровождения, куратора (Responsible) или соинвестора (InvestorSecondary). Удаление — soft-remove (проставляется RemovedAt), история участия сохраняется для аудита и чата. Обязательный каркас сделки не разрушается: нельзя снять единственного исполнителя (Executor) — смена исполнителя выполняется атомарной передачей ведения (UC-CRM-009); основной инвестор (InvestorPrimary) и системные роли (SellManager/PinManager) через агентский эндпоинт не снимаются. Агрегат — DealEntity (DealMemberEntity, INV-DEAL-3). Связан с контекстом crm.

Metadata

ПолеЗначение
IDUC-CRM-008
Контекстcrm (см. docs/06-services/crm/)
Типосновной (+ альтернативные потоки A1–A4)
Статусdraft

Актор

ПолеЗначение
РольАгент (активный участник сделки), Руководитель/администратор агентства-владельца, Менеджер ПИН
Permissions / scopecrm.deals.manage — организация (агентство-владелец); Менеджер ПИН — platform-scope (кросс-tenant, ADR-0052)
TenantСделка TenantId = DeveloperCompanyId (ADR-0052); агентская сторона — по членству; Менеджер ПИН — null (сквозной доступ)
Record-levelдоступ = актор ∈ активные участники сделки (любая роль) ИЛИ руководитель/администратор компании-владельца (агентство активного Executor по scope-иерархии ADR-0025; застройщик — свой TenantId) ИЛИ Менеджер ПИН (обход record-level)

Предусловие

  • Актор аутентифицирован и несёт crm.deals.manage (агентская сторона) либо platform-scope Менеджера ПИН.
  • Целевая сделка существует (DealEntity), доступна актору и нетерминальна (Status ∉ {Completed, Cancelled}, INV-DEAL-9).
  • MemberIdактивное участие (RemovedAt is null) именно этой сделки.
  • Роль снимаемого участия допускает снятие агентом: CoAgent / Assistant / Lawyer / Responsible / InvestorSecondary.

Триггер

ПолеЗначение
ИсточникUI-действие / вызов API
Триггерящий элементКарточка сделки, блок «Участники» → «Убрать участника»; DELETE /api/agent/crm/deals/{dealId}/members/{memberId} (remove-deal-participant.md)

Основной Поток

  1. Актор открывает карточку сделки и в блоке «Участники» выбирает «Убрать» напротив конкретного участия (MemberId).
  2. Интерфейс отправляет запрос на снятие участника (идентификация — по MemberId в маршруте, тело пустое).
  3. Система определяет контекст и доступ актора: активный участник сделки, руководитель компании-владельца или Менеджер ПИН.
  4. Система проверяет вход и предусловия: формат идентификаторов; сделка существует, доступна и нетерминальна; участие MemberId существует, принадлежит этой сделке и активно; роль участия допускает снятие агентом.
  5. Система применяет guard обязательного каркаса (INV-DEAL-3):
    • единственный активный Executor — снять нельзя (CRM_LAST_EXECUTOR_REQUIRED): смена исполнителя — только передачей ведения;
    • InvestorPrimary (обязателен, ровно один), SellManager/PinManager — через агента не снимаются (CRM_PARTICIPANT_ROLE_FORBIDDEN).
  6. Для допустимой роли система помечает участие снятым (RemovedAt = now, «кем снято»); при снятии Responsible денормализованный ResponsibleAgentUserId сбрасывается в null; инкрементирует версию сделки и, при желании, пишет строку таймлайна «участник снят» — в одном согласованном изменении. Обязательный каркас сохранён (ровно один активный Executor и InvestorPrimary).
  7. Система фиксирует факт: публикует deal-members-changed.md (pin.crm.deal-members-changed.v1, key DealId) с дельтой (ChangeType = Removed; для снятого куратора — ResponsibleChanged) и полным активным составом после изменения.
  8. Система возвращает 204 (тело пустое); интерфейс перечитывает состав участников сделки.

Альтернативные Потоки

примечание
A1. Снятие куратора (Responsible)
  1. Актор снимает активного куратора ведения.
  2. Помимо RemovedAt система сбрасывает денормализованный ResponsibleAgentUserId = null; событие несёт дельту ChangeType = ResponsibleChanged.

Возвращается к основному потоку на шаг 7.

A2. Попытка снять единственного исполнителя
  1. Актор пытается снять участие с ролью Executor, который на сделке единственный активный.
  2. Система отклоняет запрос CRM_LAST_EXECUTOR_REQUIRED (409): сделку нельзя оставить без исполнителя — заменить исполнителя можно только атомарной передачей ведения (UC-CRM-009).

Завершается ошибкой; состав не меняется.

A3. Попытка снять основного инвестора / системную роль
  1. Актор пытается снять InvestorPrimary (обязателен, ровно один) либо SellManager/PinManager.
  2. Система отклоняет запрос CRM_PARTICIPANT_ROLE_FORBIDDEN (422): эти роли снимаются/управляются иначе, не через агентский эндпоинт.

Завершается ошибкой.

A4. Повторное снятие / несуществующее участие
  1. Участие MemberId уже снято (RemovedAt проставлен), не найдено или принадлежит другой сделке.
  2. Система отвечает CRM_DEAL_MEMBER_NOT_FOUND (404) — активного участия нет (операция неидемпотентна по заголовку, но повтор безопасен: не создаёт побочных изменений).

Завершается ошибкой.

Постусловие

  • Участие помечено снятым (RemovedAt = now, «кем снято»); при снятии куратора — ResponsibleAgentUserId = null; версия сделки инкрементирована. Обязательный каркас сохранён (ровно один активный Executor и InvestorPrimary).
  • Опубликован DealMembersChangedEvent (ChangeType = Removed / ResponsibleChanged, полный активный состав).
  • Окно eventual-consistency: участник исчезает из сделки сразу; из чата сделки (messenger, set-реконсиляция по полному составу) и уведомлений — с лагом консюмеров (< 1 мин).
  • При отказе — ни снятия участия, ни изменения версии, ни события.

Возможные Ошибки

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

Доменные События

СобытиеКогда поднимаетсяSubscribers
DealMemberRemovedSoft-remove активного участникаподписчик таймлайна (MemberRemoved), сборщик DealMembersChangedEvent

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
DealMembersChangedEventpin.crm.deal-members-changed.v1Снятие участника — контрактmessenger (исключение из чата сделки, set-реконсиляция), notifications

Acceptance Criteria

#КритерийПроверка
AC1Снятие CoAgent/Assistant/Lawyer/InvestorSecondary204, участие помечено RemovedAt, версия сделки инкрементирована, событие deal-members-changed (Removed, полный состав)Тест операции + проверка outbox
AC2Снятие Responsible204, ResponsibleAgentUserId = null, событие с ChangeType = ResponsibleChangedТест снятия куратора
AC3Снятие единственного активного Executor409 CRM_LAST_EXECUTOR_REQUIRED, состав не меняетсяGuard-тест INV-DEAL-3
AC4Снятие InvestorPrimary/SellManager/PinManager422 CRM_PARTICIPANT_ROLE_FORBIDDENGuard-тест роли
AC5Повторное снятие уже снятого участия → 404 CRM_DEAL_MEMBER_NOT_FOUNDТест идемпотентности-как-отсутствия
AC6Снятие в терминальной сделке → 409 CRM_DEAL_ALREADY_TERMINALGuard-тест INV-DEAL-9
AC7Актор не участник/руководитель/ПИН → 404 CRM_DEAL_NOT_FOUNDRecord-level тест
AC8Через < 1 мин после успеха участник исключён из чата сделки (messenger, set-реконсиляция по полному составу)Downstream (eventual)

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

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

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