UC-IDN-010. Агент выходит из агентства, сохраняя метрики
Назначение
Развязка членства (ADR-0061, аналогия «игрок меняет клан»): агент выходит из агентства и становится
независимым, сохраняя UserId, роль agent (переезжает в scope Platform) и все личные
метрики/достижения (user-centric, агрегация по AgentUserId). История членства не удаляется, а исторические
сделки/лиды/фиксации остаются у прежней компании (снапшоты AgencyCompanyId/AgentUserId в crm
иммутабельны, back-fill запрещён). Вызывает сам агент либо руководитель агентства. Связан с контекстом
identity.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-IDN-010 |
| Контекст | identity (см. docs/06-services/identity/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Агент (сам) — self-действие; либо Агентство (руководитель) исключает агента с identity.users.manage |
| Permissions / scope | self (UserId из sub); либо identity.users.manage в scope Organization |
| Tenant | членство читается по AgentUserId (кросс-компанийно); мутация — в scope прежнего Organization |
| Record-level | активное AgentMembership по AgentUserId |
Предусловие
- У агента есть активное
AgentMembership(Status = Active); иначе он уже независимый. UserEntity.Type = Agent,Status = Active.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | ЛК агента → «Моё агентство» → «Выйти из агентства» |
Основной Поток
- Агент нажимает «Выйти из агентства» и подтверждает (диалог поясняет: наработки и метрики сохранятся, доступ к клиентской базе агентства прекратится).
- Система принимает запрос выхода (leave-agency.md).
- Проверить: у агента есть активное
AgentMembership(иначеIDENTITY_MEMBERSHIP_NOT_ACTIVE);UserEntity.Status = Active(иначеIDENTITY_USER_BLOCKED); найти активное назначение ролиagentвOrganization. - В одном согласованном изменении (INV-AM6): закрыть членство (
Active → Left, заполнитьLeftAt, история сохраняется — INV-AM2); отвязать пользователя от агентства (зеркалоCompanyId → null, тип остаётсяAgent); отозвать назначение ролиagentвOrganizationи создатьRoleAssignment (agent, Platform)— роль переезжаетOrganization → Platform(INV-R3). Историческая привязка сделок/лидов/фиксаций не пересчитывается (ADR-0061 п.4). Гонка leave/accept разрешается оптимистичной блокировкой (CONCURRENCY_CONFLICT). - Зафиксировать факты:
agent-membership-changed (Left),user-upserted (CompanyId = null),user-access-changed. - Агент видит статус «Независимый агент»; клиентская база агентства скрыта, личные метрики/достижения и лоты остаются. Руководитель по membership-событию видит убытие из ростера.
Альтернативные Потоки
A1. Руководитель исключает агента
- Та же операция, вызванная руководителем с
identity.users.manage(дельта доступа — leave-agency.md); actor-stamping = руководитель. Итог как в основном потоке.
A2. Агент выходит и хочет полностью вернуться к инвестору
- Вместо leave агент вызывает
become-investor(become-investor.md): снимается рольagent, закрывается членство, типAgent → Investor(UC пересекается — см. become-investor).
A3. Нет активного членства (уже независимый)
- Шаг 3 →
IDENTITY_MEMBERSHIP_NOT_ACTIVE(404); повтор идемпотентен для UI.
Постусловие
AgentMembership.Status = Left,LeftAtзаполнен; запись сохранена (история найма).UserEntity.Type = Agent(независимый), зеркалоCompanyId = null.- Роль
agentдействует в scopePlatform; назначение вOrganizationотозвано. - Личные метрики/достижения агента (user-centric) сохранены; исторические сделки/комиссии — у прежней компании.
- Опубликованы
agent-membership-changed (Left),user-upserted,user-access-changed.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
IDENTITY_MEMBERSHIP_NOT_ACTIVE | 404 | Нет активного членства (A3) | «Вы не состоите в агентстве» |
IDENTITY_USER_BLOCKED | 400 | Status != Active | «Учётная запись заблокирована» |
CONCURRENCY_CONFLICT | 409 | Гонка leave/accept | «Повторите попытку» |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
AgentLeftAgencyEvent | Active → Left | in-proc |
RoleAssignmentRevokedEvent | Отзыв роли agent в Organization | in-proc аудит |
RoleAssignmentGrantedEvent | Выдача роли agent в Platform | in-proc аудит |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
AgentMembershipChangedKafkaEvent.v1 | pin.identity.agent-membership-changed.v1 | Active → Left | crm (ростер, user-centric метрики), rewards (основания комиссий), notifications |
UserUpsertedKafkaEvent.v1 | pin.identity.users.v1 | Зеркало CompanyId = null | crm, messenger, profile |
UserAccessChangedKafkaEvent.v1 | pin.identity.access.v1 | Роль Organization → Platform | crm, catalog, chessboard, gateway |
События идут с ключом партиционирования UserId; потребители идемпотентны и сохраняют порядок изменений на
пользователя. Членство закрывается в identity атомарно (INV-AM6), но доступ агента к клиентской базе
агентства фактически прекращается после инвалидации кэшей прав по pin.identity.access.v1 (окно ≤ TTL,
UC-IDN-008) — короткое время после выхода агент ещё может видеть данные агентства из кэша. crm по
agent-membership-changed (Left) убирает агента из ростера, но не переписывает исторические снапшоты
AgencyCompanyId/AgentUserId на сделках/лидах/фиксациях (back-fill запрещён, ADR-0061 п.4) — прежняя
компания сохраняет основания комиссий. Гонка «leave против accept другого приглашения» разрешается
оптимистичной блокировкой членства (CONCURRENCY_CONFLICT): победитель один, проигравший повторяет операцию
на актуальном состоянии.
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Выход переводит membership в Left (не удаляет) — история сохранена (INV-AM2) | тест: запись присутствует с LeftAt |
| AC2 | Роль agent сохраняется, переезжает Organization → Platform | тест эффективных прав до/после |
| AC3 | Личные метрики/достижения агента не теряются (user-centric) | тест агрегации по AgentUserId |
| AC4 | Исторические сделки/лиды/фиксации остаются у прежней компании (back-fill запрещён) | тест: снапшоты AgencyCompanyId/AgentUserId не меняются |
| AC5 | Повторный выход без активного членства → 404 (A3) | двойной вызов |
| AC6 | Атомарность (membership + user + роль + события) | сбой между шагами → откат |
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): Identity Agent API — реестр методов и общие правила области, POST /api/v1/me/agency-membership/leave — Выход агента из агентства
- Use Cases (2): UC-CRM-013. Увольнение агента: каскад отвязки от процессов CRM, UC-CRM-014. Агент выбирает активное агентство перед созданием лида/сделки
- scenarios (1): UC-OVR-008. Агент выходит из агентства, сохраняя метрики
- index.md (1): Identity Service (Pin.Identity)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: leave-agency.md; реестр области — agent/README.md.
- Domain: ../domain/agent-membership.md (INV-AM2/AM3/AM6), ../domain/user.md, ../domain/role.md.
- Событие: ../events/agent-membership-changed.md.
- Историческая привязка (reference only):
docs/06-services/crm/domain/deal.md,docs/06-services/crm/api/agent/get-statistics.md. - Связанные UC: UC-IDN-009 (вступление), UC-IDN-011 (become-agent); полный возврат к инвестору — become-investor.
- Решение: ADR-0061.