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

GET /api/pin-manager/crm/deals — Сквозной список всех сделок платформы

Отдаёт Менеджеру ПИН (и Службе заботы — read-only) сквозной постраничный список всех сделок платформы поверх всех застройщиков и агентств: без tenant-ограничений, с фильтрами по агенту/застройщику/статусу/объекту и владельческими колонками (US-MGR-01). Read-only проекция агрегата DealEntity.

Назначение

Отдаёт Менеджеру ПИН (и Службе заботы — read-only) постранично все сделки платформы поверх всех застройщиков и агентств, без tenant-ограничений, с фильтрами по агенту, застройщику, статусу и объекту и явными владельческими колонками (DeveloperCompanyId, OriginAgencyCompanyId, ExecutorAgentUserId, PrimaryInvestorUserId) — чтобы понимать «чья сделка» без принадлежности к одному tenant (US-MGR-01: Менеджер ПИН — авто-участник и наблюдатель всех сделок). Только чтение; дом агрегата — DealEntity.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areapin-manager
Feature groupdeals
Статусdraft
Документdocs/06-services/crm/api/pin-manager/list-deals.md

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

ПроверкаЗначение
Actor typeUser (Менеджер ПИН; Служба заботы — read-only арбитраж)
Auth policy / Permissionspermission crm.deals.view, выданный на scope «платформа» (каталог — identity/domain/permission.md)
Scope (RBAC)платформа (кросс-tenant; org/project scope недостаточно)
Record-levelне применяется: сквозная видимость всех сделок всех сторон (US-MGR-01)
Tenant isolationснята для этого запроса: TenantId в запрос не передаётся; выборка сквозная, гейт — только платформенный permission (ADR-0052)
Auditread-sensitive (кросс-tenant просмотр): пишется факт запроса и набор фильтров; ФИО/телефон клиента и номер эскроу в логи не пишутся
Доступ

Tenant isolation для этого запроса снята: TenantId в запрос не передаётся, выборка идёт сквозным доступом поверх всех застройщиков и агентств. Record-level также не применяется — единственный гейт — платформенный permission crm.deals.view (org/project scope недостаточно, ADR-0052).

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/pin-manager/crm/deals
Success status200
Idempotency headerне применяется (read-only, безопасный метод)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
PagingqueryPagedQueryНетTake 1..200 (default 20), Offset >= 0 (default 0)защита от unbounded
SortquerySortQuery<DealSortField>?Нетполе — enum CreatedAt(default)/UpdatedAt/Price; direction default Desc
ExecutorAgentUserIdqueryGuid?Нетфильтр по текущему исполнителю сделки (!= Guid.Empty); несуществующий → пустая страница (не 404)
DeveloperCompanyIdqueryGuid?Нетфильтр по застройщику (= Deal.TenantId, != Guid.Empty)
OriginAgencyCompanyIdqueryGuid?Нетфильтр по агентству-владельцу (атрибуция, != Guid.Empty)
PropertyObjectIdqueryGuid?Нетфильтр по объекту (логическая ссылка chessboard, != Guid.Empty)
StatusqueryDealStatus?Нетenum строкой Application/Booking/Sale/Completed/Cancelled (deal.md); unknown → VALIDATION_FAILED
Searchquerystring?Нетtrim, 2..128; полнотекстовый поиск (multi-match, fuzzy)вход untrusted — не raw-DSL; PII не в логи

Все id-фильтры комбинируются по AND. Сортировка типизирована DealSortField (enum, не строка); default CreatedAt Desc, tiebreak Id desc.

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

Постраничный список: элементы DealListItemModel + метаданные страницы (Total, Take, Offset, HasMore). Элемент — полная проекция DealEntity, включая владельческие колонки DeveloperCompanyId/OriginAgencyCompanyId, которые проекции developer/agent не отдают.

DealListItemModel

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаDealEntity.IdИдентификатор сделки (навигация в карточку).
TitlestringДаDealEntity.TitleЗаголовок карточки («Объект №…, ЖК …»).
ClientClientRefModel ({ ClientId: Guid, ClientName: string })Даклиент по DealEntity.ClientIdPII (ФИО): не в логиКлиент сделки (client.md).
StatusDealStatusДаDealEntity.Status (enum строкой)Application/Booking/Sale/Completed/Cancelled.
StepIdGuidДаDealEntity.StepIdТекущий настраиваемый этап канбана застройщика.
StepGroupPipelineStepGroupДаDealEntity.StepGroup (enum строкой)Системная группа этапа (pipeline-step.md).
PropertyObjectIdGuidДаDealEntity.PropertyObjectIdОбъект сделки (логическая ссылка chessboard).
ProjectIdGuidДаDealEntity.ProjectIdПроект объекта.
PriceMoneyViewДаDealEntity.Price (Money)Полная стоимость объекта недвижимости (snapshot; Amount,CurrencyCode,Formatted).
ContractTypeDealContractType?НетDealEntity.ContractType (enum строкой)Ddu/Dkp; null до группы Contract.
PaymentMethodPaymentMethod?НетDealEntity.PaymentMethod (enum строкой)Способ оплаты (purchase-program.md).
DeveloperCompanyIdGuidДаDealEntity.TenantIdЗастройщик-владелец сделки (кросс-tenant разрез, US-MGR-01).
OriginAgencyCompanyIdGuidДаDealEntity.OriginAgencyCompanyIdАгентство-владелец сделки (иммутабельная атрибуция; кросс-tenant разрез).
ExecutorAgentUserIdGuidДаDealEntity.ExecutorAgentUserIdТекущий агент-исполнитель сделки (получатель комиссии).
PrimaryInvestorUserIdGuidДаDealEntity.PrimaryInvestorUserIdОсновной инвестор сделки (возможны соинвесторы InvestorSecondary).
CreatedAtDateTimeOffsetДаDealEntity.CreatedAtДата создания (ключ сортировки по умолчанию).
UpdatedAtDateTimeOffsetДаDealEntity.UpdatedAtДата последнего обновления (US-INV-04).
BookingPaymentStatusBookingPaymentStatus?Нетстатус оплаты активной брони (DealEntity.BookingId)Статус оплаты брони строкой (booking.md); null — активной брони нет.

EscrowAccountNumber и данные фиксаций в список не входят (Sensitive; карточка сделки — отдельная операция). Все enum — строкой; дом значений — deal.md.

Алгоритм

  1. Контекст и доступ. Операцию выполняет Менеджер ПИН с permission crm.deals.view на scope «платформа»; если платформенного права нет → CRM_DEALS_PLATFORM_SCOPE_REQUIRED. TenantId в запрос не передаётся (кросс-tenant).
  2. Проверка входа и предусловий. Paging.Take ∈ 1..200, Offset >= 0; Sort.Field — известное значение enum; Status — известное значение enum (unknown → VALIDATION_FAILED, не «тихий» фильтр); Search2..128 после trim; ExecutorAgentUserId/DeveloperCompanyId/OriginAgencyCompanyId/PropertyObjectId != Guid.Empty при заданных. Существование по id не проверяется — это фильтры списка (несуществующий id → пустая страница, без 404). Tenant/record-level предикаты не применяются (сквозной охват — замысел US-MGR-01).
  3. Что читаем. Сделки по составленному предикату (опциональные фильтры ExecutorAgentUserId, DeveloperCompanyId (= TenantId), OriginAgencyCompanyId, PropertyObjectId, Status комбинируются по AND) сквозным доступом (без tenant-предиката; soft-deleted отсекаются). При заданном Search — предварительный отбор идентификаторов сделок полнотекстовым поиском по проекции DealSearchDocument (multi-match, fuzzy; platform-scope, ADR-0059), затем ограничение выборки этими id. Порядок — по Sort.Field (CreatedAt/UpdatedAt/Price), tiebreak по Id; постранично. Для каждой строки — ClientName клиента и BookingPaymentStatus активной брони.
  4. Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле — таблица «Модель ответа»; StepGroup — из денормализованной колонки сделки; DeveloperCompanyId = DealEntity.TenantId. Enum сериализуются строкой.
  5. Результат. Постраничный список DealListItemModel + метаданные (Total, Take, Offset) → HTTP 200. Пустой результат — 200 с пустым списком (не 404); эскроу/фиксации не отдаются.

Диаграмма

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

ТипДетали
Поискпри Search — чтение полнотекстовой проекции DealSearchDocument (platform-scope, ADR-0059)
Внешние вызовынет (данные — своя схема crm; объект/проект/цена — snapshot из chessboard на момент создания сделки)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400Take вне 1..200, неизвестный Status/Sort.Field, Search короче 2, Guid.Empty в фильтре«Проверьте параметры запроса»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
CRM_DEALS_PLATFORM_SCOPE_REQUIRED403у актора нет permission crm.deals.view на scope «платформа» (org/project scope недостаточно для кросс-tenant)«Недостаточно прав для сквозного просмотра»no retry
Важно

Неизвестное значение Status/Sort.Field — это 400 VALIDATION_FAILED, а не молчаливый пропуск фильтра. Несуществующий ExecutorAgentUserId/DeveloperCompanyId/OriginAgencyCompanyId/PropertyObjectId, напротив, не ошибка: это фильтр списка, результат — пустая страница 200 (не 404).

Предусловия и постусловия

Предусловия (истинны до сценария):

  • актор — Менеджер ПИН (или Служба заботы, read-only) с permission crm.deals.view на scope «платформа»; org/project-scope недостаточно для кросс-tenant;
  • при заданном Search используется Elasticsearch-проекция DealSearchDocument (ADR-0059), обновляемая асинхронно после мутаций сделки.

Постусловия (после запроса):

  • состояние не изменяется (read-only); кросс-tenant просмотр логируется (read-sensitive audit: набор фильтров, Total);
  • возвращена сквозная страница сделок с владельческими колонками (DeveloperCompanyId/OriginAgencyCompanyId/ExecutorAgentUserId/PrimaryInvestorUserId); EscrowAccountNumber и данные фиксаций не отдаются.

Кросс-сервисные ожидания (eventual-согласованность поиска). Без Search — прямой запрос PostgreSQL по фильтрам (актуально на момент запроса). С Search — предварительный отбор id по FTS-проекции DealSearchDocument, индексируемой асинхронно: только что созданная/переименованная сделка может не попасть в результаты поиска в течение окна индексации (секунды); фильтры и сортировка применяются к отобранным id.

Acceptance Criteria

  • Happy path. Дано: сделки нескольких застройщиков и агентств. Когда: GET …/deals без фильтров. Тогда: 200, сквозная страница, порядок CreatedAt Desc (tie-break Id), заполнены владельческие колонки; эскроу/фиксации не отдаются.
  • Комбинация фильтров (AND). Когда: executorAgentUserId=A&status=Sale. Тогда: только сделки с текущим исполнителем A в статусе Sale.
  • Несуществующий фильтр-id. Когда: developerCompanyId без совпадений. Тогда: 200 c пустой страницей (не 404).
  • Поиск. Дано: сделка проиндексирована в DealSearchDocument. Когда: search=Иванов. Тогда: 200, отбор по FTS, порядок по релевантности; только что созданная сделка может отсутствовать до завершения индексации (окно eventual-consistency).
  • Неизвестный enum/короткий поиск. Когда: status=Foo, sort.field=Bar или search короче 2. Тогда: 400 VALIDATION_FAILED.
  • Пустой результат. Когда: фильтры не дали совпадений. Тогда: 200 c пустым списком.
  • Нет платформенного права. Когда: у актора crm.deals.view только на org/project-scope. Тогда: 403 CRM_DEALS_PLATFORM_SCOPE_REQUIRED.

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

  • Новые optional поля ответа/фильтры — additive (без breaking); удаление/переименование/смена типа поля или значения enum — breaking (migration + rollout). Unknown enum во входе — VALIDATION_FAILED (не игнор). DealStatus/PipelineStepGroup/BookingPaymentStatus — enum строкой; неизвестное значение наружу не отдаётся.
  • Logs: pin-manager all-deals listed (набор фильтров, Total, кросс-tenant флаг для аудита; ФИО клиента и Search — не в логи); metrics: crm_all_deals_list_total{result}, latency p95 < 1 c (SLO списков); traces — сквозной trace через traceparent; dashboard pin-crm.

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

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

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