PUT /api/agent/crm/deals/{dealId}/members/{memberId}/role — Сменить роль участника сделки
Агент (или руководитель агентства / Менеджер ПИН) меняет роль активного участника сделки. Метод покрывает
как обычную смену роли (например Assistant → CoAgent), так и передачу ведения — назначение участника
исполнителем (Executor) или основным инвестором (InvestorPrimary) с атомарным понижением прежнего носителя
singleton-роли. Дом полей состава, ролей и инвариантов — в deal.md
(DealMemberEntity, ParticipantRole, INV-DEAL-3, INV-DEAL-10).
Назначение
Дать агентской стороне перераспределить роли внутри сделки: повысить со-агента до исполнителя (передача
ведения — новый получатель комиссии), назначить/сменить куратора (Responsible), сделать соинвестора
основным (InvestorPrimary) или изменить нефинансовую роль сопровождения. Singleton-роли (Executor,
InvestorPrimary) в сделке всегда ровно по одной активной: назначение новой роли-синглтона выполняется как
атомарная передача — прежний носитель понижается в той же операции, и процесс ни на миг не остаётся без
исполнителя (INV-DEAL-3).
Историческая атрибуция (OriginAgentUserId/OriginAgencyCompanyId) при передаче ведения не меняется
(INV-DEAL-10). Добавление и снятие участников выполняется отдельными операциями
(add-deal-participant.md,
remove-deal-participant.md), а не этим методом.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent |
| Feature group | deals |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/change-participant-role.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — активный участник сделки (агентская сторона), руководитель компании-владельца или Менеджер ПИН |
| Auth policy / Permissions | crm.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); агентская сторона идёт по членству |
| Audit | update состава: Role целевого участия (при передаче ведения — понижение прежнего носителя); при передаче — денормализация ExecutorAgentUserId/PrimaryInvestorUserId/ResponsibleAgentUserId; Version++ сделки |
Выполнить может активный участник сделки (любая роль ParticipantRole), администратор/руководитель
компании-владельца (агентство активного Executor по scope-иерархии ADR-0025; застройщик — свой TenantId)
или Менеджер ПИН (platform-scope, обход record-level).
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | PUT |
| Route | /api/agent/crm/deals/{dealId}/members/{memberId}/role |
| Success status | 200 (обновлённое участие) |
| Idempotency header | не применяется — повтор той же роли = no-op (участие уже в целевой роли), гонка ловится конкурентной проверкой версии |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
DealId | route | Guid | Да | != Guid.Empty; существующая сделка, доступная актору по членству | — |
MemberId | route | Guid | Да | != Guid.Empty; активное участие (RemovedAt is null) этой сделки | — |
NewRole | body | string (enum ParticipantRole) | Да | одно из Executor / Responsible / CoAgent / Assistant / Lawyer / InvestorPrimary / InvestorSecondary; SellManager/PinManager через агентский эндпоинт не назначаются → CRM_PARTICIPANT_ROLE_FORBIDDEN | — |
RowVersion | body | uint? | Нет | наблюдаемая версия карточки для оптимистической блокировки | — |
ActorUserId | identity claims | Guid | Да | из JWT | — |
ActorCompanyId | identity claims | Guid | Да | компания актора — record-level предикат | — |
IsCompanyManager | identity claims | bool | Да | администратор/руководитель компании-владельца | — |
IsPinManager | identity claims | bool | Да | platform-scope Менеджера ПИН (обход record-level) | — |
Модель ответа DealMemberModel
Участие после смены роли (дом полей — deal.md; ниже — отдаваемая дельта).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
MemberId | Guid | Да | DealMemberEntity.Id | — | Идентификатор участия. |
DealId | Guid | Да | DealMemberEntity.DealId | — | Сделка. |
UserId | Guid | Да | DealMemberEntity.UserId | — | Пользователь-участник. |
Role | string (enum ParticipantRole) | Да | DealMemberEntity.Role (после смены) | — | Новая роль участника. |
PreviousExecutorUserId | Guid? | Нет | понижённый прежний Executor (только при передаче ведения) | — | Кто был исполнителем до передачи (для отражения в UI). |
Version | long | Да | DealEntity.Version | — | Версия сделки после изменения. |
Алгоритм
- Контекст и доступ. Операцию выполняет активный участник сделки (permission
crm.deals.manage), руководитель компании-владельца или Менеджер ПИН (platform-scope). - Проверка входа и предусловий.
- формат:
DealId/MemberId!= Guid.Empty;NewRoleзадан и из enumParticipantRole— иначе ошибка валидации; NewRoleназначается через агентский эндпоинт (неSellManager/PinManager) — иначеCRM_PARTICIPANT_ROLE_FORBIDDEN;- сделка существует и доступна актору по членству/руководству/ПИН — иначе
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).
- формат:
- Что читаем. Сделку (доступ/статус, текущие носители singleton-ролей), целевое участие (
MemberId, текущая роль) и — при назначении роли-синглтона — действующего носителя этой роли. - Бизнес-правила и переходы (INV-DEAL-3).
- No-op. Если
NewRole == member.Role→ изменений нет, возвращается текущее участие (идемпотентный повтор); мутаций и событий нет. - Смена на несинглтон (
CoAgent/Assistant/Lawyer/InvestorSecondary): роль участия меняется напрямую; если участник был носителем singleton-роли (Executor/InvestorPrimary) и понижается без назначения преемника — операция запрещена (CRM_LAST_EXECUTOR_REQUIREDдляExecutor; понижение единственногоInvestorPrimaryбез назначения нового —CRM_PARTICIPANT_ROLE_FORBIDDEN). Событие —RoleChanged. - Передача ведения (
NewRole = Executor): целевой участник становитсяExecutor; прежний активныйExecutorв той же операции понижается доCoAgent(не остаётся вторымExecutor) →ExecutorChanged. ДенормализованныйExecutorAgentUserId = member.UserId.OriginAgentUserId/OriginAgencyCompanyIdне меняются (INV-DEAL-10). Процесс ни на миг не остаётся без исполнителя. - Смена основного инвестора (
NewRole = InvestorPrimary): целевой участник становитсяInvestorPrimary; прежнийInvestorPrimaryпонижается доInvestorSecondaryв той же операции;PrimaryInvestorUserId = member.UserId. Событие —RoleChanged(полныйActiveMembersнесёт актуальный синглтон). - Назначение/смена куратора (
NewRole = Responsible): если действующийResponsibleесть — понижается (Responsibleопционален, ≤1);ResponsibleAgentUserId = member.UserId→ResponsibleChanged. Version++,UpdatedByUserId = actor,UpdatedAt = now.
- No-op. Если
- Что меняется. Роль целевого участия (и, при передаче, роль прежнего носителя singleton-роли) +
денормализованные скаляры сделки (
ExecutorAgentUserId/PrimaryInvestorUserId/ResponsibleAgentUserId) — в одном согласованном изменении сVersion++(при желании — запись в таймлайнMemberRoleChanged, deal-timeline-event.md). - События.
DealMembersChangedEvent→ топикpin.crm.deal-members-changed.v1(ContractVersion = 1,AggregateId = DealId) с дельтойMembers(при передаче ведения — две записи: понижение прежнего носителя- назначение новому) и
ChangeType(RoleChanged/ExecutorChanged/ResponsibleChanged) плюс полный активный составActiveMembersпосле изменения — transactional outbox; контракт — deal-members-changed.md.
- назначение новому) и
- Результат. Участие после смены роли → HTTP
200. Конкурентная гонка (устаревшийRowVersionили параллельная смена состава) →CRM_DEAL_CONFLICT(409); клиент перечитывает состав.
Диаграмма
Предусловия и постусловия
Предусловия. Актор — активный участник сделки / руководитель компании-владельца / Менеджер ПИН с
crm.deals.manage. Сделка существует, доступна актору и нетерминальна (INV-DEAL-9). MemberId — активное
участие этой сделки. NewRole назначается через агента (не SellManager/PinManager).
Постусловие (успех, не no-op). Роль целевого участия = NewRole. При передаче ведения (Executor) прежний
исполнитель понижен до CoAgent, ExecutorAgentUserId = member.UserId, Origin-атрибуция не изменена
(INV-DEAL-10). При InvestorPrimary прежний основной инвестор понижен до InvestorSecondary,
PrimaryInvestorUserId обновлён. При Responsible действующий куратор понижен, ResponsibleAgentUserId
обновлён. Инварианты INV-DEAL-3 сохранены (ровно один активный Executor и InvestorPrimary). Version
инкрементирован. Опубликован DealMembersChangedEvent (pin.crm.deal-members-changed.v1, key DealId) с
соответствующим ChangeType и полным ActiveMembers.
Новая раскладка ролей видна в ответе сразу; чат (messenger), уведомления (notifications) и — при передаче
ведения — снятие доступа уходящего исполнителя обновляются с лагом консюмеров (< 1 мин).
Постусловие (no-op, NewRole == member.Role). Состояние сделки, состав и outbox не изменены; возвращается
текущее участие.
Постусловие (отказ). Ни смены ролей, ни изменения Version, ни интеграционного события.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | DealMembersChangedEvent → pin.crm.deal-members-changed.v1 (RoleChanged/ExecutorChanged/ResponsibleChanged + полный ActiveMembers), outbox |
| Уведомления | опосредованно: консюмер deal-members-changed в notifications (новому исполнителю/куратору); messenger — обновление ролей участников чата + системное сообщение о передаче ведения |
Ошибки и failure modes
Понижение единственного активного Executor без назначения преемника в той же операции запрещено —
409 CRM_LAST_EXECUTOR_REQUIRED (INV-DEAL-3): передача ведения обязана оставаться атомарной. Понижение
единственного InvestorPrimary без назначения нового блокируется 422 CRM_PARTICIPANT_ROLE_FORBIDDEN.
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | DealId/MemberId = Guid.Empty или NewRole вне enum | «Проверьте корректность запроса» | no retry |
CRM_PARTICIPANT_ROLE_FORBIDDEN | 422 | NewRole = SellManager/PinManager; попытка понизить единственного InvestorPrimary без назначения нового | «Эта роль назначается другим способом» | no retry |
CRM_DEAL_NOT_FOUND | 404 | сделки нет или она недоступна актору (record-level скрывает существование) | «Сделка не найдена» | no retry |
CRM_DEAL_ALREADY_TERMINAL | 409 | сделка Completed/Cancelled (INV-DEAL-9) | «Сделка завершена и не редактируется» | no retry |
CRM_DEAL_MEMBER_NOT_FOUND | 404 | участия нет, оно чужой сделки или снято | «Участник не найден» | no retry |
CRM_LAST_EXECUTOR_REQUIRED | 409 | понижение единственного активного Executor без назначения преемника в той же операции (INV-DEAL-3) | «Нельзя оставить сделку без исполнителя — передайте ведение» | no retry |
CRM_DEAL_CONFLICT | 409 | конкурентная гонка (устаревший RowVersion / параллельная смена состава) | «Состав сделки изменён, обновите данные» | retry (перечитать состав) |
Совместимость и наблюдаемость
- Контракт: route +
NewRole/RowVersion(body) →DealMemberModel. Новые optional-поля — additive; смена маршрута/семантики передачи ведения — breaking (migration + rollout). Новые значенияParticipantRole— additive (см. deal-members-changed.md). - Logs:
deal-member role-changed/executor-reassigned(DealId,TenantId,MemberId,FromRole→NewRole,ActorUserId; ФИО/телефон не пишутся); metricscrm_deal_member_role_changed_total{change_type}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/.
Acceptance Criteria
- Смена нефинансовой роли. Given активный
Assistant; WhenPUTcNewRole = CoAgent; Then200, роль обновлена, событиеChangeType = RoleChanged, полныйActiveMembers. - Передача ведения. Given сделка с исполнителем A и со-агентом B; When
PUTдля B сNewRole = Executor; Then200, B —Executor, A понижен доCoAgent,ExecutorAgentUserId = B, событиеChangeType = ExecutorChanged, вActiveMembersровно одинExecutor;OriginAgentUserId/OriginAgencyCompanyIdне изменены (INV-DEAL-10). - Смена основного инвестора. Given
InvestorPrimaryP иInvestorSecondaryS; WhenPUTдля S сNewRole = InvestorPrimary; Then S —InvestorPrimary, P понижен доInvestorSecondary,PrimaryInvestorUserId = S, вActiveMembersровно одинInvestorPrimary. - Назначение куратора. Given без активного
Responsible; WhenPUTcNewRole = Responsible; Then200,ResponsibleAgentUserIdзадан, событиеChangeType = ResponsibleChanged. - Нельзя понизить последнего исполнителя. Given единственный активный
Executor; WhenPUTcNewRole = CoAgentдля него (без назначения преемника); Then409 CRM_LAST_EXECUTOR_REQUIRED. - Недопустимая роль. Given
NewRole = SellManager/PinManager; WhenPUT; Then422 CRM_PARTICIPANT_ROLE_FORBIDDEN. - No-op. Given
NewRole == member.Role; WhenPUT; Then200с текущим участием, без мутаций и событий. - Терминальная сделка. Given сделка
Completed/Cancelled; WhenPUT; Then409 CRM_DEAL_ALREADY_TERMINAL. - Гонка. Given параллельная смена состава или устаревший
RowVersion; WhenPUT; Then409 CRM_DEAL_CONFLICT; проигравший перечитывает состав. - Downstream (eventual). Given успешная передача ведения; When проходит < 1 мин; Then роли участников чата
обновлены (
messenger), новый исполнитель уведомлён (notifications), доступ уходящего исполнителя снят вместе с его понижением.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): DELETE /api/agent/crm/deals/{dealId}/members/{memberId} — Удалить участника сделки, POST /api/agent/crm/deals/{dealId}/members — Добавить участника сделки
- Use Cases (3): UC-CRM-007. Добавление участника в сделку, UC-CRM-008. Снятие участника со сделки, US-реестр «Участники и владение процессами» (US-CRM-PART / US-AG-ADMIN / US-AGT-MULTI / US-INV-CO)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей
DealMemberEntity,ParticipantRole, INV-DEAL-3/10, передача ведения): deal.md; таймлайн: deal-timeline-event.md. - Событие: deal-members-changed.md (
pin.crm.deal-members-changed.v1,ChangeTypeRoleChanged/ExecutorChanged/ResponsibleChanged). - Соседние операции состава: add-deal-participant.md, remove-deal-participant.md; создание и начальный состав — create-deal.md.
- ADR: 0052 tenancy, 0055 мини-мессенджер, 0071 участники и владелец сделки.