POST /api/agent/crm/leads/{leadId}/booking-intents — Агент предлагает объект клиенту
Агент предлагает инвестору конкретный объект в рамках лида: заводит намерение со стороны агента
(Source = AgentProposed), которое ждёт подтверждения инвестором (Flow B). До подтверждения намерение
активно, но ConfirmedByInvestor = false — сделку по нему можно завести только в статусе Draft, застройщик
не вовлекается. Дом полей — lead.md.
Назначение
Принять от агента объект (PropertyObjectId) в контексте лида и завести намерение
LeadBookingIntentEntity со стороной агента (Source = AgentProposed, ConfirmedByInvestor = false). Это
предложение инвестору поработать по объекту; инвестор подтверждает его позже
(confirm-booking-intent.md). Намерение не создаёт сделку и не
вовлекает застройщика.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | agent (JWT агента) |
| Feature group | booking-intents |
| Статус | draft |
| Документ | docs/06-services/crm/api/agent/propose-booking-intent.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Агент); Operator (Менеджер ПИН — platform-scope, от имени агента) |
| Auth policy / Permissions | crm.leads.manage (агентская сторона лида) |
| Scope (RBAC) | организация (агентство): record-level по лиду |
| Record-level | Актор — активный участник лида (обычно Executor) либо руководитель компании-владельца (AgencyCompanyId = lead.TenantId) / Менеджер ПИН |
| Tenant isolation | лид читается в tenant актора (AgencyCompanyId); намерение TenantId = lead.TenantId = AgencyCompanyId |
| Audit | create (AddedByUserId = actor.UserId) |
Выполнить операцию может только активный участник лида со стороны агентства (обычно Executor) либо
руководитель компании-владельца лида (AgencyCompanyId = lead.TenantId); либо Менеджер ПИН — от имени
агента, в рамках platform-scope. Намерение создаётся в tenant агентства (TenantId = lead.TenantId).
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/agent/crm/leads/{leadId}/booking-intents |
| Success status | 201 (намерение создано) |
| Idempotency header | не применяется (бизнес-дедуп по INV-INTENT-1: одно активное намерение на пару LeadId+PropertyObjectId) |
| Correlation | сквозной trace через заголовок traceparent |
Отдельного idempotency-ключа нет: повторное предложение того же объекта по тому же лиду блокируется
бизнес-инвариантом INV-INTENT-1 — активное намерение на пару LeadId+PropertyObjectId уже одно
(CRM_BOOKING_INTENT_ALREADY_EXISTS).
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
LeadId | route | Guid | Да | != Guid.Empty; существующий лид Status = Active, принадлежит агентству актора | — |
PropertyObjectId | body | Guid | Да | != Guid.Empty; существующий объект недвижимости chessboard (шаг 2); из него берутся ProjectId, DeveloperCompanyId, статус | — |
ActorUserId | identity claims | Guid | Да | JWT агента; = lead.ExecutorAgentUserId (кроме подачи Менеджером ПИН) | — |
ActorAgencyCompanyId | identity claims | Guid | Да | tenant агентства актора — record-level по лиду | Tenant isolation |
Модель ответа BookingIntentCardModel
Проекция созданного намерения (дельта дома lead.md);
полная таблица полей — add-booking-intent.md. Значимые для операции
значения: Source = AgentProposed, ConfirmedByInvestor = false, ConfirmedAt = null.
Алгоритм
- Контекст и доступ. Операцию выполняет агент (permission
crm.leads.manage) в tenant своего агентства; либо Менеджер ПИН от имени агента (platform-scope). - Проверка входа и предусловий.
- формат:
LeadId/PropertyObjectId!= Guid.Empty→ иначеVALIDATION_FAILED; - лид существует → иначе
CRM_LEAD_NOT_FOUND; - лид принадлежит агентству актора и агент — активный участник лида → иначе
CRM_LEAD_FORBIDDEN; - лид активен (
Status = Active) → иначеCRM_LEAD_NOT_ACTIVE; - существование объекта: запрос в
chessboardпоPropertyObjectId→ нет →CRM_PROPERTY_OBJECT_NOT_FOUND; из ответа берёмProjectId,DeveloperCompanyId, статус объекта; - допустимость состояния: статус объекта =
Свободен; иначеCRM_PROPERTY_OBJECT_NOT_AVAILABLE.
- формат:
- Что читаем. Активное намерение лида по паре (
LeadId,PropertyObjectId) — для дедупа (INV-INTENT-1). - Бизнес-правила и переходы (INV-INTENT-1). Если по паре (
LeadId,PropertyObjectId) уже есть активное намерение (любой стороны) —CRM_BOOKING_INTENT_ALREADY_EXISTS(повторное предложение бессмысленно; если инвестор уже добавил объект — намерение уже подтверждено). Иначе создать намерение:Source = AgentProposed,ConfirmedByInvestor = false,ConfirmedAt = null,Status = Active, снимокProjectId/DeveloperCompanyIdизchessboard,TenantId = lead.TenantId,AddedByUserId = ActorUserId,DealId = null. В ленту лида добавитьLeadEventType.BookingIntentAdded(payloadLeadBookingIntentRefPayload { IntentId, PropertyObjectId, DeveloperCompanyId }) и обновитьLastEventAt. - Что меняется. Появляется запись намерения и новое событие ленты лида — в одном согласованном изменении.
- Результат. Карточка намерения →
201. При гонке уникальной пары (INV-INTENT-1) —CRM_BOOKING_INTENT_ALREADY_EXISTS.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none (застройщик не вовлекается) |
| Внешние вызовы | chessboard lookup объекта (timeout 2 c): существование, статус, ProjectId/DeveloperCompanyId |
| Проекции / поиск | none |
| Уведомления | инвестор видит предложение в ленте лида (LastEventAt); push инвестору — best-effort через notifications по активности лида |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | LeadId/PropertyObjectId == Guid.Empty | «Проверьте корректность запроса» | no retry |
UNAUTHORIZED | 401 | нет/невалидный JWT или нет crm.leads.manage | «Требуется авторизация» | no retry |
CRM_LEAD_NOT_FOUND | 404 | лид не найден | «Лид не найден» | no retry |
CRM_LEAD_FORBIDDEN | 403 | лид другого агентства/агента | «Нет доступа к лиду» | no retry |
CRM_LEAD_NOT_ACTIVE | 409 | lead.Status != Active | «Лид неактивен — создайте или продлите лид» | no retry |
CRM_PROPERTY_OBJECT_NOT_FOUND | 404 | объект не найден в chessboard | «Объект не найден» | no retry |
CRM_PROPERTY_OBJECT_NOT_AVAILABLE | 409 | статус объекта не Свободен | «Объект недоступен» | no retry |
CRM_BOOKING_INTENT_ALREADY_EXISTS | 409 | активное намерение по паре (LeadId, PropertyObjectId) уже есть (INV-INTENT-1) | «По этому объекту уже есть намерение» | no retry (перечитать намерения) |
При гонке на создание намерения по одной и той же паре (LeadId, PropertyObjectId) побеждает первый
запрос; второй получает 409 CRM_BOOKING_INTENT_ALREADY_EXISTS. Это не повод для retry — клиенту следует
перечитать список намерений лида (list-booking-intents.md).
Acceptance Criteria
- AC-1 (happy — предложение). Дано: активный лид агентства, объект
Свободен, намерения по объекту нет. Когда:POST. Тогда:201, намерениеSource = AgentProposed,ConfirmedByInvestor = false, в ленте лидаBookingIntentAdded. - AC-2 (дубль намерения). Дано: по объекту уже есть активное намерение. Когда:
POST. Тогда:409 CRM_BOOKING_INTENT_ALREADY_EXISTS. - AC-3 (объект недоступен). Дано: статус объекта не
Свободен. Когда:POST. Тогда:409 CRM_PROPERTY_OBJECT_NOT_AVAILABLE. - AC-4 (чужой лид). Дано: лид другого агентства/агента. Когда:
POST. Тогда:403 CRM_LEAD_FORBIDDEN. - AC-5 (неактивный лид). Дано:
lead.Status != Active. Когда:POST. Тогда:409 CRM_LEAD_NOT_ACTIVE.
Совместимость и наблюдаемость
- Route и тело стабильны; поля ответа additive; сужение enum намерения — breaking (migration + rollout).
- Logs:
booking-intent proposed(IntentId,LeadId,PropertyObjectId); metrics:crm_booking_intents_total{source=proposed,result}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): GET /api/agent/crm/leads/{leadId}/booking-intents — Список намерений лида, POST /api/investor/crm/leads/{leadId}/booking-intents — Инвестор добавляет объект-намерение, POST /api/investor/crm/leads/{leadId}/booking-intents/{intentId}/confirm — Инвестор подтверждает предложенное намерение
- Use Cases (1): UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, INV-INTENT-1, enum-ы,
LeadBookingIntentRefPayload): lead.md; лид иLeadEventType— lead.md. - Смежные методы: list-booking-intents.md (список лида), confirm-booking-intent.md (инвестор подтверждает), add-booking-intent.md (инвестор добавляет сам).
- Продолжение flow: create-deal.md (Flow B — сделка
Draftдо подтверждения); карта области — README. ADR: 0071 company-ownership и участники.