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

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 areaagent
Feature groupclients
Статусdraft
Документdocs/06-services/crm/api/agent/create-client.md

Актор и доступ

Доступ
ПроверкаЗначение
Actor typeUser (Агент; руководитель агентства может создавать от своего имени)
Auth policy / Permissionspermission crm.clients.manage
Scope (RBAC)организация (агентство AgencyCompanyId)
Record-levelкарточка создаётся с OriginAgentUserId = actor (создатель, иммутабельно); дальнейший доступ к клиенту — по членству агентства (client.md)
Tenant isolationTenantId = AgencyCompanyId из identity claims в каждом запросе (ADR-0052)
Auditcreate (CreatedByUserId = actor); PII формы (ФИО/телефон/email) в логи не пишется

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/agent/crm/clients
Success status201 (создано)
Idempotency headerне применяется (бизнес-уникальность телефона INV-CLI-1)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired (AgencyCompanyId)Tenant isolation
LastNamebodystringДа1..128, не пустое после trimPII: в логи не пишется
FirstNamebodystringДа1..128, не пустое после trimPII
MiddleNamebodystring?Нет0..128 (отчество опционально)PII
PhoneNumberbodystringДанормализация + формат E.164 (^\+[1-9]\d{9,14}$), max 32PII; маска +7***NNNN в логах
Emailbodystring?Нетформат email, max 256PII
Commentbodystring?Нет0..4000 (свободная заметка агента)PII

Модель ответа ClientCardModel

Проекция ClientEntity (дом полей и семантика — client.md); ниже — только состав карточки, возвращаемой сразу после создания.

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаClientEntity.IdclientId для маршрута /crm/clients/[clientId].
NameFullNameДаClientEntity.NameФИО (VO FirstName/LastName/MiddleName?).
PhoneNumberstringДаClientEntity.PhoneNumberактору с доступом — полностьюНормализованный E.164.
Emailstring?НетClientEntity.EmailКонтактный email.
StatusClientStatusДаClientEntity.StatusСтрокой; при создании — Active.
SourceClientSourceДаClientEntity.SourceСтрокой; здесь всегда Manual.
OriginAgentUserIdGuidДаClientEntity.OriginAgentUserIdАгент-создатель (actor); иммутабельно.
InvestorUserIdGuid?НетClientEntity.InvestorUserIdnull — не привязан к identity.
ActiveLeadIdGuid?НетClientEntity.ActiveLeadIdnull — активности ещё нет.
CreatedAtDateTimeOffsetДаClientEntity.CreatedAtМомент создания карточки.

Алгоритм

Тривиальное создание одного агрегата с бизнес-дедупом по телефону:

  1. Контекст и доступ. Операцию выполняет агент (permission crm.clients.manage) в tenant своего агентства (TenantId = AgencyCompanyId); создатель карточки (OriginAgentUserId) — сам агент.
  2. Проверка входа. ФИО (1..128), телефон в формате E.164, email (если задан) — по формату; при нарушении — ошибка валидации.
  3. Бизнес-правило (INV-CLI-1). Нормализовать телефон в E.164 и проверить: если в агентстве уже есть живая карточка с этим телефоном — отказ CRM_CLIENT_PHONE_DUPLICATE. Иначе создать карточку Source = Manual, Status = Active, OriginAgentUserId = actor (создатель), InvestorUserId = null, ActiveLeadId = null (активности нет, INV-CLI-2 выполнен тривиально); Email/Comment — если заданы.
  4. Что меняется. Появляется карточка клиента. При гонке INV-CLI-1 (параллельная вставка того же телефона) — CRM_CLIENT_PHONE_DUPLICATE.
  5. События. Интеграционных Kafka-событий нет: клиентская база — внутренние данные агентства, внешние контексты реагируют на события лидов/сделок (напр. lead-created.md).
  6. Результат. Карточка созданного клиента → 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_FAILED400формат ФИО/телефона/email«Проверьте корректность полей формы»no retry
FORBIDDEN403нет permission crm.clients.manage«Недостаточно прав»no retry
CRM_CLIENT_PHONE_DUPLICATE409INV-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; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path. Given агент с crm.clients.manage и без карточки с телефоном +7…1234; When POST с валидной формой; Then 201, карточка Active/Manual, создатель — агент (OriginAgentUserId = actor), телефон в E.164, InvestorUserId и ActiveLeadIdnull.
  • Дедуп телефона. 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 создание; Then 403 FORBIDDEN, карточка не создаётся.

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

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

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

  • Сущность (дом полей, INV-CLI-1..4, ClientCreated): client.md.
  • Автоматический (lazy) путь создания клиента: create-booking-request.md; почему интеграционных событий здесь нет — lead-created.md (внешние контексты слушают события лидов/сделок).
  • README контекста: ../../README.md; tenancy — ADR-0052.