GET /api/developer/crm/fixations — Список фиксаций застройщика (+ FTS)
Список заявок на фиксацию со стороны застройщика: менеджер видит только заявки, адресованные его
компании, с фильтрами по статусу/агентству и полнотекстовым поиском по ФИО+телефон, с подсветкой дублей.
Read-only проекция агрегата FixationRequestEntity.
Назначение
Отдать менеджеру застройщика постранично заявки на фиксацию своего TenantId для экрана «Фиксации» ЛК
застройщика — вход в принятие решения (одобрить уникальный/не уникальный / отклонить,
approve/reject). В отличие от
агентской стороны (list-fixations.md), здесь
FixationRequestEntity.TenantId = DeveloperCompanyId совпадает со скоупом актора — разграничение по
tenant применяется автоматически, без scope-bypass (ADR-0052). Каждая строка несёт результат авто-проверки
дублей (Uniqueness, ссылка на дубль-фиксацию, тип совпадения) для подсветки в UI.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | developer |
| Feature group | fixations |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/list-fixations.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Менеджер застройщика) |
| Auth policy / Permissions | crm.fixations.decide (identity RBAC); инвестор доступа не имеет |
| Scope (RBAC) | организация (застройщик) — TenantId = DeveloperCompanyId |
| Record-level | не применяется: застройщик видит все заявки своего TenantId (клиентскую базу/лиды агентства — нет). OriginAgencyCompanyId/OriginAgentUserId — опциональные фильтры, не ограничение |
| Tenant isolation | TenantId в каждом запросе (tenant_id = DeveloperCompanyId) + отсечение soft-deleted; применяются автоматически |
| Audit | read (не sensitive: данные переданы застройщику для фиксации осознанно); в логи — только Id/счётчики, телефон/ФИО не пишутся |
FixationRequestEntity.TenantId = DeveloperCompanyId совпадает со scope актора — в отличие от агентской
проекции того же дома (list-fixations.md), где TenantId заявки не
совпадает со scope актора и требуется явный record-level предикат. Здесь разграничение по tenant применяется
автоматически, без scope-bypass (ADR-0052); поверх tenant-скоупа record-level не применяется — застройщик
видит все заявки своего TenantId (клиентскую базу/лиды агентства — нет).
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/developer/crm/fixations |
| Success status | 200 |
| Idempotency header | не применяется (безопасный read) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required, != Guid.Empty (DeveloperCompanyId) | Tenant isolation |
OriginAgencyCompanyId | query | Guid? | Нет | != Guid.Empty если задан; несуществующий/чужой → пустая страница, без 404 | Фильтр по агентству-инициатору (историческая атрибуция) |
OriginAgentUserId | query | Guid? | Нет | != Guid.Empty если задан | Фильтр по агенту-инициатору (историческая атрибуция) |
Status | query | FixationStatus? | Нет | enum строкой; неизвестное значение → ValidationError | — |
Uniqueness | query | FixationUniqueness? | Нет | enum строкой; быстрый фильтр «только дубли» (HasDuplicates) для рассмотрения | — |
SearchTerm | query | string? | Нет | trim, 1..128 после trim; передаётся как SearchQuery multi-match (fuzzy), не raw-DSL | PII (ФИО/телефон): в логи не пишется |
Paging | query | PagedQuery | Нет | Take ∈ [1..200] (default 20), Offset >= 0 (default 0) | — |
Sort | query | SortQuery<FixationSortField>? | Нет | FixationSortField ∈ {CreatedAt, DecidedAt, Status}; default CreatedAt desc (при поиске — relevance/score desc) | — |
Все фильтры комбинируются по AND; Status/Uniqueness/OriginAgencyCompanyId/OriginAgentUserId — логические предикаты
(отсутствие совпадений → пустая страница, не ошибка). Сортировка типизирована SortQuery<FixationSortField>
(enum, не строка), tiebreak Id desc.
Модель ответа — страница DeveloperFixationListItemModel
Постраничный контейнер: Items (список DeveloperFixationListItemModel) + Meta
(Total, Take, Offset, HasMore). Поля строки — проекция
FixationRequestEntity (здесь только дельта списка):
DeveloperFixationListItemModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | FixationRequestEntity.Id | — | Идентификатор заявки (ключ перехода на карточку рассмотрения). |
ClientName | FullNameView | Да | ClientName (VO FullName, snapshot) | — | ФИО клиента; контракт VO — client.md. |
ClientPhoneNumber | string | Да | ClientPhoneNumber (snapshot, E.164) | +7***NNNN только в логах | Телефон клиента (ключ дубль-проверки; передан застройщику осознанно — в ответе не маскируется). |
OriginAgencyCompanyId | Guid | Да | OriginAgencyCompanyId | — | Агентство-инициатор (историческая атрибуция); display-имя резолвит фронт/identity (не в проекции). |
OriginAgentUserId | Guid | Да | OriginAgentUserId | — | Агент-инициатор заявки (историческая атрибуция). |
Status | FixationStatus | Да | Status | — | enum строкой: Pending/FixedUnique/FixedNonUnique/Rejected/Cancelled (дом). |
Uniqueness | FixationUniqueness | Да | Uniqueness | — | enum строкой: Unknown/NoDuplicates/HasDuplicates (результат авто-проверки дублей). |
DuplicateOfFixationId | Guid? | Нет | DuplicateOfFixationId | — | Активная фиксация-дубль того же TenantId (лучшее совпадение); null при NoDuplicates. Карточку резолвит фронт (self-ссылка, дом). |
DuplicateMatchKind | DuplicateMatchKind? | Нет | DuplicateMatchKind | — | enum строкой: Phone/Name/PhoneAndName — чем сработала подсветка; null при NoDuplicates. |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Подача заявки (ключ сортировки по умолчанию). |
Все enum — строкой; дом значений и правило дубль-детекта — fixation-request.md.
Алгоритм
Операция только читает — шагов записи, событий и транзакции нет.
- Контекст и доступ. Выполняет менеджер застройщика (
User) в своём tenant (TenantId = DeveloperCompanyId); требуется permissioncrm.fixations.decide, иначеCRM_FIXATIONS_FORBIDDEN. Параметры — пагинация, сортировка, фильтры и поисковая строка. - Проверка входа.
Paging.Take ∈ 1..200,Offset >= 0;SearchTerm(если задан) —1..128после trim;Status/Uniqueness/Sort.Field— известные значения enum (unknown →VALIDATION_FAILED, не «тихий» фильтр);OriginAgencyCompanyId/OriginAgentUserId != Guid.Emptyпри заданных. Существование по id не проверяется — это логические фильтры (несуществующий/чужой id → пустая страница, без 404). Scope — только tenant; record-level не применяется. - Что читаем. Заявки на фиксацию застройщика с опциональными фильтрами (AND):
OriginAgencyCompanyId,OriginAgentUserId,Status,Uniqueness. При заданномSearchTerm— предварительный отборFixationRequestIdчерез поисковую проекциюFixationRequestSearchDocument(multi-match по ФИО+телефон, fuzzy; scope =TenantIdзастройщика, ADR-0059), затем предикатId ∈ ids, сортировка по релевантности desc; иначе сортировка изSort(defaultCreatedAt desc → Id desc). Разграничение поTenantId = DeveloperCompanyIdи отсечение soft-deleted применяются автоматически (bypass не нужен, в отличие от агентской стороны). - Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле — таблица
DeveloperFixationListItemModel;DuplicateOfFixationId/DuplicateMatchKind/Uniquenessберутся из уже сохранённого результата авто-проверки (правило детекта — fixation-request.md, «Дубль-детект»), в списке не пересчитываются. Enum — строкой. - Результат. Страница
DeveloperFixationListItemModel(Items+Meta { Total, Take, Offset, HasMore },Total— счётчик того же предиката) → HTTP200. Пустой результат —200сItems = [](не 404). PII:ClientPhoneNumber— в ответе без маски, в логи — толькоId/Total.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (read-only) |
| External calls | none (display-имена агентств/агентов и карточка дубль-фиксации резолвятся отдельно на фронте) |
| Cache / projections / search | при поиске — чтение поисковой проекции FixationRequestSearchDocument (multi-match, scope по tenant, ADR-0059); PostgreSQL-индексы фильтров: ix_fixation_requests_tenant_id, ix_fixation_requests_origin_agency_company_id, ix_fixation_requests_status, ix_fixation_requests_created_at |
| Notifications | none |
Ошибки и failure modes
Несуществующие/чужие значения OriginAgencyCompanyId/OriginAgentUserId/Status/Uniqueness не считаются
ошибкой — это логические фильтры, при отсутствии совпадений GET отдаёт 200 с пустой страницей, без 404.
Ошибкой являются только неизвестные значения enum и нарушения формата входных параметров (см. таблицу ниже).
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | Take/Offset вне диапазона; неизвестный Status/Uniqueness/Sort.Field; SearchTerm > 128; Guid.Empty в фильтре | «Проверьте параметры запроса» | no retry |
CRM_FIXATIONS_FORBIDDEN | 403 | нет permission crm.fixations.decide / нет DeveloperCompanyId в claims | «Недостаточно прав для просмотра фиксаций» | no retry |
UNAUTHORIZED | 401 | нет/невалидный JWT | «Требуется авторизация» | no retry |
Совместимость и наблюдаемость
- Новые optional фильтры/поля ответа — additive (без breaking). Удаление/переименование полей или сужение
enum
FixationStatus/FixationUniqueness/DuplicateMatchKind— breaking (миграция клиента + rollout); неизвестный член enum на клиенте — по правилу unknown-enum (cross-cutting «Совместимость»). - Logs:
developer-fixations listed(TenantId, набор фильтров,Total, флагfts; телефон/ФИО — не пишутся); metrics:crm_developer_fixations_list_total{fts,has_duplicates}, latency p95 (< 1 c, SLO списков); traces — OTel, correlation черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь).
Acceptance criteria
- Given заявки на фиксацию, адресованные застройщику, When менеджер запрашивает список без фильтров,
Then
200со страницейDeveloperFixationListItemModel(CreatedAt desc → Id desc), где каждая строка несёт результат авто-проверки дублей (Uniqueness,DuplicateOfFixationId,DuplicateMatchKind). - Given фильтр
Uniqueness = HasDuplicates, When GET, Then в выдаче только заявки с найденными дублями (для приоритетного рассмотрения). - Given
SearchTermдлиной1..128, When GET, Then предварительный отбор черезFixationRequestSearchDocument(multi-match по ФИО + телефон, fuzzy, scope =TenantIdзастройщика), затем фильтры; сортировка — по релевантности. - Given неизвестное значение
Status/Uniqueness/Sort.Field, When GET, Then400 VALIDATION_FAILED;OriginAgencyCompanyId/OriginAgentUserIdнесуществующий →200пустая страница (это фильтр). - Given результат отдан, When проверяется PII, Then
ClientPhoneNumberв ответе без маски (данные переданы застройщику осознанно), а в логи — толькоId/Total(телефон/ФИО не пишутся). - Given актор без
crm.fixations.decideили безDeveloperCompanyIdв claims, When GET, Then403 CRM_FIXATIONS_FORBIDDEN; без JWT —401.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): GET /api/pin-manager/crm/fixations — Сквозной список всех фиксаций платформы, GET /api/pin-manager/crm/leads — Сквозной список всех лидов
- Use Cases (1): UC-FIX-003. Застройщик отклоняет фиксацию
- Поиск (1): FixationRequestSearchDocument
- registries (1): Реестр API-методов PIN
Связанные документы
- Дом сущности, правило дубль-детекта/FTS и enum (
FixationStatus/FixationUniqueness/DuplicateMatchKind): fixation-request.md; VO ФИО — client.md; лид-основание — lead.md. - Решения по заявкам (не публикуются этим методом): fixation-approved.md, fixation-rejected.md.
- Агентская проекция того же дома: list-fixations.md; соседние методы area developer: list-deals.md, confirm-booking-documents.md; карта областей — README.
- ADR: 0052 tenancy.