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

GET /api/agent/crm/fixations/{fixationId} — Карточка фиксации (агент)

Страница фиксации агентской стороны: агент (или руководитель его агентства) открывает поданную заявку на фиксацию и видит, что именно ушло застройщику и чем решилось. Возвращает snapshot ФИО/телефона клиента, эффективные хотелки (override заявки либо актуальная «шапка» лида), лид-основание, статус решения застройщика (DecidedAt/RejectReason) и созданную при одобрении сделку (DealId). Только чтение: домен фиксации — fixation-request.md.

Назначение

Дать агенту карточку одной фиксации по fixationId — иммутабельный снимок того, что подано застройщику, плюс исход. Инвестор фиксаций не видит; застройщик работает со своей стороной (.decide).

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent
Feature groupfixation
Статусdraft
Документdocs/06-services/crm/api/agent/get-fixation.md

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

ПроверкаЗначение
Actor typeUser (Агент — свои; Руководитель агентства — весь AgencyCompanyId; Менеджер ПИН — platform-scope)
Auth policy / Permissionscrm.fixations.view
Scope (RBAC)организация (агентство) — OriginAgencyCompanyId
Record-levelАктор — текущий исполнитель заявки (ExecutorAgentUserId = actor.UserId) либо руководитель/админ компании-владельца агентской стороны (FixationRequestEntity.OriginAgencyCompanyId = actor.CompanyId)
Tenant isolationЗапрос не tenanted по TenantId фиксации (= DeveloperCompanyId ≠ актор): чтение по явному предикату OriginAgencyCompanyId (ADR-0052)
Auditread (не sensitive); телефон клиента в логи — только маской
Доступ

Фиксация принадлежит tenant'у застройщика (TenantId = DeveloperCompanyId), но агентский актор проверяется не по этому tenant, а record-level предикатом: явным сравнением OriginAgencyCompanyId заявки с агентством актора (ADR-0052). Доступ имеет текущий исполнитель заявки (ExecutorAgentUserId = actor.UserId) либо руководитель/админ агентства-владельца (OriginAgencyCompanyId = actor.CompanyId).

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
FixationIdrouteGuidДа!= Guid.Empty
ActorUserIdidentity claimsGuidДаJWT агента
ActorCompanyIdidentity claimsGuidДаAgencyCompanyId агента — сверяется с OriginAgencyCompanyId заявки (record-level)

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

Проекция агрегата; дом полей — fixation-request.md (VO FullNameclient.md; VO LeadWisheslead.md). Ниже — только дельта.

ПолеТипОбязательностьИсточникMaskingОписание
FixationIdGuidДаIdИдентификатор заявки.
StatusFixationStatus (enum строкой)ДаStatusPending/FixedUnique/FixedNonUnique/Rejected/Cancelled.
DeveloperCompanyIdGuidДаTenantIdЗастройщик-адресат.
LeadIdGuidДаLeadIdЛид-основание (переход в карточку лида).
ClientClientRefModel ({ ClientId: Guid, ClientName: FullName })ДаClientId + ClientName (snapshot)Клиент фиксации; ФИО — snapshot на момент подачи (иммутабельно), карточка — ClientId.
ClientPhoneNumberstringДаClientPhoneNumber (snapshot)телефон — маска в логахТелефон клиента на момент подачи (E.164).
WishesLeadWishes (owned)Даcomputed: WishesOverride ?? Lead.WishesЭффективные хотелки для показа.
WishesSourceWishesSource (enum строкой)ДаcomputedOverride (снимок заявки) / Lead (актуальная «шапка» лида).
UniquenessFixationUniqueness (enum строкой)ДаUniquenessUnknown/NoDuplicates/HasDuplicates (результат авто-проверки застройщика).
DecidedByUserIdGuid?НетDecidedByUserIdКто одобрил/отклонил; null — решения нет.
DecidedAtDateTimeOffset?НетDecidedAtМомент решения застройщика.
RejectReasonstring?НетRejectReasonПричина отклонения (при Status = Rejected).
CancelledAtDateTimeOffset?НетCancelledAtМомент отмены агентом.
DealIdGuid?НетDealIdСделка, созданная при одобрении (deal.md); null — до одобрения.
CreatedAtDateTimeOffsetДаCreatedAtПодача заявки.
Не показывается

Дубль-детект (DuplicateOfFixationId/DuplicateMatchKind) — сторона застройщика (.decide); в агентской карточке не раскрывается.

Алгоритм

  1. Контекст и доступ. Операцию выполняет агент (permission crm.fixations.view) в рамках своего агентства.
  2. Проверка входа. FixationId != Guid.Empty — иначе ошибка валидации. Существование и доступ проверяются при чтении (шаги 3–4).
  3. Что читаем. Заявку (агентская сторона — по явному предикату, у актора нет tenant застройщика); если нет/ soft-deleted → CRM_FIXATION_NOT_FOUND. Если WishesOverride == null — лид-основание (для чтения актуальной Lead.Wishes); иначе лид не загружается.
  4. Маппинг и правила доступа. Record-level: актор — текущий исполнитель (ExecutorAgentUserId = actor.UserId) либо руководитель/админ компании-владельца (OriginAgencyCompanyId = actor.CompanyId) — иначе FORBIDDEN (403, чужая агентская сторона не раскрывается). Вычислить Wishes = WishesOverride ?? Lead.Wishes, WishesSource = WishesOverride != null ? Override : Lead. Остальные поля маппятся 1:1.
  5. Результат. Карточка фиксации → HTTP 200. Ошибки — 404/403/400 (таблица ниже).

Диаграмма

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

ТипДетали
Интеграционные событияnone (read-only)
Внешние вызовыnone
Проекции / поискnone (прямое чтение агрегата и лида)
Уведомленияnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400FixationId = Guid.Empty«Некорректный идентификатор фиксации»no retry
FORBIDDEN403актор не исполнитель заявки и не руководитель/админ fixation.OriginAgencyCompanyId«Нет доступа к фиксации»no retry
CRM_FIXATION_NOT_FOUND404нет фиксации с таким Id (или soft-deleted)«Фиксация не найдена»no retry

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

  • Новые optional поля FixationCardModel — additive (без breaking change); удаление/переименование/смена типа — breaking (migration + rollout). Enum-поля (Status/Uniqueness/WishesSource) сериализуются строкой; клиент обязан толерантно обрабатывать неизвестное значение.
  • Logs: fixation-card requested (FixationId, DeveloperCompanyId, OriginAgencyCompanyId; телефон — маской); metrics: crm_fixation_card_requests_total{result}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path. Given агент своей агентской стороны и существующая фиксация; When GET; Then 200 с FixationCardModel: snapshot ФИО/телефона, эффективные Wishes, статус и исход решения, DealId при одобрении.
  • Wishes из override. Given фиксация с заданным WishesOverride; When GET; Then Wishes = WishesOverride, WishesSource = Override; лид-основание не читается.
  • Wishes из лида. Given WishesOverride == null; When GET; Then Wishes = Lead.Wishes, WishesSource = Lead.
  • Чужое агентство. Given актор не исполнитель и OriginAgencyCompanyId != actor.CompanyId; When GET; Then 403 FORBIDDEN.
  • Не найдена. Given несуществующая или soft-deleted фиксация; When GET; Then 404 CRM_FIXATION_NOT_FOUND.
  • Валидация. Given FixationId = Guid.Empty; When GET; Then 400 VALIDATION_FAILED.
  • Одобрена. Given Status ∈ {FixedUnique, FixedNonUnique}; When GET; Then DealId != null, заданы DecidedAt/DecidedByUserId.
  • Отклонена. Given Status = Rejected; When GET; Then заданы RejectReason и DecidedAt, DealId == null.

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

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

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

  • Сущности: fixation-request.md (дом полей, VO LeadWishes override), lead.md (актуальная «шапка» хотелок), client.md (VO FullName), deal.md (сделка из одобрения).
  • Создание заявки — create-booking-request.md (соседний flow воронки агента).
  • ADR: 0052 tenancy (двухсторонние сущности, record-level).