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

GET /api/investor/crm/leads/{leadId} — Карточка лида (инвестор)

Карточка своего лида для инвестора (страница «Мои лиды» ЛК): какой агент ведёт, статус связи, срок жизни лида и список порождённых сделок. Read-only проекция агрегата LeadEntity (lead.md); здесь описана только форма InvestorLeadCardModel и алгоритм её сборки. Это урезанный вид агентской карточки (../agent/get-lead.md): инвестору не показываются заявки на фиксацию и внутренняя аналитика агентства — «шапка хотелок» (LeadWishes), карточка клиента агентства (PII/Client*), счётчики продлений и лента событий лида (о лиде не знает застройщик; фиксации инвестору недоступны — README, раздел «Безопасность»).

Назначение

Отдать инвестору сводку одного своего лида по leadId: ответственный агент (ExecutorAgentUserId — display-данные клиент резолвит в identity), текущий Status, происхождение (Source), срок жизни (ExpiresAt) и компактный список сделок, порождённых из лида, — для перехода в «Мои сделки».

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Инвестор — только свои лиды)
Auth policy / Permissionscrm.leads.view (JWT инвестора); фактический доступ ограничен record-level self
Scope (RBAC)самообслуживание: инвестор видит только записи с PrimaryInvestorUserId = actor (не organization-scope)
Record-levelLeadEntity.PrimaryInvestorUserId = ActorUserId; иначе FORBIDDEN (403) — чужой лид не раскрывается
Tenant isolationЗапрос не tenanted (Lead.TenantId = AgencyCompanyId ≠ актор): чтение инвесторской стороны по явному record-level предикату PrimaryInvestorUserId (ADR-0052); удалённые записи скрыты глобальным фильтром
Auditread (данные принадлежат самому инвестору); телефоны/PII в логи не пишутся
Доступ

Инвестор видит только свои лиды: PrimaryInvestorUserId = ActorUserId. Несовпадение — явный FORBIDDEN (403), без маскировки под «не найдено» (в отличие от агентской карточки). Запрос не tenant-scoped: доступ определяется record-level предикатом PrimaryInvestorUserId, а не TenantId (ADR-0052).

HTTP-контракт

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

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

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

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

Проекция LeadEntity (дом полей — lead.md); ниже — только форма инвесторской карточки. Коллекция Deals живёт внутри контейнера InvestorLeadCardModelограниченный компактный ряд (у лида немного сделок); глубокий постраничный просмотр — отдельный инвесторский список сделок list-deals. Фиксации, Wishes, карточка клиента агентства и лента событий в модель не входят.

ПолеТипОбязательностьИсточникMaskingОписание
LeadIdGuidДаLead.IdИдентификатор лида.
StatusLeadStatus (enum→string)ДаLead.StatusActive/Expired/Archived (lead.md).
SourceLeadSource (enum→string)ДаLead.SourceКак появился лид (заявка/реферал/…); иммутабельно.
ExecutorAgentUserIdGuidДаLead.ExecutorAgentUserIdОтветственный агент; display-данные (ФИО/контакты) клиент резолвит в identity.
ExpiresAtDateTimeOffsetДаLead.ExpiresAtДо какого момента лид активен (срок жизни).
ExpiredAtDateTimeOffset?НетLead.ExpiredAtФакт перевода в Expired worker-ом; null, если не истёк.
CreatedAtDateTimeOffsetДаLead.CreatedAtКогда лид появился.
DealsIReadOnlyList<InvestorLeadDealRefModel>Даcrm.deals (LeadId)Порождённые сделки (может быть пустым).

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

Проекция DealEntity (дом полей, VO Money, enum DealStatusdeal.md); инвесторская дельта — без агентских/застройщических внутренних полей.

ПолеТипОбязательностьИсточникОписание
DealIdGuidДаDeal.IdСделка (переход в «Мои сделки»).
TitlestringДаDeal.TitleЗаголовок карточки (объект недвижимости/ЖК).
StatusDealStatus (enum→string)ДаDeal.StatusApplication/Booking/Sale/Completed/Cancelled (US-INV-03).
PropertyObjectIdGuidДаDeal.PropertyObjectIdОбъект; display-данные — API chessboard.
PriceMoney (owned VO)ДаDeal.PriceСтоимость объекта недвижимости (US-INV-05; контракт VO — deal.md).
UpdatedAtDateTimeOffsetДаDeal.UpdatedAtПоследнее обновление сделки (US-INV-04).

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор с permission crm.leads.view; ActorUserId — из токена.
  2. Проверка входа и предусловий. LeadId != Guid.Empty, иначе VALIDATION_FAILED (400). Существование и record-level — при загрузке.
  3. Что читаем. Лид по LeadId (scope-bypass developer/agency-tenant, ADR-0052). Если лида нет или он удалён → CRM_LEAD_NOT_FOUND. Порождённые сделки — проекцией по LeadId = LeadId && PrimaryInvestorUserId = ActorUserId, порядок UpdatedAt desc. Фиксации, Wishes, лента событий и клиентская карточка агентства не загружаются.
  4. Бизнес-правила и маппинг. Record-level: lead.PrimaryInvestorUserId == ActorUserId; иначе FORBIDDEN (403) — чужой лид не раскрывается. Состояние: карточка доступна в любом Status (Active/Expired/Archived). Маппинг: lead → InvestorLeadCardModel (поля выше, enum-ы строкой), Deals — из проекции шага 3. Переходов состояний нет.
  5. Результат. InvestorLeadCardModel → HTTP 200. Допустима микро-рассинхронизация лида и списка сделок (карточка — сводка, не финансовая консистентность); свежесть — на момент запроса. Лид не найден/удалён → CRM_LEAD_NOT_FOUND (404); чужой лид → FORBIDDEN (403).

Диаграмма

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

ТипДетали
Integration eventsnone (read-only GET)
External callsnone (объект недвижимости/проект в Deal* — снапшоты в crm; display агента — отдельный запрос клиента к identity)
Cache / projections / searchnone (прямое чтение агрегата и проекции сделок)
Notificationsnone
Отдельные запросыглубокий просмотр сделок — инвесторский list-deals; лента событий инвестору не отдаётся.

Ошибки и failure modes

Ошибки

Чужой лид не маскируется под 404: PrimaryInvestorUserId != actor возвращает явный 403 FORBIDDEN. Отсутствующий или удалённый лид — отдельный код 404 CRM_LEAD_NOT_FOUND.

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400LeadId = Guid.Empty«Некорректный идентификатор лида»no retry
FORBIDDEN403лид существует, но PrimaryInvestorUserId != actor (чужой лид)«Нет доступа к лиду»no retry
CRM_LEAD_NOT_FOUND404лида с таким Id нет / удалён«Лид не найден»no retry

Acceptance Criteria

  • Given свой лид, When инвестор запрашивает карточку, Then возвращается InvestorLeadCardModel (Status/Source/ExecutorAgentUserId/ExpiresAt + компактный Deals), ответ 200; фиксации, Wishes, клиентская карточка агентства и лента событий в модель не входят.
  • Given Status ∈ {Active, Expired, Archived}, When запрос, Then карточка читается; ExpiredAt заполнен только для лида, переведённого worker-ом в Expired.
  • Given лид существует, но PrimaryInvestorUserId != actor, When запрос, Then FORBIDDEN (403) — чужой лид не раскрывается.
  • Given лида с таким Id нет или он удалён, When запрос, Then CRM_LEAD_NOT_FOUND (404); пустой LeadIdVALIDATION_FAILED (400).
  • Given у лида ещё нет порождённых сделок, When запрос, Then Deals = [].
  • Given лид и его сделки читаются как сводка, When запрос, Then допустима микрорассинхронизация лида и списка сделок (не финансовая консистентность); список сделок упорядочен UpdatedAt desc и ограничен PrimaryInvestorUserId = actor.

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

  • Новые optional поля InvestorLeadCardModel/InvestorLeadDealRefModel — additive (без breaking change); удаление/переименование/смена типа поля или enum — breaking (migration + rollout клиентов). Enum-поля (LeadStatus/LeadSource/DealStatus) сериализуются строкой; неизвестное значение клиент обязан толерантно трактовать как «прочее», не падать.
  • Logs: investor-lead-card requested (LeadId, ActorUserId, result; PII не пишется); metrics: crm_investor_lead_card_requests_total{result}, latency p95 (< 500 мс, SLO чтений README); traces — OTel, correlation через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/ (будущий путь по конвенции).

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

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

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

  • Сущности: lead.md (дом полей, LeadStatus/LeadSource), deal.md (проекция сделки, VO Money, DealStatus).
  • Агентский аналог (полная карточка с фиксациями/хотелками): ../agent/get-lead.md; порождение лида инвестором: ../public/create-booking-request.md.
  • Истечение срока жизни лида (переход Active → Expired): lead-expired.md (pin.crm.lead-expired.v1). Tenancy/record-level: ADR-0052.
  • Карта областей и события контекста: README контекста.