POST /api/agent/crm/clients — Создать клиента вручную
Агент заводит карточку клиента в своей базе (/crm/clients) до какой-либо активности — телефонный контакт,
встреча на объекте, холодный лид. Это ручная точка входа в клиентскую базу (Source = Manual), альтернатива
автоматическому появлению клиента при первом лиде пары «агент ↔ инвестор» или по заявке с витрины
(create-booking-request.md, lazy-создание). Карточка живёт в контуре
агентства и не видна застройщику (client.md).
Назначение
Принять форму «Фамилия, Имя, Отчество (опционально), Телефон, Email (опционально)» и создать карточку клиента
(ClientEntity) с Source = Manual, Status = Active, создателем (OriginAgentUserId, иммутабельно) — текущим
агентом, InvestorUserId = null (человек ещё не привязан к пользователю identity).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent |
| Feature group | clients |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/create-client.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Агент; руководитель агентства может создавать от своего имени) |
| Auth policy / Permissions | permission crm.clients.manage |
| Scope (RBAC) | организация (агентство AgencyCompanyId) |
| Record-level | карточка создаётся с OriginAgentUserId = actor (создатель, иммутабельно); дальнейший доступ к клиенту — по членству агентства (client.md) |
| Tenant isolation | TenantId = AgencyCompanyId из identity claims в каждом запросе (ADR-0052) |
| Audit | create (CreatedByUserId = actor); PII формы (ФИО/телефон/email) в логи не пишется |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/agent/crm/clients |
| Success status | 201 (создано) |
| Idempotency header | не применяется (бизнес-уникальность телефона INV-CLI-1) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required (AgencyCompanyId) | Tenant isolation |
LastName | body | string | Да | 1..128, не пустое после trim | PII: в логи не пишется |
FirstName | body | string | Да | 1..128, не пустое после trim | PII |
MiddleName | body | string? | Нет | 0..128 (отчество опционально) | PII |
PhoneNumber | body | string | Да | нормализация + формат E.164 (^\+[1-9]\d{9,14}$), max 32 | PII; маска +7***NNNN в логах |
Email | body | string? | Нет | формат email, max 256 | PII |
Comment | body | string? | Нет | 0..4000 (свободная заметка агента) | PII |
Модель ответа ClientCardModel
Проекция ClientEntity (дом полей и семантика — client.md); ниже — только состав
карточки, возвращаемой сразу после создания.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | ClientEntity.Id | — | clientId для маршрута /crm/clients/[clientId]. |
Name | FullName | Да | ClientEntity.Name | — | ФИО (VO FirstName/LastName/MiddleName?). |
PhoneNumber | string | Да | ClientEntity.PhoneNumber | актору с доступом — полностью | Нормализованный E.164. |
Email | string? | Нет | ClientEntity.Email | — | Контактный email. |
Status | ClientStatus | Да | ClientEntity.Status | — | Строкой; при создании — Active. |
Source | ClientSource | Да | ClientEntity.Source | — | Строкой; здесь всегда Manual. |
OriginAgentUserId | Guid | Да | ClientEntity.OriginAgentUserId | — | Агент-создатель (actor); иммутабельно. |
InvestorUserId | Guid? | Нет | ClientEntity.InvestorUserId | — | null — не привязан к identity. |
ActiveLeadId | Guid? | Нет | ClientEntity.ActiveLeadId | — | null — активности ещё нет. |
CreatedAt | DateTimeOffset | Да | ClientEntity.CreatedAt | — | Момент создания карточки. |
Алгоритм
Тривиальное создание одного агрегата с бизнес-дедупом по телефону:
- Контекст и доступ. Операцию выполняет агент (permission
crm.clients.manage) в tenant своего агентства (TenantId = AgencyCompanyId); создатель карточки (OriginAgentUserId) — сам агент. - Проверка входа. ФИО (
1..128), телефон в формате E.164, email (если задан) — по формату; при нарушении — ошибка валидации. - Бизнес-правило (INV-CLI-1). Нормализовать телефон в E.164 и проверить: если в агентстве уже есть живая
карточка с этим телефоном — отказ
CRM_CLIENT_PHONE_DUPLICATE. Иначе создать карточкуSource = Manual,Status = Active,OriginAgentUserId = actor(создатель),InvestorUserId = null,ActiveLeadId = null(активности нет, INV-CLI-2 выполнен тривиально);Email/Comment— если заданы. - Что меняется. Появляется карточка клиента. При гонке INV-CLI-1 (параллельная вставка того же телефона)
—
CRM_CLIENT_PHONE_DUPLICATE. - События. Интеграционных Kafka-событий нет: клиентская база — внутренние данные агентства, внешние контексты реагируют на события лидов/сделок (напр. lead-created.md).
- Результат. Карточка созданного клиента → HTTP
201.
Диаграмма
Предусловия и постусловия
Предусловия. Актор аутентифицирован и имеет crm.clients.manage в scope своего агентства; TenantId = AgencyCompanyId присутствует в claims. Бизнес-контекст: живой карточки клиента с этим телефоном в агентстве
ещё нет (иначе INV-CLI-1 отклонит).
Постусловие (успех). В агентстве появилась одна карточка ClientEntity: Status = Active, Source = Manual, OriginAgentUserId = actor (создатель), InvestorUserId = null, ActiveLeadId = null (INV-CLI-2 выполнен тривиально),
телефон нормализован в E.164 и уникален среди живых карточек агентства (INV-CLI-1). Проекция поиска клиентов
пополняется асинхронно (ADR-0059); интеграционных событий не публикуется.
Постусловие (отказ). Состояние не изменилось: ни карточки, ни проекции.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none (Kafka не публикуется) |
| Внешние вызовы | none |
| Проекции / поиск | проекция клиентов (ClientSearchDocument) обновляется проектором (ADR-0059) |
| Уведомления | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | формат ФИО/телефона/email | «Проверьте корректность полей формы» | no retry |
FORBIDDEN | 403 | нет permission crm.clients.manage | «Недостаточно прав» | no retry |
CRM_CLIENT_PHONE_DUPLICATE | 409 | INV-CLI-1: живая карточка с этим PhoneNumber в агентстве (предпроверка или гонка) | «Клиент с таким телефоном уже есть в базе» | no retry |
Совместимость и наблюдаемость
- Новые optional поля тела/ответа — additive, без breaking change; удаление/переименование/смена типа —
breaking (migration + rollout).
ClientStatus/ClientSource— enum строкой; при создании фиксированоActive/Manual. - Logs:
client-created(Id, tenant; телефон/email — маской, ФИО не пишется); metrics:crm_clients_created_total{result}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/.
Acceptance Criteria
- Happy path. Given агент с
crm.clients.manageи без карточки с телефоном+7…1234; WhenPOSTс валидной формой; Then201, карточкаActive/Manual, создатель — агент (OriginAgentUserId = actor), телефон в E.164,InvestorUserIdиActiveLeadId—null. - Дедуп телефона. Given в агентстве уже есть живая карточка с этим телефоном; When создание; Then
409 CRM_CLIENT_PHONE_DUPLICATE, вторая карточка не создаётся. - Гонка вставки. Given два параллельных запроса с одним телефоном прошли pre-check; When оба доходят до
записи; Then создаётся ровно одна карточка, второй запрос —
409 CRM_CLIENT_PHONE_DUPLICATE. - Валидация. Given телефон не в формате E.164 (или пустое ФИО); When создание; Then
400 VALIDATION_FAILED, телефон/ФИО в лог не пишутся. - Доступ. Given актор без
crm.clients.manage; When создание; Then403 FORBIDDEN, карточка не создаётся.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (1): GET /api/agent/crm/deals — Сделки агента (список/поиск)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, INV-CLI-1..4,
ClientCreated): client.md. - Автоматический (lazy) путь создания клиента: create-booking-request.md; почему интеграционных событий здесь нет — lead-created.md (внешние контексты слушают события лидов/сделок).
- README контекста: ../../README.md; tenancy — ADR-0052.