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

POST /api/agent/crm/leads/{leadId}/archive — Архивировать лид

Ручное закрытие лида агентом: клиент отказался, дубль или лид отработан. Перевод Active|Expired → Archived с фиксацией причины (ArchiveReason) и момента (ArchivedAt).

Назначение

Дать агенту (или руководителю агентства / Менеджеру ПИН) явно убрать лид из рабочей воронки с указанием причины, сохранив ленту событий, фиксации и сделки как историю.

Отличие от архивации клиента — INV-LEAD-4

В отличие от архивирования клиента (INV-CLI-4, client.md), архивирование лида не требует отсутствия активных сделок: порождённые сделки и заявки на фиксацию — исторические записи дочерних агрегатов и продолжают жить своим циклом (INV-LEAD-4, lead.md). Единственный побочный эффект на связанном агрегате — обнуление Client.ActiveLeadId (INV-CLI-2), если денормализованная ссылка указывала на закрываемый лид.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Агент; либо Руководитель агентства / Менеджер ПИН)
Auth policy / Permissionscrm.leads.manage
Scope (RBAC)организация (агентство); Менеджер ПИН — платформа
Record-levelагент — свои лиды (ExecutorAgentUserId = actor); руководитель — весь TenantId; Менеджер ПИН — platform-scope (lead.md)
Tenant isolationTenantId = AgencyCompanyId в каждом запросе; лид грузится с фильтром по TenantId
Auditupdate (actor-stamping: UpdatedByUserId, Version)
Доступ

Круг действующих лиц сужен record-level: агент архивирует только свои лиды (ExecutorAgentUserId = actor); руководитель агентства — любой лид своего TenantId; Менеджер ПИН — сквозной доступ (platform-scope). Лид вне этого круга → 403 CRM_LEAD_FORBIDDEN.

HTTP-контракт

ПолеЗначение
MethodPOST
Success status204 (No Content)
Route/api/agent/crm/leads/{leadId}/archive
Idempotency headerне применяется (терминальный статус: повтор на Archived409)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = AgencyCompanyId актораTenant isolation
LeadIdrouteGuidДа!= Guid.Empty; существующий лид в TenantIdrecord-level по ExecutorAgentUserId
ArchiveReasonbodystring?Нет0..1000 (varchar(1000)); trim; пустая строка → nullсвободный текст, не PII
ActorUserIdidentity claimsGuidДаJWT агента

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

Тела нет: HTTP 204 без тела. Ошибки — ProblemDetails (таблица ниже).

Алгоритм

  1. Контекст и доступ. Операцию выполняет агент/руководитель агентства/Менеджер ПИН (permission crm.leads.manage) в рамках TenantId.
  2. Проверка входа и предусловий.
    • формат: LeadId != Guid.Empty; ArchiveReason (если задан) ≤ 1000 — иначе ошибка валидации;
    • лид существует в TenantId — иначе CRM_LEAD_NOT_FOUND;
    • допустимость состояния: Status ∈ {Active, Expired}; уже ArchivedCRM_LEAD_ALREADY_ARCHIVED (повторный переход в терминал недопустим);
    • record-level: лид того же tenant и агент — владелец (кроме руководителя агентства и Менеджера ПИН) — иначе CRM_LEAD_FORBIDDEN.
  3. Что читаем. Лид (для проверок выше) и его клиента-родителя (для поддержки INV-CLI-2).
  4. Бизнес-правила и переходы. Переход Active|Expired → Archived (INV-LEAD-4: активные сделки и фиксации лида не трогаются, лента событий сохраняется append-only): ArchivedAt = now, ArchiveReason (trimmed, null для пустого), UpdatedByUserId = actor; ExpiredAt/ExpiresAt не меняются (историческая правда срока жизни). У клиента: если ActiveLeadId == lead.Id — обнулить (INV-CLI-2); иначе клиент не мутируется. Доменных событий Archived-перехода нет (lead.md).
  5. Что меняется. Статус лида → Archived и (опционально) обнуление Client.ActiveLeadId — в одном согласованном изменении.
  6. События. Интеграционных и доменных событий нет: закрытие лида внутреннее для агентства, внешние контексты на него не реагируют (в списке produced контекста lead-archived нет, см. README; наружу — только lead-created.md и lead-expired.md).
  7. Результат. HTTP 204 No Content. Повторный вызов на уже Archived лиде → CRM_LEAD_ALREADY_ARCHIVED.

Диаграмма

Предусловия и постусловия

Предусловия. Актор с crm.leads.manage и доступом к лиду (исполнитель ExecutorAgentUserId = actor / руководитель агентства / Менеджер ПИН), TenantId = AgencyCompanyId. Лид существует в tenant, Status ∈ {Active, Expired} (не Archived).

Постусловие (успех). Status = Archived, ArchivedAt = now, ArchiveReason (trim; null для пустого); ExpiresAt/ExpiredAt сохранены (историческая правда срока жизни). Если Client.ActiveLeadId == lead.Id — обнулён (INV-CLI-2), иначе клиент не мутируется. Дочерние сделки/фиксации продолжают жить (INV-LEAD-4), лента событий append-only сохранена. Ни доменных, ни интеграционных событий не публикуется.

Постусловие (отказ). Лид не изменён, клиент не тронут.

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

ТипДетали
Интеграционные событияnone (закрытие лида интеграционных событий не порождает)
Внешние вызовыnone
Проекции / поискnone (проекция клиента ClientSearchDocument не зависит от ActiveLeadId)
Уведомленияnone (нет события → нет downstream-уведомления; лид пропадает из фильтра Active в списке агента)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ArchiveReason длиннее 1000 или LeadId = Guid.Empty«Проверьте причину архивирования»no retry
CRM_LEAD_NOT_FOUND404лида нет в TenantId«Лид не найден»no retry
CRM_LEAD_ALREADY_ARCHIVED409Status = Archived (повтор/гонка)«Лид уже архивирован»no retry
CRM_LEAD_FORBIDDEN403actor не владелец лида и не руководитель/Менеджер ПИН«Недостаточно прав на лид»no retry
CRM_CLIENT_ACTIVE_LEAD_MISMATCH409защита INV-CLI-2: обнуление ActiveLeadId с чужим leadId (рассинхрон)«Повторите попытку»retry (перечитка)
CRM_LEAD_CONCURRENCY_CONFLICT409лид изменён конкурентно«Лид изменён, повторите»retry (перечитка)

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

  • Изменение статуса — терминальное и внутреннее; внешних контрактов метод не меняет (breaking-риска нет). Новые optional поля тела (напр. код причины) — additive.
  • Logs: lead-archived (LeadId, TenantId, ExecutorAgentUserId, наличие причины — без текста, если счесть чувствительным); metrics: crm_leads_archived_total{reason_present}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path (Active). Given Active-лид, у клиента ActiveLeadId указывает на него; When archive; Then 204, Status = Archived, Client.ActiveLeadId = null.
  • Happy path (Expired). Given Expired-лид; When archive; Then 204, Status = Archived.
  • Активная сделка не блокирует. Given у лида незавершённая сделка; When archive; Then 204 (в отличие от архивации клиента), сделка не изменена (INV-LEAD-4).
  • Повтор. Given Status = Archived; When archive; Then 409 CRM_LEAD_ALREADY_ARCHIVED.
  • Рассинхрон ActiveLeadId. Given денормализованная ссылка клиента указывает на другой лид; When обнуление; Then 409 CRM_CLIENT_ACTIVE_LEAD_MISMATCH (retry, перечитка).
  • Доступ. Given чужой лид не-руководителю; When archive; Then 403 CRM_LEAD_FORBIDDEN.

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

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

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

  • Сущности: lead.md (переход Archived, ArchiveReason/ArchivedAt, INV-LEAD-4), client.md (INV-CLI-2, ActiveLeadId).
  • Жизненный цикл лида (события, которые метод НЕ шлёт): lead-created.md, lead-expired.md.
  • Карта областей и permissions: README; tenancy — ADR-0052.