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

POST /api/developer/crm/purchase-program/presets/{presetId}/archive — Архивировать пресет

Менеджер застройщика убирает из своей программы покупки один пресет рассрочки — soft-delete, а не физическое удаление: уже заключённые сделки со ссылкой Deal.InstallmentPresetId продолжают жить (purchase-program.md). Ключевой гейт — INV-PPR-5: пресет, выбранный в нетерминальных сделках, архивировать нельзя (сначала менеджер/агент меняет способ оплаты в этих сделках). Дом полей пресета, инвариантов и индексов — purchase-program.md.

Назначение

Скрывает неактуальный пресет рассрочки из конфигурации застройщика так, чтобы он перестал предлагаться при конвертации лида в сделку и в публичном чтении конфигурации покупки, но исторические сделки сохранили ссылку. У застройщика — один живой агрегат программы покупки (INV-PPR-1), архив трогает дочерний пресет. Вызывает менеджер застройщика; тела ответа нет.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areadeveloper
Feature grouppurchase-program
Статусdraft
Документdocs/06-services/crm/api/developer/archive-installment-preset.md

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

Доступ
ПроверкаЗначение
Actor typeUser (Менеджер застройщика в рамках TenantId = DeveloperCompanyId); администратор платформы (ADR-0053)
Auth policy / Permissionscrm.purchase-programs.manage
Scope (RBAC)организация (застройщик): менеджер оперирует программой своего TenantId
Record-levelPurchaseProgramEntity.TenantId == actor.DeveloperCompanyId (пресет принадлежит программе застройщика)
Tenant isolationTenantId = DeveloperCompanyId в каждом запросе (ADR-0052); чужой tenant невидим = 404
Auditdelete (soft): DeletedAt/DeletedByUserId пресета, инкремент Version; PII отсутствует (конфигурация)

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/developer/crm/purchase-program/presets/{presetId}/archive
Success status204 (пресет архивирован; тело пустое)
Idempotency headerне применяется — архив одноразовый; повтор над уже архивированным пресетом ловится soft-delete-фильтром (шаг 3) → 404
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = DeveloperCompanyId актораTenant isolation
PresetIdrouteGuidДа!= Guid.Empty; живой пресет в программе tenant (шаги 2–3)
ActorUserIdidentity claimsGuidДаJWT менеджера застройщика; пишется в DeletedByUserId

Тело запроса пустое (все параметры — route/identity); вложенных сущностей и коллекций нет.

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

Тело ответа отсутствует: успешный soft-delete не возвращает проекцию — HTTP 204 No Content. Обновлённый состав пресетов клиент перечитывает списком программы покупки (query-метод области developer). Ошибки — ProblemDetails (таблица ниже).

Алгоритм

  1. Контекст и доступ. Выполняет менеджер застройщика (User, permission crm.purchase-programs.manage; администратор платформы — ADR-0053); tenant — TenantId = DeveloperCompanyId. Архив одноразовый: повтор над архивным пресетом ловится состоянием (шаг 3).
  2. Проверка входа и предусловий.
    • PresetId != Guid.Empty — иначе VALIDATION_FAILED;
    • у застройщика есть живая программа покупки — иначе CRM_PURCHASE_PROGRAM_NOT_FOUND;
    • пресет есть среди живых пресетов программы (архивный невидим, повторный архив запрещён) — иначе CRM_INSTALLMENT_PRESET_NOT_FOUND;
    • scope: программа принадлежит tenant актора (чужой tenant = 404). Деловая связь INV-PPR-5 проверяется на шаге 4.
  3. Что читаем. Программу покупки застройщика (с пресетами) под блокировкой строки — сериализует параллельные операции над пресетами (архив/upsert) того же застройщика; если её нет → CRM_PURCHASE_PROGRAM_NOT_FOUND. Целевой живой пресет — если его нет → CRM_INSTALLMENT_PRESET_NOT_FOUND. Сделки как агрегаты не загружаются — нужен лишь пробник существования (шаг 4).
  4. Бизнес-правила и переходы.
    • INV-PPR-5: проверить, используется ли пресет в нетерминальных сделках — существует ли сделка с InstallmentPresetId == PresetId и Status ∉ {Completed, Cancelled} (нетерминальные Application/Booking/Sale, deal.md); если да → CRM_INSTALLMENT_PRESET_IN_USE (сначала смена способа оплаты в этих сделках);
    • soft-delete пресета (через корень-агрегат): guard (пресет жив и принадлежит программе); маппинг: preset.DeletedAt = now, preset.DeletedByUserId = ActorUserId, preset.Version += 1; остальные поля пресета не трогаются (сделки читают snapshot). Фиксируется доменный факт PurchaseProgramChanged.
  5. Что меняется. Пресет помечается архивным; сделки со ссылкой на него не перечитываются (FK deals.installment_preset_id без изменений). Конкурентный доступ — по версии строки: если пресет параллельно правили/архивировали → CRM_INSTALLMENT_PRESET_CONFLICT.
  6. События. Интеграционных Kafka-событий нет: конфигурация покупки читается синхронно, а сделки хранят собственный snapshot PaymentMethod/InstallmentPresetId — внешние контексты не зависят от жизненного цикла пресета (лента сделок deal-stage-changed.md архивом не затрагивается). Внутренний доменный факт PurchaseProgramChanged инвалидирует кэш конфигурации покупки.
  7. Согласованность. Soft-delete пресета и audit/Version фиксируются в одном согласованном изменении (атомарно).
  8. Результат. Тела ответа нет → HTTP 204 No Content. Ошибки — 400/403/404/409 (таблица ниже).

Диаграмма

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

ТипДетали
Integration eventsnone (архив пресета Kafka-событий не публикует; сделки держат snapshot InstallmentPresetId)
External callsnone (chessboard/rewards/identity не вызываются)
Cache / projections / searchдоменное PurchaseProgramChanged инвалидирует кэш getPurchaseConfig; сделки со ссылкой на пресет не перечитываются (FK deals.installment_preset_id без изменений)
Notificationsnone (настроечная операция; уведомления не шлются)

Ошибки и failure modes

Ошибки
КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400PresetId = Guid.Empty / формат запроса«Проверьте корректность запроса»no retry
CRM_PURCHASE_PROGRAM_FORBIDDEN403нет crm.purchase-programs.manage / чужой tenant«Нет доступа к программе покупки»no retry
CRM_PURCHASE_PROGRAM_NOT_FOUND404у застройщика нет живой программы покупки«Программа покупки не найдена»no retry
CRM_INSTALLMENT_PRESET_NOT_FOUND404пресет не найден в программе tenant либо уже архивирован«Пресет рассрочки не найден»no retry
CRM_INSTALLMENT_PRESET_IN_USE409INV-PPR-5: пресет выбран в нетерминальных сделках (Any(spec))«Пресет используется в активных сделках — сначала смените способ оплаты»no retry
CRM_INSTALLMENT_PRESET_CONFLICT409RowVersion-гонка: параллельная правка/архив пресета (шаг 5)«Пресет изменён параллельно, обновите данные»retry (перечитать)

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

Предусловия (истинны до сценария):

  • актор — менеджер застройщика (или администратор платформы) с permission crm.purchase-programs.manage;
  • у застройщика есть единственный живой агрегат программы покупки (INV-PPR-1) в TenantId = DeveloperCompanyId;
  • целевой пресет жив (DeletedAt == null) и принадлежит этой программе;
  • пресет не выбран ни в одной нетерминальной сделке (Status ∉ {Completed, Cancelled}, INV-PPR-5).

Постусловия (гарантированы после успеха):

  • пресет помечен архивным (DeletedAt, DeletedByUserId, Version +1) и выпадает из живых пресетов программы; повторный архив даёт 404;
  • сделки со ссылкой Deal.InstallmentPresetId не перечитываются и не меняются: они держат собственный snapshot PaymentMethod/InstallmentPresetId, FK deals.installment_preset_id валиден (soft-delete, не физическое удаление);
  • интеграционных Kafka-событий не публикуется; внутренний доменный факт PurchaseProgramChanged инвалидирует кэш конфигурации покупки (getPurchaseConfig) — при следующем чтении пресет не предлагается при конвертации лида в сделку (синхронный источник, наружу окна eventual-consistency нет).

Acceptance Criteria

  • Happy path. Дано: живой пресет, не используемый в нетерминальных сделках. Когда: менеджер архивирует. Тогда: 204, пресет архивен, исторические сделки со ссылкой не тронуты, событий нет.
  • Пресет используется. Дано: существует сделка с InstallmentPresetId == PresetId и статусом Application/Booking/Sale. Когда: архив. Тогда: 409 CRM_INSTALLMENT_PRESET_IN_USE (сначала сменить способ оплаты в этих сделках).
  • Повторный архив. Дано: пресет уже архивен. Когда: повтор. Тогда: 404 CRM_INSTALLMENT_PRESET_NOT_FOUND.
  • Нет программы. Дано: у застройщика нет живой программы покупки. Когда: архив. Тогда: 404 CRM_PURCHASE_PROGRAM_NOT_FOUND.
  • Конкурентная правка. Дано: пресет параллельно правится/архивируется. Когда: сохранение проигрывает по версии. Тогда: 409 CRM_INSTALLMENT_PRESET_CONFLICT; клиент перечитывает и повторяет.
  • Гонка с конвертацией лида. Дано: параллельно создаётся сделка на этом пресете. Когда: создание сделки валидирует пресет как живой (DeletedAt == null). Тогда: после архива новые сделки его не выберут; остаточное окно ограничено и INV-PPR-5 не нарушает (сделка хранит snapshot, а не активную ссылку).

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

  • Route/семантика стабильны; 204 без тела — контракт минимален. Смена маршрута или семантики soft-delete — breaking (migration + rollout). Enum DealStatus (спецификация INV-PPR-5) сериализуется строкой; неизвестное значение статуса в БД трактуется как нетерминальное (безопасный default — не даёт архивировать используемый пресет).
  • Logs: installment-preset-archive requested/succeeded/in-use/conflict (PresetId, TenantId, ActorUserId; без PII); metrics: crm_installment_preset_archive_total{result}, latency p95; traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/ (будущий путь по конвенции).
Гонка «архив ↔ конвертация лида»

Создание сделки валидирует пресет как живой (DeletedAt == null), поэтому после архива новые сделки его не выберут; остаточное окно ограничено и не нарушает INV-PPR-5 (сделка хранит snapshot, а не активную ссылку на конфигурацию).

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

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

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

  • Сущность (дом полей, INV-PPR-5, soft-delete, доменное PurchaseProgramChanged): purchase-program.md; потребитель пресета (PaymentMethod, InstallmentPresetId, нетерминальные статусы) — deal.md.
  • События: карта — README; внешняя видимость lifecycle сделок — deal-stage-changed.md (интеграционных событий пресет не публикует).
  • ADR: 0052 tenancy, 0056 simplicity-first.