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

GET /api/agent/crm/leads/{leadId} — Карточка лида

Карточка пути клиента для агента (страница лида /crm/leads/[leadId], UC-1..3): «шапка хотелок» инвестора, клиент, срок жизни лида и списки порождённых фиксаций и сделок. Read-only проекция агрегата LeadEntity (lead.md). Лента событий лида на карточке не разворачивается — она грузится отдельным пагинированным методом list-lead-events.md, чтобы карточка оставалась лёгкой.

Назначение

Отдать агенту сводную карточку одного лида: агрегированные «хотелки» (LeadWishes), реквизиты клиента, ExpiresAt/ExtensionsCount (сколько осталось жить и сколько раз продлевали) и компактные списки фиксаций/сделок, порождённых из лида, — для навигации в дочерние процессы.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent
Feature groupleads
Статусdraft
Документdocs/06-services/crm/api/agent/get-lead.md

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

ПроверкаЗначение
Actor typeUser (Агент; руководитель агентства — весь TenantId; Менеджер ПИН — platform-scope)
Auth policy / Permissionscrm.leads.view
Scope (RBAC)организация (агентство): TenantId = AgencyCompanyId
Record-levelагент — активный участник (Executor: ExecutorAgentUserId = actor); руководитель агентства — любой лид своего TenantId; несоответствие маскируется под CRM_LEAD_NOT_FOUND (без enumeration)
Tenant isolationTenantId в каждом запросе; soft-deleted — глобальный фильтр
Auditread (не sensitive-read: PII клиента доступна владеющему агенту штатно)
Доступ

Рядовой агент видит только свои лиды (ExecutorAgentUserId = actor); руководитель агентства — любой лид своего TenantId; Менеджер ПИН — сквозной доступ (platform-scope). Любое несоответствие (чужой лид, другой TenantId, soft-deleted) маскируется под CRM_LEAD_NOT_FOUND — без enumeration.

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/agent/crm/leads/{leadId}
Success status200
Idempotency headerне применяется (безопасный GET)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequiredTenant isolation
ActorUserIdidentity claimsGuidДаRequiredRecord-level (агент/руководитель)
LeadIdrouteGuidДа!= Guid.Empty; существующий лид

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

Проекция LeadEntity (дом полей — lead.md); ниже — только форма карточки. Списки Fixations/Dealsограниченные компактные ряды (у лида немного дочерних процессов); глубокий пагинированный просмотр — отдельные agent-списки фиксаций/сделок. Лента событий — отдельный метод list-lead-events.md.

ПолеТипОбязательностьИсточникMaskingОписание
LeadIdGuidДаLead.IdИдентификатор лида.
StatusLeadStatus (enum→string)ДаLead.StatusActive/Expired/Archived (lead.md).
SourceLeadSource (enum→string)ДаLead.SourceКак появился лид.
ClientLeadClientModelДаClientEntity (Lead.ClientId)см. нижеРеквизиты клиента (шапка).
WishesLeadWishesModelДаLead.Wishes (owned VO)«Шапка хотелок»; все поля nullable.
ExpiresAtDateTimeOffsetДаLead.ExpiresAtСрок жизни лида.
ExtensionsCountintДаLead.ExtensionsCountСколько раз продлевали.
LastExtendedAtDateTimeOffset?НетLead.LastExtendedAtПоследнее продление.
ExpiredAtDateTimeOffset?НетLead.ExpiredAtФакт перевода в Expired (worker).
LastEventAtDateTimeOffset?НетLead.LastEventAtПоследнее событие ленты (для перехода в list-lead-events).
CreatedAtDateTimeOffsetДаLead.CreatedAtСоздание лида.
FixationsIReadOnlyList<LeadFixationRefModel>Даcrm.fixation_requests (LeadId)Порождённые фиксации (может быть пустым).
DealsIReadOnlyList<LeadDealRefModel>Даcrm.deals (LeadId)Порождённые сделки (может быть пустым).

LeadClientModel — реквизиты клиента

Проекция ClientEntity (дом — client.md); только шапка карточки.

ПолеТипОбязательностьИсточникMaskingОписание
ClientIdGuidДаClient.IdКарточка клиента (/crm/clients/[clientId]).
FullNameFullName (owned VO)ДаClient.NameФИО; контракт VO — client.md.
PhoneNumberstringДаClient.PhoneNumberPII: в логах маска +7***NNNNТелефон клиента (E.164).
InvestorUserIdGuid?НетClient.InvestorUserIdnull — клиент не зарегистрирован (lazy).
StatusClientStatus (enum→string)ДаClient.StatusActive/Archived.

LeadWishesModel — «шапка хотелок»

Форма 1:1 с owned-VO LeadWishes (дом полей/ограничений — lead.md): Budget (NullableRange<decimal>), BudgetCurrencyId (short?, справочник валют handbook), CityId (long?, справочник городов handbook) + CityName (string?, денормализованное display-имя, снятое при записи хотелок), Urgency (LeadUrgency?→string), PurchaseGoal (PurchaseGoal?→string), PreferredPaymentMethod (PaymentMethod?→string), Comment (string?). Enum-ы сериализуются строкой; display-код валюты клиент получает из lookup справочника (lookup-references.md).

LeadFixationRefModel — компактный ряд фиксации

Проекция FixationRequestEntity (дом — fixation-request.md).

ПолеТипОбязательностьИсточникОписание
FixationRequestIdGuidДаFixationRequest.IdЗаявка на фиксацию.
DeveloperCompanyIdGuidДаFixationRequest.TenantIdЗастройщик-адресат.
StatusFixationStatus (enum→string)ДаFixationRequest.StatusСостояние заявки.
UniquenessFixationUniqueness (enum→string)ДаFixationRequest.UniquenessИтог дубль-проверки.
DealIdGuid?НетFixationRequest.DealIdСделка, если одобрено.
CreatedAtDateTimeOffsetДаFixationRequest.CreatedAtПодача.
DecidedAtDateTimeOffset?НетFixationRequest.DecidedAtРешение застройщика.

LeadDealRefModel — компактный ряд сделки

Проекция DealEntity (дом — deal.md).

ПолеТипОбязательностьИсточникОписание
DealIdGuidДаDeal.IdСделка.
TitlestringДаDeal.TitleЗаголовок карточки канбана.
StatusDealStatus (enum→string)ДаDeal.StatusСтатус процесса.
StepGroupPipelineStepGroup (enum→string)ДаDeal.StepGroupСистемная группа текущего этапа.
PropertyObjectIdGuidДаDeal.PropertyObjectIdОбъект (chessboard).
PriceMoney (owned VO)ДаDeal.PriceСтоимость объекта (контракт VO — deal.md).
DeveloperCompanyIdGuidДаDeal.TenantIdЗастройщик.
UpdatedAtDateTimeOffsetДаDeal.UpdatedAtПоследнее обновление.

Алгоритм

  1. Контекст и доступ. Операцию выполняет агент (permission crm.leads.view) в рамках TenantId.
  2. Проверка входа и предусловий.
    • формат: LeadId != Guid.Empty — иначе ошибка валидации (400);
    • лид существует в TenantId (чужой tenant и soft-deleted — как «не найдено») — иначе CRM_LEAD_NOT_FOUND;
    • record-level: рядовой агент видит только свои лиды (ExecutorAgentUserId = actor), иначе CRM_LEAD_NOT_FOUND (без enumeration; руководитель агентства/Менеджер ПИН — проходят);
    • состояние: карточка доступна в любом Status (Active/Expired/Archived).
  3. Что читаем. Лид, его клиента-родителя (шапка), компактные списки порождённых фиксаций (по CreatedAt desc) и сделок (по UpdatedAt desc). Лента событий не грузится (отдельный метод list-lead-events).
  4. Маппинг. Поле-в-поле Lead → LeadCardModel, Lead.Wishes → LeadWishesModel (enum-ы строкой), Client → LeadClientModel (PhoneNumber отдаётся владеющему агенту, в логи — маской), Fixations/Deals — из проекций шага 3.
  5. Результат. Карточка лида → HTTP 200. Лид не найден/не виден → CRM_LEAD_NOT_FOUND404 ProblemDetails. Freshness — на момент запроса; допустима микро-рассинхронизация между списками (карточка — сводка, не финансовая консистентность).

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияnone (read-only)
Внешние вызовыnone (объект/проект в Deal* — снапшоты в crm; синхронного вызова chessboard карта не делает)
Проекции / поискnone
Уведомленияnone

Ошибки и failure modes

Ошибки

Главный гейт — маскировка: чужой лид, лид другого TenantId и soft-deleted лид неотличимы снаружи и все отдаются как CRM_LEAD_NOT_FOUND (без enumeration).

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400LeadId = Guid.Empty«Некорректный идентификатор лида»no retry
CRM_LEAD_NOT_FOUND404лид не существует / другой tenant / soft-deleted / не виден по record-level«Лид не найден»no retry

Совместимость и наблюдаемость

  • Новые optional поля карточки — additive; удаление/переименование/смена типа поля или enum — breaking (migration + rollout клиентов). Неизвестные enum-значения клиент трактует как «прочее», не падает.
  • Logs: lead-card requested (LeadId, TenantId, actor; телефон клиента — маской); metrics: crm_lead_card_requests_total{result}, latency p95 (< 500 мс, SLO чтений README); traces — сквозной trace через traceparent; dashboard pin-crm.

Acceptance Criteria

  • Happy path. Given агент-владелец и существующий лид своего TenantId; When GET; Then 200 с LeadCardModel: «шапка хотелок», реквизиты клиента, ExpiresAt/ExtensionsCount, компактные списки фиксаций и сделок; телефон — владельцу, в логах маска.
  • Руководитель. Given руководитель агентства и лид своего TenantId другого агента; When GET; Then 200.
  • Чужой лид. Given рядовой агент и чужой лид (ExecutorAgentUserId != actor) того же TenantId; When GET; Then 404 CRM_LEAD_NOT_FOUND (без enumeration).
  • Другой tenant. Given лид другого TenantId или soft-deleted; When GET; Then 404 CRM_LEAD_NOT_FOUND.
  • Валидация. Given LeadId = Guid.Empty; When GET; Then 400 VALIDATION_FAILED.
  • Закрытый лид. Given лид в статусе Expired/Archived; When GET; Then 200 (карточка доступна в любом статусе).
  • Без процессов. Given лид без порождённых фиксаций/сделок; When GET; Then Fixations = [] и Deals = [].

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

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

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