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

POST /api/investor/crm/leads/{leadId}/booking-intents/{intentId}/confirm — Инвестор подтверждает предложенное намерение

Инвестор подтверждает объект, который агент предложил ему в рамках лида (Flow B): агент завёл намерение Source = AgentProposed (или создал сделку по незапрошенному объекту в статусе Draft), а инвестор соглашается работать по этому объекту. Подтверждение — обязательное условие гейта Draft → Application сделки. Дом полей — lead.md.

Назначение

Принять от инвестора идентификатор предложенного агентом намерения и проставить ConfirmedByInvestor = true, ConfirmedAt = now. Операция не создаёт сделку и не вовлекает застройщика — она снимает одно из двух условий перевода сделки из Draft в Application (второе — одобренная фиксация для пары инвестор × застройщик). Подтверждение уже подтверждённого намерения — идемпотентный no-op.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areainvestor (JWT инвестора)
Feature groupbooking-intents
Статусdraft
Документdocs/06-services/crm/api/investor/confirm-booking-intent.md

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

ПроверкаЗначение
Actor typeUser (Инвестор)
Auth policy / Permissionscrm.leads.participate (инвесторская сторона лида)
Scope (RBAC)не применяется: инвестор — вне agency-tenant (кросс-tenant участник, ADR-0052)
Record-levellead.PrimaryInvestorUserId = actor.UserId; намерение принадлежит этому лиду
Tenant isolationнамерение TenantId = lead.TenantId = AgencyCompanyId; инвестор кросс-tenant → изоляция record-level по лиду
Auditupdate (ConfirmedByInvestor, ConfirmedAt)
Доступ

Выполнить операцию может только инвестор — основная сторона лида (lead.PrimaryInvestorUserId = actor.UserId). Инвестор — кросс-tenant участник (вне agency-tenant, ADR-0052): доступ проверяется record-level по лиду, а не по scope.

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/investor/crm/leads/{leadId}/booking-intents/{intentId}/confirm
Success status200 (подтверждено или идемпотентный повтор)
Idempotency headerне применяется (повтор при ConfirmedByInvestor = true — no-op)
Correlationсквозной trace через заголовок traceparent
подсказка

Отдельного idempotency-ключа нет: повторное подтверждение уже подтверждённого намерения (ConfirmedByInvestor = true) — идемпотентный no-op, возвращается текущая карточка без изменений.

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
LeadIdrouteGuidДа!= Guid.Empty; существующий лид Status = Active, инвестор — его сторона
IntentIdrouteGuidДа!= Guid.Empty; существующее активное намерение лида
ActorUserIdidentity claimsGuidДаJWT инвестора; record-level = lead.PrimaryInvestorUserIdscope isolation

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

Та же карточка, что у add-booking-intent.md (дом полей — lead.md); значимое поле операции — ConfirmedByInvestor = true, ConfirmedAt заполнен.

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор (permission crm.leads.participate); ActorUserId — из токена; LeadId/IntentId — из route.
  2. Проверка входа и предусловий.
    • формат: LeadId/IntentId != Guid.Empty → иначе VALIDATION_FAILED;
    • лид существует → иначе CRM_LEAD_NOT_FOUND;
    • инвестор — сторона лида → иначе CRM_LEAD_FORBIDDEN;
    • лид активен (Status = Active) → иначе CRM_LEAD_NOT_ACTIVE;
    • намерение существует, принадлежит лиду и Status = Active → иначе CRM_BOOKING_INTENT_NOT_FOUND (снятое/чужое намерение недостижимо).
  3. Что читаем. Намерение по (LeadId, IntentId) — для проверки состояния и подтверждения.
  4. Бизнес-правила и переходы. Если ConfirmedByInvestor = true уже — идемпотентный no-op: вернуть текущую карточку без записи. Иначе проставить ConfirmedByInvestor = true, ConfirmedAt = now; в ленту лида добавить LeadEventType.BookingIntentConfirmed (payload LeadBookingIntentRefPayload { IntentId, PropertyObjectId, DeveloperCompanyId }) и обновить LastEventAt. Подтверждение само по себе сделку не переводит: перевод Draft → Application инициирует агент отдельной операцией при выполнении обоих условий (подтверждённое намерение + одобренная фиксация).
  5. Что меняется. Обновляются флаг/дата подтверждения на намерении и событие ленты лида — в одном согласованном изменении.
  6. Результат. Карточка намерения (ConfirmedByInvestor = true) → 200.

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

ТипДетали
Интеграционные событияnone (застройщик не вовлекается на подтверждении; developer-facing события — только при Draft → Application)
Внешние вызовыnone
Проекции / поискnone
Уведомленияопосредованно: агент видит подтверждение в ленте лида (LastEventAt)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400LeadId/IntentId == Guid.Empty«Проверьте корректность запроса»no retry
UNAUTHORIZED401нет/невалидный JWT«Требуется авторизация»no retry
CRM_LEAD_NOT_FOUND404лид не найден«Лид не найден»no retry
CRM_LEAD_FORBIDDEN403инвестор не сторона лида«Нет доступа к лиду»no retry
CRM_LEAD_NOT_ACTIVE409lead.Status != Active«Лид неактивен»no retry
CRM_BOOKING_INTENT_NOT_FOUND404намерение не найдено / снято / другого лида«Намерение не найдено»no retry
Ошибки

Снятое (Status = Cancelled) или намерение другого лида возвращает 404 CRM_BOOKING_INTENT_NOT_FOUND, а не 403 — операция не раскрывает существование чужих намерений.

Acceptance Criteria

  • AC-1 (happy — подтверждение). Дано: активное AgentProposed-намерение (ConfirmedByInvestor = false). Когда: POST …/confirm. Тогда: 200, ConfirmedByInvestor = true, ConfirmedAt заполнен, в ленте лида BookingIntentConfirmed.
  • AC-2 (идемпотентный повтор). Дано: намерение уже ConfirmedByInvestor = true. Когда: повтор. Тогда: 200 (no-op), новое событие не добавляется.
  • AC-3 (снятое намерение). Дано: намерение Status = Cancelled. Когда: POST. Тогда: 404 CRM_BOOKING_INTENT_NOT_FOUND.
  • AC-4 (чужой лид). Дано: lead.PrimaryInvestorUserId != actor. Когда: POST. Тогда: 403 CRM_LEAD_FORBIDDEN.

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

  • Route стабилен; поля ответа additive; сужение enum намерения — breaking (migration + rollout).
  • Logs: booking-intent confirmed (IntentId, LeadId); metrics: crm_booking_intents_confirmed_total{result}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm.

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

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

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