POST /api/agent/crm/fixations — Отправить заявку на фиксацию
Агент запрашивает у выбранного застройщика закрепить клиента (ФИО + телефон) за собой до сделки. Точка входа
агентской воронки: заявка создаётся только из лида (lead.md) в статусе Active,
снимает иммутабельный snapshot ФИО/телефона из карточки клиента и выполняет авто-подсветку дублей у застройщика
(полнотекстовый поиск по ФИО + точное совпадение телефона). Дом полей — fixation-request.md.
Назначение
Принять от агента лид-основание, выбранного застройщика и опциональный override хотелок, завести заявку на
фиксацию (FixationRequestEntity) в статусе Pending. Агентские данные (агентство, агент, инвестор, клиент)
берутся из лида; адресат — выбранный застройщик; ФИО/телефон фиксируются snapshot-ом из карточки клиента
(client.md). Инвестор о фиксациях не знает.
INV-FIX-001: у тройки (агент, инвестор, застройщик) не может быть больше одной активной заявки на фиксацию
одновременно (Status ∈ {Pending, FixedUnique, FixedNonUnique}) — попытка создать вторую завершается 409 CRM_FIXATION_ACTIVE_EXISTS, новая заявка не создаётся.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent (JWT агента) |
| Feature group | fixation |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/create-fixation.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Агент); Operator (Менеджер ПИН — platform-scope, подача от имени агента) |
| Auth policy / Permissions | crm.fixations.manage (агентская сторона) |
| Scope (RBAC) | организация (агентство): record-level по лиду |
| Record-level | Актор — активный участник лида (обычно Executor) либо руководитель компании-владельца (AgencyCompanyId = TenantId лида) / Менеджер ПИН |
| Tenant isolation | лид читается в tenant актора (AgencyCompanyId); создаваемая фиксация TenantId = DeveloperCompanyId (застройщик-адресат, ADR-0052) — кросс-tenant запись санкционирована доменной моделью, выборка застройщика идёт по явному предикату |
| Audit | create (CreatedByUserId = actor.UserId); телефон/ФИО клиента в логи не пишутся |
Кросс-tenant запись санкционирована доменной моделью: заявка создаётся в tenant'е выбранного застройщика
(TenantId = DeveloperCompanyId), а актор проверяется не по этому tenant, а record-level предикатом по лиду —
лид читается в tenant агентства актора (AgencyCompanyId), выборка застройщика идёт по явному предикату.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/agent/crm/fixations |
| Success status | 201 (создано) |
| Idempotency header | не применяется (бизнес-защита от дублей — INV-FIX-001 → 409) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
LeadId | body | Guid | Да | != Guid.Empty; существующий лид Status = Active, принадлежит агентству актора | — |
DeveloperCompanyId | body | Guid | Да | != Guid.Empty; существующий застройщик; становится FixationRequestEntity.TenantId | — |
WishesOverride | body | LeadWishes? (owned VO — lead.md) | Нет | поля VO по контракту lead.md (Budget.Min <= Budget.Max; BudgetCurrencyId short? — валюта справочника handbook, не задан → RUB; CityId long? — город справочника, CityName снимается из resolve при записи; enum строкой); null → застройщику показываются Lead.Wishes | PII: Comment целиком в логи не пишется |
ActorUserId | identity claims | Guid | Да | JWT агента; = lead.ExecutorAgentUserId (кроме подачи Менеджером ПИН) | — |
ActorAgencyCompanyId | identity claims | Guid | Да | tenant агентства актора — record-level по лиду | Tenant isolation |
WishesOverride — единственный typed nested-объект тела; его поля не дублируются здесь (дом — VO LeadWishes
в lead.md).
Модель ответа FixationCardModel
Проекция созданной заявки (дельта дома fixation-request.md); агент видит свои же переданные данные, поэтому маскирование телефона не требуется.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
FixationRequestId | Guid | Да | FixationRequestEntity.Id | — | Идентификатор заявки. |
Status | string (enum FixationStatus) | Да | Status | — | Всегда Pending на создании. |
DeveloperCompanyId | Guid | Да | TenantId | — | Застройщик-адресат. |
LeadId | Guid | Да | LeadId | — | Лид-основание. |
ClientId | Guid | Да | ClientId | — | Карточка клиента агентства. |
ClientName | FullName (owned — client.md) | Да | ClientName (snapshot) | — | ФИО в заявке. |
ClientPhoneNumber | string | Да | ClientPhoneNumber (snapshot) | — | Телефон в заявке (E.164). |
WishesOverride | LeadWishes? (lead.md) | Нет | WishesOverride | — | Override хотелок или null. |
Uniqueness | string (enum FixationUniqueness) | Да | Uniqueness | — | NoDuplicates / HasDuplicates (результат авто-проверки). |
DuplicateOfFixationId | Guid? | Нет | DuplicateOfFixationId | — | Лучшее активное совпадение у застройщика. |
DuplicateMatchKind | string? (enum DuplicateMatchKind) | Нет | DuplicateMatchKind | — | Phone / Name / PhoneAndName. |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Момент подачи. |
Алгоритм
- Контекст и доступ. Операцию выполняет агент (permission
crm.fixations.manage) в tenant своего агентства; либо Менеджер ПИН от имени агента (platform-scope). - Проверка входа и предусловий.
- формат полей:
LeadId/DeveloperCompanyId!= Guid.Empty, поляWishesOverride(диапазоны бюджета, enum строкой; заданныеBudgetCurrencyId/CityIdрезолвятся в справочникеhandbook(resolve-references.md): неизвестная/архивная валюта → ошибка валидации, неизвестный/архивный город →CRM_CITY_UNKNOWN) — иначе ошибка валидации; - лид-основание существует — иначе
CRM_LEAD_NOT_FOUND; - лид принадлежит агентству актора и агент — активный участник лида (обычно
Executor; или руководитель агентства / Менеджер ПИН), INV-FIX-2 — иначеCRM_LEAD_FORBIDDEN; - лид активен (
Status = Active, INV-FIX-2 / INV-LEAD-2) — иначеCRM_LEAD_NOT_ACTIVE; - выбранный застройщик существует — иначе
CRM_DEVELOPER_NOT_FOUND.
- формат полей:
- Что читаем. Карточку клиента лида (обязательна) — источник snapshot ФИО/телефона. Активные фиксации выбранного застройщика — для проверки уникальности и авто-подсветки дублей.
- Бизнес-правила и дубль-детект (INV-FIX-001). Если по тройке (агент, инвестор, застройщик) уже есть
активная фиксация (
Status ∈ {Pending, FixedUnique, FixedNonUnique}) — отказCRM_FIXATION_ACTIVE_EXISTS. Иначе свести дубль-детект по активным фиксациям того же застройщика: совпали телефон и ФИО →DuplicateMatchKind = PhoneAndName; только телефон →Phone; только ФИО →Name; лучшее совпадение →DuplicateOfFixationId,Uniqueness = HasDuplicates; иначеUniqueness = NoDuplicates. Создать заявкуStatus = Pending, снять snapshot ФИО/телефона из карточки клиента, зафиксировать атрибуцию (OriginAgencyCompanyId/OriginAgentUserId— снимок агента-инициатора и его агентства на момент подачи) и текущего исполнителя (ExecutorAgentUserId, при создании= OriginAgentUserId), перенести из лида инвестора (InvestorUserId) и клиента,TenantId = DeveloperCompanyId,WishesOverrideиз тела. В ленту лида добавить событиеLeadEventType.FixationCreated(payloadLeadFixationRefPayload { FixationRequestId, DeveloperCompanyId }, INV-LEAD-2) и обновитьLastEventAt. - Что меняется. Появляется запись заявки на фиксацию и новое событие ленты лида — в одном согласованном изменении.
- События. Интеграционные Kafka-события на создании не публикуются — заявка ждёт решения застройщика; downstream-события возникают только при решении: fixation-approved.md (одобрение + авто-сделка) / fixation-rejected.md (отклонение).
- Результат. Карточка созданной заявки (включая результат дубль-детекта) → HTTP
201. При гонке уникальной тройки (INV-FIX-001) —CRM_FIXATION_ACTIVE_EXISTS.
Диаграмма
Предусловия и постусловия
Предусловия. Актор — агент-владелец лида / руководитель агентства / Менеджер ПИН с crm.fixations.manage.
Лид-основание существует, Status = Active, принадлежит агентству актора. Застройщик-адресат существует. У
тройки (агент, инвестор, застройщик) нет активной фиксации (Status ∈ {Pending, FixedUnique, FixedNonUnique},
INV-FIX-001).
Ожидания от соседей (дубль-детект). Авто-подсветка дублей опирается на проекцию фиксаций Elasticsearch
(fuzzy ФИО + точный телефон, ADR-0059); при её отставании/недоступности — деградация: Uniqueness может быть
занижен до NoDuplicates, но заявка всё равно создаётся, а окончательное решение «уникальный/не уникальный»
остаётся за менеджером застройщика при approve.
Постусловие (успех). Создана FixationRequestEntity: Status = Pending, TenantId = DeveloperCompanyId,
иммутабельный snapshot ФИО/телефона из карточки клиента, зафиксирована атрибуция
(OriginAgencyCompanyId/OriginAgentUserId — агент-инициатор и агентство на момент подачи), текущий
исполнитель ExecutorAgentUserId (= OriginAgentUserId при создании), перенесены из лида инвестор
(InvestorUserId) и клиент, WishesOverride из тела или null. Зафиксирован результат дубль-детекта (Uniqueness,
DuplicateOfFixationId?, DuplicateMatchKind?). В ленте лида — событие FixationCreated, обновлён
LastEventAt. Интеграционных событий нет — заявка ждёт решения застройщика и появляется в его списке
(crm.fixations.decide) через проекцию (eventual). Инвестор о фиксации не уведомляется.
Постусловие (отказ). Ни заявки, ни события ленты лида.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none на создании (интеграционные — только на решении: fixation-approved.md, fixation-rejected.md) |
| Внешние вызовы | проверка существования застройщика (timeout 2 c, retry 1); batch-резолв BudgetCurrencyId/CityId из WishesOverride в справочнике handbook — только если заданы |
| Проекции / поиск | проекция фиксаций (FixationRequestSearchDocument) обновляется проектором; дубль-детект — fuzzy-поиск по ФИО + точный телефон, ADR-0059 |
| Уведомления | нет прямых; застройщик видит новую Pending-заявку в своём списке фиксаций (crm.fixations.decide) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | формат LeadId/DeveloperCompanyId/WishesOverride (включая неизвестный/архивный BudgetCurrencyId) | «Проверьте корректность полей заявки» | no retry |
CRM_CITY_UNKNOWN | 422 | WishesOverride.CityId не найден или архивен в справочнике городов handbook | «Город не найден в справочнике» | no retry (выбрать город из lookup) |
CRM_LEAD_NOT_FOUND | 404 | лид не найден | «Лид не найден» | no retry |
CRM_LEAD_FORBIDDEN | 403 | лид другого агентства/агента | «Нет доступа к лиду» | no retry |
CRM_LEAD_NOT_ACTIVE | 409 | lead.Status != Active | «Лид неактивен — создайте или продлите лид» | no retry |
CRM_DEVELOPER_NOT_FOUND | 404 | застройщик не найден | «Застройщик не найден» | no retry |
CRM_FIXATION_ACTIVE_EXISTS | 409 | активная фиксация на тройку (Agent,Investor,Developer) уже есть (INV-FIX-001) | «По этому клиенту уже есть активная фиксация у застройщика» | no retry (при гонке — перечитать состояние) |
Совместимость и наблюдаемость
WishesOverrideи все поляFixationCardModel— additive optional без breaking change; удаление/ переименование/смена типа — breaking (migration + rollout).- Logs:
fixation-request created(FixationRequestId,LeadId,DeveloperCompanyId,Uniqueness; ФИО/телефон — не пишутся); metrics:crm_fixation_requests_total{uniqueness}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/.
Acceptance Criteria
- Happy path (без дублей). Given
Active-лид и застройщик без совпадений; WhenPOST; Then201, заявкаPending,Uniqueness = NoDuplicates, в ленте лидаFixationCreated. - Обнаружен дубль. Given у застройщика активная фиксация с тем же телефоном; When
POST; Then201,Uniqueness = HasDuplicates,DuplicateMatchKind = Phone,DuplicateOfFixationIdзаполнен. - Активная тройка (INV-FIX-001). Given активная фиксация той же тройки (агент, инвестор, застройщик); When
POST; Then409 CRM_FIXATION_ACTIVE_EXISTS. - Неактивный лид. Given
lead.Status != Active; WhenPOST; Then409 CRM_LEAD_NOT_ACTIVE. - Чужой лид. Given лид другого агентства/агента; When
POST; Then403 CRM_LEAD_FORBIDDEN. - Деградация поиска. Given проекция Elasticsearch недоступна; When
POST; Then201, дубль-детект best-effort (возможноNoDuplicates), заявка создана. - PII. ФИО/телефон клиента не попадают в логи ни на одном исходе.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (1): POST /api/developer/crm/fixations/{fixationId}/approve — Одобрить фиксацию (уникальная / не уникальная)
- Поиск (1): FixationRequestSearchDocument
- scenarios (2): UC-OVR-004. От реферальной ссылки до объекта у собственника, UC-OVR-005. От лида до сделки (4 фазы)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: fixation-request.md (агрегат, INV-FIX-001/2, дубль-детект,
enum-ы), lead.md (основание, VO
LeadWishes,LeadFixationRefPayload), client.md (snapshotFullName/телефон). - События решения: fixation-approved.md, fixation-rejected.md; карта области — README.
- ADR: 0052 tenancy, 0056 simplicity-first.