GET /api/developer/crm/deals — Сделки застройщика (список/поиск)
Список всех сделок по проектам застройщика для канбан-доски и реестра сделок ЛК застройщика
(US-DEV-01). Read-only проекция агрегата DealEntity с фильтрами
по проекту/зданию/статусу/этапу и полнотекстовым поиском (FTS по Title + ФИО клиента), сортировкой по
дате создания. Разграничение — по TenantId = DeveloperCompanyId (ADR-0052): застройщик видит только свои
сделки. Мутаций и событий не производит.
Назначение
Отдать менеджеру застройщика постранично сделки его компании (заголовок, ФИО клиента-покупателя, объект,
проект, статус/этап канбана, цена, тип договора и способ оплаты) для доски /crm и карточек-строк реестра.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | developer |
| Feature group | deals |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/list-deals.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Менеджер застройщика) |
| Auth policy / Permissions | crm.deals.view (JWT identity) |
| Scope (RBAC) | организация (застройщик) — TenantId = DeveloperCompanyId |
| Record-level | не применяется: застройщик видит все сделки своего TenantId (лиды/клиентскую базу агентства — нет) |
| Tenant isolation | TenantId в каждом запросе (= DeveloperCompanyId); разграничение применяется автоматически |
| Audit | read (не sensitive): ФИО клиента-покупателя в логи не пишется |
Record-level ограничения внутри тенанта не применяются: застройщик видит все сделки своего TenantId
(= DeveloperCompanyId). Это не доступ к клиентской базе агентства — Client.ClientName приходит join'ом
только по контрагенту конкретной сделки.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/developer/crm/deals |
| Success status | 200 |
| Idempotency header | не применяется (read-only, безопасный метод) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required (DeveloperCompanyId) | Tenant isolation |
Paging | query | PagedQuery | Нет | Take 1..200 (default 20), Offset >= 0 (default 0) | — |
Sort | query | SortQuery<DealSortField>? | Нет | поле — enum CreatedAt(default)/UpdatedAt; direction default Desc | — |
Search | query | string? | Нет | trim, 2..128; полнотекстовый поиск по проекции DealSearchDocument (multi-match, fuzzy) | PII: не в логи |
ProjectId | query | Guid? | Нет | != Guid.Empty; несуществующий/чужой → пустой список (scope+filter), без 404 | — |
BuildingId | query | Guid? | Нет | != Guid.Empty; здание/литер проекта | — |
Status | query | DealStatus? | Нет | enum строкой Application/Booking/Sale/Completed/Cancelled; unknown → ValidationError | — |
StepId | query | Guid? | Нет | != Guid.Empty; этап канбана застройщика (фильтр колонки) | — |
StepGroup | query | PipelineStepGroup? | Нет | enum строкой (Application…Cancelled); быстрый фильтр по системной группе | — |
Сортировка типизирована SortQuery<DealSortField> (enum, не строка); default CreatedAt Desc, tiebreak
Id desc. Основной индекс — (tenant_id, status, created_at desc) (см. deal.md,
«Индексы»). Status/StepGroup/ProjectId/BuildingId/StepId — комбинируемые предикаты (AND).
Модель ответа — страница DealListItemModel
Постраничный контейнер: Items (список DealListItemModel) + Meta
(Total, Take, Offset, HasMore). Поля элемента — проекция DealEntity
(здесь только дельта списка):
DealListItemModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | DealEntity.Id | — | Идентификатор сделки (навигация в карточку). |
Title | string | Да | DealEntity.Title | — | Заголовок карточки («Объект №…, ЖК …»). |
Client | ClientRefModel ({ ClientId: Guid, ClientName: string }) | Да | join crm.clients по DealEntity.ClientId | PII (ФИО): не в логи | Контрагент сделки. |
PropertyObjectId | Guid | Да | DealEntity.PropertyObjectId | — | Объект сделки (логическая ссылка chessboard). |
ProjectId | Guid | Да | DealEntity.ProjectId | — | Проект объекта (фильтр/группировка). |
Status | DealStatus | Да | DealEntity.Status (enum строкой) | — | Application/Booking/Sale/Completed/Cancelled. |
StepId | Guid | Да | DealEntity.StepId | — | Текущий настраиваемый этап канбана застройщика. |
StepGroup | PipelineStepGroup | Да | DealEntity.StepGroup (enum строкой) | — | Системная группа этапа (pipeline-step.md). |
Price | MoneyView | Да | DealEntity.Price | — | Полная стоимость объекта недвижимости (snapshot chessboard). |
ContractType | DealContractType? | Нет | DealEntity.ContractType (enum строкой) | — | Ddu/Dkp; null до перехода в группу Contract. |
PaymentMethod | PaymentMethod? | Нет | DealEntity.PaymentMethod (enum строкой) | — | Способ оплаты (purchase-program.md). |
CreatedAt | DateTimeOffset | Да | DealEntity.CreatedAt | — | Дата создания (ключ сортировки по умолчанию). |
Price наружу — MoneyView (Amount, CurrencyCode, Formatted); все enum — строкой; дом значений —
deal.md.
Алгоритм
Операция только читает — шагов записи, событий и транзакции нет.
- Контекст и доступ. Выполняет менеджер застройщика (
User) в своём tenant (TenantId = DeveloperCompanyId); требуется permissioncrm.deals.view, иначеCRM_DEALS_FORBIDDEN. Параметры — пагинация, сортировка, фильтры и поисковая строка. - Проверка входа.
Paging.Take ∈ 1..200,Offset >= 0;Sort.Field— известное значение enum;Status/StepGroup— известные значения enum (unknown →VALIDATION_FAILED, не «тихий» фильтр);Search—2..128после trim;ProjectId/BuildingId/StepId != Guid.Emptyпри заданных. Существование по id не проверяется — это фильтры списка (несуществующий/чужойProjectId/StepId→ пустая страница, без 404). Scope — только tenant; record-level не применяется. - Что читаем. Сделки застройщика с фильтрами
Status/StepId/StepGroup/ProjectId/BuildingId(AND). При заданномSearch— предварительный отборDealIdчерез поисковую проекциюDealSearchDocument(multi-match поTitle+ ФИО клиента, fuzzy; scope =TenantId, ADR-0059), затем предикатId ∈ ids. Разграничение поTenantId = DeveloperCompanyIdи отсечение soft-deleted применяются автоматически. Пагинация и сортировкаCreatedAt desc → Id desc; опорный индекс —(tenant_id, status, created_at desc)(deal.md, «Индексы»). - Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле — таблица «Модель ответа»;
StepGroupберётся из денормализованной колонкиDealEntity.StepGroup(без join наpipeline_steps);ClientName— из joincrm.clients(застройщик видит только контрагента своих сделок, не клиентскую базу агентства). Enum — строкой,Price → MoneyView. - Результат. Страница
DealListItemModel(Items+Meta { Total, Take, Offset, HasMore }) → HTTP200. Пустой результат —200сItems = [](не 404).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (read-only) |
| External calls | none (данные — своя схема crm; PropertyObjectId/ProjectId/цена — snapshot из chessboard на момент создания сделки) |
| Cache / projections / search | прямой запрос PostgreSQL; при Search — чтение поисковой проекции DealSearchDocument (multi-match, scope по tenant, ADR-0059) |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | Take вне 1..200, неизвестный Status/StepGroup/Sort.Field, Search короче 2, Guid.Empty в фильтре | «Проверьте параметры запроса» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
CRM_DEALS_FORBIDDEN | 403 | нет permission crm.deals.view | «Недостаточно прав» | no retry |
Неизвестное значение Status/StepGroup/Sort.Field — это 400 VALIDATION_FAILED, а не молчаливый пропуск
фильтра. Несуществующий или чужой ProjectId/BuildingId/StepId, напротив, не ошибка: это фильтр списка,
результат — пустая страница 200.
Совместимость и наблюдаемость
- Новые optional поля ответа/фильтры — additive (без breaking); удаление/переименование/смена типа поля или
значения enum — breaking (migration + rollout). Unknown enum во входе —
ValidationError(не игнор). - Logs:
developer deals listed(TenantId, набор фильтров, счётчикTotal; ФИО клиента иSearch— не в логи); metrics:crm_developer_deals_list_total{result}, latency p95 (< 1 c, SLO списков); traces — OTel, correlation черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь).
Acceptance criteria
- Given сделки застройщика, When менеджер запрашивает список без фильтров, Then
200со страницейDealListItemModelв порядкеCreatedAt desc → Id desc+Meta { Total, Take, Offset, HasMore }. - Given заданы
Status/StepId/StepGroup/ProjectId/BuildingId, When GET, Then предикаты комбинируются по AND; пустое пересечение —200сItems = [](не404). - Given
Searchдлиной2..128, When GET, Then предварительный отбор идёт через полнотекстовую проекциюDealSearchDocument(multi-match поTitle+ ФИО клиента, fuzzy, scope =TenantId), затем фильтры; сортировка — по релевантности. - Given неизвестное значение
Status/StepGroup/Sort.Field, When GET, Then400 VALIDATION_FAILED(unknown enum — ошибка, а не «тихий» фильтр). - Given
ProjectId/StepIdнесуществующий или чужой, When GET, Then200с пустой страницей (это фильтр, не сущность). - Given
Takeвне1..200илиSearchкороче 2, When GET, Then400 VALIDATION_FAILED. - Given актор без
crm.deals.view, When GET, Then403 CRM_DEALS_FORBIDDEN; ФИО клиента и строка поиска в логи не пишутся.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (8): GET /api/developer/crm/fixations — Список фиксаций застройщика (+ FTS), GET /api/developer/crm/pipeline-steps — Этапы воронки застройщика, GET /api/developer/crm/purchase-program — Программа покупки застройщика, GET /api/developer/crm/statistics — Статистика ЛК застройщика, GET /api/pin-manager/crm/deals — Сквозной список всех сделок платформы, POST /api/developer/crm/deals/{dealId}/contracts-prepared — Отметить «Договоры подготовлены», POST /api/developer/crm/deals/{dealId}/documents/{documentId}/confirm — Подтвердить документы ДДУ/ДКП, POST /api/developer/crm/deals/{dealId}/documents/request — Запросить документы ДДУ/ДКП
- Поиск (1): DealSearchDocument
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: deal.md (дом полей,
DealStatus/DealContractType/PaymentMethod,Money, индекс(tenant_id,status,created_at desc)); поисковая проекция — ../../search/deals.md (DealSearchDocument, ADR-0059); pipeline-step.md (PipelineStepGroup,StepId), purchase-program.md (PaymentMethod). - События жизненного цикла: deal-created.md, deal-stage-changed.md; соседний список воронки: list-leads.md.
- Контекст: README; tenancy — ADR-0052.