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

UC-CRM-019. Агент просматривает каталог в контексте инвестора активного лида

Назначение

Описывает сценарий: агент, работая с активным лидом на инвестора, просматривает объекты недвижимости в своих рабочих списках (публичный каталог, шахматка проекта, избранное, подборки) и видит поверх каждой карточки контекст своего клиента-инвестора — сколько раз и как долго инвестор смотрел объект, лежит ли объект в избранном (у самого агента и/или у инвестора — 3-state) и в какие подборки инвестора он входит. Это помогает агенту вести диалог «по интересам клиента»: понимать, что инвестор уже смотрел и отбирал, и предлагать релевантные объекты.

Ключевой принцип (ADR-0056, Часть D): каталог остаётся платформенной проекцией без персонализации — публичные методы каталога/избранного/подборок и их карточки не меняются. Обогащение «в контексте инвестора» живёт композицией в аутентифицированном CRM-слое: фронт агента вызывает публичные списки и параллельно — пакетный CRM-метод контекста по списку PropertyObjectId, затем накладывает контекст оверлеем поверх уже отрисованных карточек. Авторизация — делегированная по активному лиду (паттерн UC-ACT-004): право «активный лид агента на инвестора» проверяет сам crm перед вызовом internal-источников; источники доверяют переданному userId и повторно его не перепроверяют.

Metadata

ПолеЗначение
IDUC-CRM-019
Контекстcrm (композиция; источники — user-activity / favorites / selections)
Типосновной (+ A1 руководитель/Менеджер ПИН, A2 нет активного лида, A3 нет доступа к лиду, A4 объект без контекста, A5 деградация источника, A6 eventual-свежесть просмотров)
Статусdraft

Актор

ПолеЗначение
РольАгент-исполнитель / куратор лида (через crm); руководитель агентства и Менеджер ПИН — A1
Permissions / scopecrm.leads.view; scope — организация (агентство): TenantId = AgencyCompanyId; руководитель — весь свой TenantId; Менеджер ПИН — платформа
Tenantконтекст собирается в TenantId актора; lead.TenantId обязан совпасть
Record-levelдоступ = актор ∈ активные участники агентской роли лида (ExecutorAgentUserId = actor / членство) или руководитель компании-владельца или Менеджер ПИН; иначе CRM_LEAD_FORBIDDEN (A3)

Предусловие

  • Агент авторизован в crm, имеет permission crm.leads.view, и у него есть активный лид с основным инвестором (../domain/lead.md, INV-LEAD-7) — иначе A2/A3.
  • Основной инвестор лида идентифицирован по PrimaryInvestorUserId (производная пары «исполнитель ↔ основной инвестор», INV-CRM-001).
  • Публичные проекции каталога/избранного/подборок доступны (объекты отрисовываются платформенно, без персонализации).
  • Источники контекста (user-activity, favorites, selections) доступны; при недоступности отдельного — деградация секции оверлея (A5), без отказа сценария.

Триггер

ПолеЗначение
ИсточникUI navigation (в crm/агентском портале) → публичные списки + пакетная композиция контекста
Триггерящий элементОткрытие списка объектов «в контексте лида»: каталог /lots, шахматка проекта /hotels, избранное /favorites, подборки /collection — с активным лидом инвестора в рабочем контексте

Основной Поток

  1. Агент выбирает активный лид как рабочий контекст и открывает список объектов (каталог/шахматка/избранное/ подборки).
  2. Фронт агента загружает платформенную проекцию списка публичными методами каталога/избранного/подборок (без персонализации, ADR-0056 Часть D) и получает набор карточек с их PropertyObjectId.
  3. Параллельно фронт вызывает пакетный CRM-метод контекста POST /api/agent/crm/leads/{leadId}/property-object-context со списком PropertyObjectIds видимых карточек (батч 1..200).
  4. Backend crm:
    • проверяет record-level авторизацию по лиду (актор — участник агентской роли / руководитель / Менеджер ПИН) в рамках TenantId; иначе CRM_LEAD_FORBIDDEN (A3);
    • резолвит PrimaryInvestorUserId из лида (INV-LEAD-7);
    • по доверенному userId делегированно и пакетно дозагружает три internal-источника по PropertyObjectIds:
      • просмотры инвестораuser-activity: { Count, TotalDurationSeconds, LastViewedAt } по объекту (только закрытые просмотры; открытые не входят);
      • 3-state избранногоfavorites по паре владельцев [ActorUserId, PrimaryInvestorUserId]: FavoritedByAgent / FavoritedByInvestor — независимые флаги как наличие строки Favorite соответствующего владельца (Note не запрашивается и не отдаётся кросс-владельца);
      • подборки инвестораselections: список подборок (SelectionId, Title), в которые объект входит.
  5. crm сшивает три источника по PropertyObjectId и возвращает PropertyObjectInvestorContextListModel — по одному элементу на каждый запрошенный (после дедупликации) объект, включая объекты с пустым контекстом (нули/false/пустой список). Состояние не меняется — доменных/интеграционных событий не поднимается; побочный эффект — read-sensitive access-audit (actor, PrimaryInvestorUserId, без PII).
  6. Фронт агента накладывает вернувшийся контекст оверлеем поверх ранее отрисованных платформенных карточек, сопоставляя по PropertyObjectId:
    • бейдж «Инвестор смотрел N раз · Mм» (из InvestorViews; нет просмотров — бейдж не показывается);
    • индикатор избранного 3-state: «не в избранном» (нет ни у кого) / «у меня» (FavoritedByAgent) / «у инвестора» (FavoritedByInvestor) — флаги независимы, могут гореть оба;
    • метки подборок инвестора, в которые входит объект (InInvestorSelections).
  7. Агент при скролле/пагинации/смене фильтров публичного списка повторяет шаги 2–6 для новой порции видимых PropertyObjectId — контекст дозагружается для актуального набора карточек.

Альтернативные Потоки

A1. Руководитель агентства / Менеджер ПИН
  1. Актор — руководитель компании-владельца лида (весь свой TenantId) или Менеджер ПИН (platform-scope). Record-level по лиду проходит по членству/руководству/платформе; целевой инвестор — тот же PrimaryInvestorUserId лида. Read-sensitive audit фиксирует PrimaryInvestorUserId. Возврат к основному потоку с шага 4.
A2. Нет активного лида в контексте
  1. Агент открывает каталог без выбранного активного лида. Фронт показывает только платформенную проекцию (каталог/избранное/подборки без персонализации) и не вызывает CRM-метод контекста — обогащение недоступно, каталог работает как обычная площадка. Сценарий завершается без контекста инвестора.
A3. Нет доступа к лиду
  1. Актор не участник агентской роли лида / не руководитель / не Менеджер ПИН, либо лид не в его TenantId. crm отклоняет запрос контекста — CRM_LEAD_FORBIDDEN (403) / CRM_LEAD_NOT_FOUND (404) — и не вызывает internal-источники. Платформенные карточки остаются видны, оверлей контекста не накладывается.
A4. Объект без контекста инвестора
  1. Часть объектов списка инвестор не смотрел, не отмечал в избранном и не добавлял в подборки. Для них ответ содержит элемент с пустым контекстом (Count = 0, LastViewedAt = null, оба флага false, InInvestorSelections = []). Фронт не показывает по ним бейджей — карточка выглядит как обычная платформенная. Индикатор избранного — состояние «не в избранном».
A5. Деградация источника контекста
  1. Один из internal-источников (user-activity/favorites/selections) недоступен. CRM-метод возвращает 200 с «пустой» секцией недоступного источника (просмотры — нули, флаги — false, подборки — пусто), а остальные секции — заполнены (graceful degradation, как в UC-ACT-004 A5). Фронт накладывает оверлей по пришедшим данным. Полная недоступность лида/CRM — не деградация, а отказ (CRM_LEAD_*).
A6. Свежесть просмотров (eventual)
  1. Инвестор только что открыл объект — просмотр ещё не закрыт polling-циклом worker-а user-activity (10–15 мин, ADR-0056). Такой открытый просмотр в InvestorViews не учитывается (Count/TotalDurationSeconds отстают) — не ошибка; при следующем открытии списка бейдж догонит фактическую активность. Избранное и подборки — актуальны на момент чтения соответствующих источников.

Постусловие

  • Состояние не изменилось (read-only: ни мутаций, ни доменных, ни интеграционных событий). Публичные проекции каталога/избранного/подборок не персонализированы — обогащение существует только оверлеем в агентском UI (ADR-0056 Часть D).
  • Агент увидел контекст только основного инвестора данного активного лида; чужой контекст недоступен ни при каких параметрах запроса.
  • 3-state избранного выведено из владельца строки Favorite (нет строки → «не в избранном»; агент → «у меня»; инвестор → «у инвестора»; флаги независимы); личная заметка (Note) кросс-владельца не раскрыта.
  • Свежесть eventual: просмотры отстают на polling-цикл worker-а; избранное/подборки — на момент чтения.
  • Зафиксирован read-sensitive access-audit (actor, PrimaryInvestorUserId, без PII) на вызов контекста.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
VALIDATION_FAILED400LeadId = Guid.Empty; PropertyObjectIds пуст / > 200 / содержит Guid.Emptyисправить набор id батча
UNAUTHORIZED401нет/невалидный JWT агентатребуется авторизация
CRM_LEAD_NOT_FOUND404лид не найден по LeadId в tenant (A3)скрыть контекст; каталог работает без обогащения
CRM_LEAD_FORBIDDEN403актор не участник агентской роли / не руководитель / не Менеджер ПИН (A3)скрыть оверлей контекста
— (не ошибка)200объекты без активности/избранного/подборок (A4)элементы с пустым контекстом; бейджи не показываются
— (не ошибка)200недоступен отдельный internal-источник (A5)секция источника пуста; остальной оверлей накладывается

События

Сценарий read-only: доменных и интеграционных событий не поднимает. Просмотры/избранное/подборки инвестора наполняются в других UC (просмотр объекта инвестором, добавление в избранное, сборка подборки) — здесь лишь читаются композицией.

Кросс-сервисные контракты (crm ↔ источники контекста)

Дом контракта и алгоритма — метод композиции get-property-object-investor-context.md; здесь — инварианты сценария:

АспектГарантия
Каталог без персонализациипубличные методы/карточки каталога/избранного/подборок не меняются; обогащение — только оверлей в CRM-слое (ADR-0056 Часть D)
Соответствие 1:1на каждый запрошенный (после дедупликации) PropertyObjectId — ровно один элемент контекста (в т.ч. пустой), для строгого оверлея по id
Делегированная авторизация«активный лид агента на инвестора» проверяет crm; user-activity/favorites/selections доверяют userId и не перепроверяют
3-state избранноговывод из владельца строки Favorite: нет строки → «не в избранном»; агент → «у меня»; инвестор → «у инвестора»; флаги независимы; Note не отдаётся кросс-владельца
Изоляцияконтекст — только основного инвестора данного лида; чужой недоступен при любых параметрах
Деградациянедоступность отдельного источника → пустая секция, 200 (A5); недоступность лида/CRM → отказ CRM_LEAD_*
Свежестьпросмотры — eventual (polling worker-а user-activity, 10–15 мин, ADR-0056); избранное/подборки — на момент чтения

Acceptance Criteria

#КритерийПроверка
AC1При активном лиде агент видит по объекту просмотры инвестора (Count/TotalDurationSeconds/LastViewedAt) оверлеем; каталог загружен платформенной проекцией без персонализациитест композиции: публичный список + контекст-метод
AC2Индикатор избранного 3-state: «не в избранном» / «у меня» / «у инвестора»; флаги независимы (могут гореть оба); Note инвестора агенту не отдаётсятест 3-state независимости + отсутствие Note
AC3По объекту показываются подборки инвестора (InInvestorSelections), в которые он входитконтракт-тест подборок
AC4Контекст — только основного инвестора данного лида; чужой контекст не отдаётся ни при каких параметрахтест изоляции record-level (обязателен)
AC5Нет активного лида в контексте → фронт не вызывает контекст-метод, показывает чистую платформенную проекцию (A2)тест «каталог без лида»
AC6Актор без доступа к лиду → 403 CRM_LEAD_FORBIDDEN / 404 CRM_LEAD_NOT_FOUND, источники не вызываются, оверлей не накладывается (A3)тест delegated-авторизации
AC7Объект без активности/избранного/подборок → элемент с пустым контекстом (нули/false/[]), бейджей нет (A4)тест пустого контекста
AC8Недоступность отдельного источника → 200, его секция пуста, остальной оверлей накладывается (A5)тест graceful degradation
AC9Только что открытый (не закрытый) просмотр инвестора может не отразиться в InvestorViews — eventual, не ошибка (A6)тест свежести просмотров

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

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

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

  • Метод композиции (дом контракта+алгоритма): ../api/agent/get-property-object-investor-context.md (3-state избранного, InvestorViews, SelectionRef; делегированная авторизация по активному лиду).
  • Делегированные источники контекста (internal m2m): просмотры — get-user-property-object-activity; избранное (3-state, Note не раскрывается) — get-favorite-status, favorite; подборки — list-selections-by-owner.
  • Паттерн делегированной авторизации по активному лиду и агрегированная активность: UC-ACT-004, UC-UAC-001.
  • Сущность-дом лида: ../domain/lead.md (PrimaryInvestorUserId, INV-LEAD-7, INV-CRM-001).
  • Обзор и границы: ../README.md, service-boundaries.md.
  • Решения: ADR-0056 (Часть D — каталог без персонализации, обогащение композицией; polling-свежесть просмотров).