GET /api/agent/crm/clients/{clientId} — Карточка клиента
Read-модель карточки клиента агентской воронки (/crm/clients/[clientId]): ФИО, контакты, комментарий,
источник, активный лид и счётчики связей (лиды/фиксации/сделки). Дом полей сущности —
client.md. Вкладки карточки events/collections/history — отдельные запросы
(см. «Побочные эффекты»), в этот ответ не входят.
Назначение
Открыть карточку клиента из списка /crm/clients для агента, ведущего клиента (участника его процессов), или
руководителя агентства: показать контактные данные, свободный комментарий, происхождение записи, текущий
активный лид пары «агент ↔ инвестор» и агрегированные счётчики дочерних связей.
| Поле | Значение |
|---|
| Сервис/контекст | crm |
| API area | agent |
| Feature group | clients |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/get-client.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Агент; либо Руководитель агентства) |
| Auth policy / Permissions | crm.clients.view (JWT) |
| Scope (RBAC) | организация (агентство AgencyCompanyId) |
| Record-level | доступ по членству агентства: актор — активный участник процесса клиента (лид/фиксация/сделка) или руководитель/администратор агентства-владельца (TenantId) или Менеджер ПИН (client.md) |
| Tenant isolation | TenantId = AgencyCompanyId в каждом запросе |
| Audit | read (не sensitive: клиент — данные своего агентства); PII в логи не пишется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/agent/crm/clients/{clientId} |
| Success status | 200 |
| Idempotency header | не применяется (безопасный GET) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims | Guid | Да | Required, != Guid.Empty | Tenant isolation (AgencyCompanyId) |
ActorUserId | identity claims | Guid | Да | Required | record-level (участник процесса/руководитель) |
IsAgencyManager | identity claims (роль/permission) | bool | Да | вычисляется из scope permission | даёт доступ к любому клиенту своего TenantId (не только к процессам актора) |
ClientId | route | Guid | Да | != Guid.Empty; существующий клиент своего TenantId | — |
Модель ответа ClientCardModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | ClientEntity.Id | — | clientId в маршруте карточки. |
Name | FullNameModel | Да | ClientEntity.Name (VO FullName) | — | ФИО; структура — VO FullName, client.md. |
PhoneNumber | string | Да | ClientEntity.PhoneNumber | нет (актор с доступом видит полностью; в логах маска +7***NNNN) | Телефон клиента (PII). |
Email | string? | Нет | ClientEntity.Email | нет | Контактный email (PII). |
Comment | string? | Нет | ClientEntity.Comment | — | Свободная заметка агента. |
Source | ClientSource (enum строкой) | Да | ClientEntity.Source | — | Происхождение записи (Manual/ConsultationRequest/BookingRequest/Referral), client.md. |
Status | ClientStatus (enum строкой) | Да | ClientEntity.Status | — | Active/Archived. |
InvestorUserId | Guid? | Нет | ClientEntity.InvestorUserId | — | Пользователь-инвестор (если зарегистрирован); ключ вкладки collections. |
ActiveLead | ClientActiveLeadModel? | Нет | лид по ClientEntity.ActiveLeadId | — | Текущий активный лид; null — активного лида нет. |
Counters | ClientRelationCountersModel | Да | computed | — | Счётчики дочерних связей. |
LastActivityAt | DateTimeOffset? | Нет | ClientEntity.LastActivityAt | — | Последнее событие клиента. |
CreatedAt | DateTimeOffset | Да | ClientEntity.CreatedAt | — | Создание карточки. |
FullNameModel
Проекция VO FullName (дом — client.md): LastName: string, FirstName: string,
MiddleName: string?. Дельты нет — поля переносятся 1:1.
ClientActiveLeadModel
| Поле | Тип | Обязательность | Источник | Описание |
|---|
LeadId | Guid | Да | LeadEntity.Id | Активный лид пары (переход в карточку лида). |
Status | LeadStatus (enum строкой) | Да | LeadEntity.Status | Всегда Active (инвариант ActiveLeadId, INV-CLI-2). |
Source | LeadSource (enum строкой) | Да | LeadEntity.Source | Как появился лид (lead.md). |
ExpiresAt | DateTimeOffset | Да | LeadEntity.ExpiresAt | Срок жизни лида. |
LastEventAt | DateTimeOffset? | Нет | LeadEntity.LastEventAt | Последнее событие ленты лида. |
ClientRelationCountersModel
| Поле | Тип | Обязательность | Источник | Описание |
|---|
LeadsCount | int | Да | число лидов клиента (ClientId) | Всего лидов клиента (lead.md). |
FixationRequestsCount | int | Да | число заявок на фиксацию (ClientId) | Всего заявок (fixation-request.md). |
DealsCount | int | Да | число сделок клиента (ClientId) | Всего сделок (deal.md). |
Алгоритм
- Контекст и доступ. Операцию выполняет агент (permission
crm.clients.view) в tenant своего агентства
(TenantId = AgencyCompanyId); признак IsAgencyManager даёт доступ к любому клиенту своего TenantId.
- Проверка входа и предусловий.
- формат:
ClientId != Guid.Empty, TenantId != Guid.Empty — иначе ошибка валидации;
- клиент существует — иначе
CRM_CLIENT_NOT_FOUND (404);
- tenant isolation: клиент того же
TenantId; чужой tenant трактуется как CRM_CLIENT_NOT_FOUND (без
утечки существования);
- record-level (членство агентства): актор — активный участник процесса клиента (лид/фиксация/сделка) или
IsAgencyManager (руководитель/администратор TenantId) или Менеджер ПИН — иначе FORBIDDEN (403);
- состояние: чтение разрешено для
Active и Archived (архивная карточка открывается).
- Что читаем. Карточку клиента; активный лид (если
ActiveLeadId != null) — проекция в
ClientActiveLeadModel; скалярные счётчики лидов/фиксаций/сделок клиента в границах TenantId. Списки
дочерних сущностей здесь не загружаются (они — отдельные вкладки).
- Маппинг. Поле-в-поле
ClientEntity → ClientCardModel; лид → ClientActiveLeadModel; счётчики →
ClientRelationCountersModel. Enum-поля сериализуются строкой.
- Результат. Карточка клиента → HTTP
200. Freshness — актуально на момент запроса (ФИО зарегистрированного
клиента — eventual-реплика из identity). Ошибки → ProblemDetails (404/403/400).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Интеграционные события | none (безопасный GET) |
| Внешние вызовы | none (ФИО зарегистрированного клиента — уже реплика от identity в crm.clients) |
| Проекции / поиск | none (чтение) |
| Уведомления | none |
| Отдельные запросы вкладок | events — лента через лиды клиента (lead.md); collections — API selections по InvestorUserId (selections README); history — списки лидов/фиксаций/сделок. В этот ответ не входят. |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | ClientId/TenantId пустой | «Некорректный идентификатор клиента» | no retry |
CRM_CLIENT_NOT_FOUND | 404 | клиент не найден или TenantId не совпал | «Клиент не найден» | no retry |
FORBIDDEN | 403 | актор не участник процессов клиента и не руководитель агентства/Менеджер ПИН | «Нет доступа к карточке клиента» | no retry |
Совместимость и наблюдаемость
- Новые optional поля
ClientCardModel — additive (без breaking change); удаление/переименование/смена типа —
breaking (migration + rollout). Неизвестные значения enum (ClientSource/ClientStatus/LeadSource) — клиент
трактует как «прочее», не падает.
- Logs:
client-card viewed (ClientId, TenantId, ActorUserId; телефон/email — не пишутся);
metrics: crm_client_card_requests_total{result}, latency p95 (< 1 с, SLO чтений README); traces — сквозной
trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.
Acceptance Criteria
- Happy path. Given агент, ведущий клиента (участник его процессов), и существующий клиент своего
TenantId;
When GET; Then 200 с ClientCardModel (контакты, источник, активный лид, счётчики), телефон — полностью, в
логах маска +7***NNNN.
- Руководитель. Given руководитель агентства и клиент своего
TenantId другого агента; When GET; Then
200 (доступ по членству открыт признаком IsAgencyManager — все клиенты TenantId).
- Чужой клиент. Given рядовой агент и клиент того же
TenantId, в процессах которого актор не участвует
(создан другим агентом); When GET; Then 403 FORBIDDEN.
- Другой tenant. Given клиент другого
TenantId; When GET; Then 404 CRM_CLIENT_NOT_FOUND (без утечки
существования).
- Не найден. Given несуществующий
ClientId; When GET; Then 404 CRM_CLIENT_NOT_FOUND.
- Валидация. Given
ClientId/TenantId = Guid.Empty; When GET; Then 400 VALIDATION_FAILED.
- Без активного лида. Given
ActiveLeadId == null; When GET; Then 200, ActiveLead = null, счётчики
корректны.
- Архивная карточка. Given
Status = Archived; When GET; Then 200 (архивная карточка открывается).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы