DELETE /api/investor/crm/leads/{leadId}/booking-intents/{intentId} — Инвестор снимает объект-намерение
Инвестор убирает объект из своих намерений в рамках лида (передумал по конкретному объекту). Снятие —
мягкое (Status = Cancelled, запись сохраняется для истории); повторное намерение по тому же объекту
можно завести заново. Дом полей — lead.md.
Назначение
Принять от инвестора идентификатор его активного намерения и перевести его в Status = Cancelled с
проставлением RemovedAt = now, RemovedByUserId = actor.UserId. Если по намерению уже создана сделка
(DealId != null), снятие запрещено — сначала отменяется сделка. Повтор по уже снятому намерению —
идемпотентный no-op.
Если по намерению уже создана неотменённая сделка (DealId != null) — снятие запрещено: 409 CRM_BOOKING_INTENT_HAS_DEAL. Сначала отмените сделку по объекту.
Повторный DELETE по уже снятому намерению (Status = Cancelled) — no-op: возвращается 200 с текущей
карточкой, новое событие ленты лида не добавляется.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | investor (JWT инвестора) |
| Feature group | booking-intents |
| Статус | draft |
| Документ | docs/06-services/crm/api/investor/remove-booking-intent.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Инвестор) |
| Auth policy / Permissions | crm.leads.participate (инвесторская сторона лида) |
| Scope (RBAC) | не применяется: инвестор — вне agency-tenant (кросс-tenant участник, ADR-0052) |
| Record-level | lead.PrimaryInvestorUserId = actor.UserId; намерение принадлежит этому лиду |
| Tenant isolation | намерение TenantId = lead.TenantId = AgencyCompanyId; инвестор кросс-tenant → изоляция record-level по лиду |
| Audit | update/soft-delete (Status = Cancelled, RemovedAt, RemovedByUserId) |
Только сторона лида: lead.PrimaryInvestorUserId = actor.UserId. Инвестор — кросс-tenant участник
(ADR-0052); изоляция — record-level по лиду, а не по scope/tenant.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | DELETE |
| Route | /api/investor/crm/leads/{leadId}/booking-intents/{intentId} |
| Success status | 200 (снято или идемпотентный повтор) |
| Idempotency header | не применяется (DELETE естественно идемпотентен: повтор по снятому — no-op) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
LeadId | route | Guid | Да | != Guid.Empty; существующий лид, инвестор — его сторона | — |
IntentId | route | Guid | Да | != Guid.Empty; намерение лида | — |
ActorUserId | identity claims | Guid | Да | JWT инвестора; record-level = lead.PrimaryInvestorUserId | scope isolation |
Модель ответа BookingIntentCardModel
Та же карточка, что у add-booking-intent.md; значимые поля после операции —
Status = Cancelled, RemovedAt заполнен.
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор (permission
crm.leads.participate);ActorUserId— из токена;LeadId/IntentId— из route. - Проверка входа и предусловий.
- формат:
LeadId/IntentId!= Guid.Empty→ иначеVALIDATION_FAILED; - лид существует → иначе
CRM_LEAD_NOT_FOUND; - инвестор — сторона лида → иначе
CRM_LEAD_FORBIDDEN; - намерение существует и принадлежит лиду → иначе
CRM_BOOKING_INTENT_NOT_FOUND; - связанная сделка: если
DealId != nullи сделка не отменена →CRM_BOOKING_INTENT_HAS_DEAL(снять намерение можно только отменив сделку).
- формат:
- Что читаем. Намерение по (
LeadId,IntentId) — для проверки состояния и наличия сделки. - Бизнес-правила и переходы. Если намерение уже
Status = Cancelled— идемпотентный no-op: вернуть текущую карточку без записи. Иначе переводActive → Cancelled:RemovedAt = now,RemovedByUserId = ActorUserId; в ленту лида добавитьLeadEventType.BookingIntentRemoved(payloadLeadBookingIntentRefPayload { IntentId, PropertyObjectId, DeveloperCompanyId }) и обновитьLastEventAt. - Что меняется. Обновляются статус/атрибуты снятия намерения и событие ленты лида — в одном согласованном изменении.
- Результат. Карточка намерения (
Status = Cancelled) →200.
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none |
| Внешние вызовы | none |
| Проекции / поиск | none |
| Уведомления | опосредованно: агент видит снятие в ленте лида (LastEventAt) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | LeadId/IntentId == Guid.Empty | «Проверьте корректность запроса» | no retry |
UNAUTHORIZED | 401 | нет/невалидный JWT | «Требуется авторизация» | no retry |
CRM_LEAD_NOT_FOUND | 404 | лид не найден | «Лид не найден» | no retry |
CRM_LEAD_FORBIDDEN | 403 | инвестор не сторона лида | «Нет доступа к лиду» | no retry |
CRM_BOOKING_INTENT_NOT_FOUND | 404 | намерение не найдено / другого лида | «Намерение не найдено» | no retry |
CRM_BOOKING_INTENT_HAS_DEAL | 409 | по намерению есть неотменённая сделка (DealId != null) | «Сначала отмените сделку по объекту» | no retry |
Acceptance Criteria
- AC-1 (happy — снятие). Дано: активное намерение без сделки (
DealId = null). Когда:DELETE. Тогда:200,Status = Cancelled,RemovedAt/RemovedByUserIdзаполнены, в ленте лидаBookingIntentRemoved. - AC-2 (идемпотентный повтор). Дано: намерение уже
Cancelled. Когда: повторDELETE. Тогда:200(no-op), новое событие не добавляется. - AC-3 (есть сделка). Дано:
DealId != null, сделка не отменена. Когда:DELETE. Тогда:409 CRM_BOOKING_INTENT_HAS_DEAL. - AC-4 (чужой лид). Дано:
lead.PrimaryInvestorUserId != actor. Когда:DELETE. Тогда:403 CRM_LEAD_FORBIDDEN.
Совместимость и наблюдаемость
- Route стабилен; поля ответа additive; сужение enum намерения — breaking (migration + rollout).
- Logs:
booking-intent removed(IntentId,LeadId); metrics:crm_booking_intents_removed_total{result}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): 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 — Инвестор подтверждает предложенное намерение, POST /api/public/crm/booking-requests — Заявка на бронирование объекта (витрина)
- Use Cases (2): UC-CRM-016. Инвестор добавляет намерения бронирования (много объектов), UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, INV-INTENT-1, enum-ы): lead.md;
лид и
LeadEventType— lead.md. - Смежные методы: add-booking-intent.md, confirm-booking-intent.md, list-booking-intents.md.
- Отмена связанной сделки: cancel-booking.md; карта области — README. ADR: 0071 company-ownership и участники.