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 area | developer |
| Feature group | purchase-program |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/archive-installment-preset.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Менеджер застройщика в рамках TenantId = DeveloperCompanyId); администратор платформы (ADR-0053) |
| Auth policy / Permissions | crm.purchase-programs.manage |
| Scope (RBAC) | организация (застройщик): менеджер оперирует программой своего TenantId |
| Record-level | PurchaseProgramEntity.TenantId == actor.DeveloperCompanyId (пресет принадлежит программе застройщика) |
| Tenant isolation | TenantId = DeveloperCompanyId в каждом запросе (ADR-0052); чужой tenant невидим = 404 |
| Audit | delete (soft): DeletedAt/DeletedByUserId пресета, инкремент Version; PII отсутствует (конфигурация) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/developer/crm/purchase-program/presets/{presetId}/archive |
| Success status | 204 (пресет архивирован; тело пустое) |
| Idempotency header | не применяется — архив одноразовый; повтор над уже архивированным пресетом ловится soft-delete-фильтром (шаг 3) → 404 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required; = DeveloperCompanyId актора | Tenant isolation |
PresetId | route | Guid | Да | != Guid.Empty; живой пресет в программе tenant (шаги 2–3) | — |
ActorUserId | identity claims | Guid | Да | JWT менеджера застройщика; пишется в DeletedByUserId | — |
Тело запроса пустое (все параметры — route/identity); вложенных сущностей и коллекций нет.
Модель ответа
Тело ответа отсутствует: успешный soft-delete не возвращает проекцию — HTTP 204 No Content. Обновлённый
состав пресетов клиент перечитывает списком программы покупки (query-метод области developer). Ошибки —
ProblemDetails (таблица ниже).
Алгоритм
- Контекст и доступ. Выполняет менеджер застройщика (
User, permissioncrm.purchase-programs.manage; администратор платформы — ADR-0053); tenant —TenantId = DeveloperCompanyId. Архив одноразовый: повтор над архивным пресетом ловится состоянием (шаг 3). - Проверка входа и предусловий.
PresetId != Guid.Empty— иначеVALIDATION_FAILED;- у застройщика есть живая программа покупки — иначе
CRM_PURCHASE_PROGRAM_NOT_FOUND; - пресет есть среди живых пресетов программы (архивный невидим, повторный архив запрещён) — иначе
CRM_INSTALLMENT_PRESET_NOT_FOUND; - scope: программа принадлежит tenant актора (чужой tenant = 404). Деловая связь INV-PPR-5 проверяется на шаге 4.
- Что читаем. Программу покупки застройщика (с пресетами) под блокировкой строки — сериализует параллельные
операции над пресетами (архив/upsert) того же застройщика; если её нет →
CRM_PURCHASE_PROGRAM_NOT_FOUND. Целевой живой пресет — если его нет →CRM_INSTALLMENT_PRESET_NOT_FOUND. Сделки как агрегаты не загружаются — нужен лишь пробник существования (шаг 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.
- INV-PPR-5: проверить, используется ли пресет в нетерминальных сделках — существует ли сделка с
- Что меняется. Пресет помечается архивным; сделки со ссылкой на него не перечитываются (FK
deals.installment_preset_idбез изменений). Конкурентный доступ — по версии строки: если пресет параллельно правили/архивировали →CRM_INSTALLMENT_PRESET_CONFLICT. - События. Интеграционных Kafka-событий нет: конфигурация покупки читается синхронно, а сделки хранят
собственный snapshot
PaymentMethod/InstallmentPresetId— внешние контексты не зависят от жизненного цикла пресета (лента сделок deal-stage-changed.md архивом не затрагивается). Внутренний доменный фактPurchaseProgramChangedинвалидирует кэш конфигурации покупки. - Согласованность. Soft-delete пресета и audit/
Versionфиксируются в одном согласованном изменении (атомарно). - Результат. Тела ответа нет → HTTP
204 No Content. Ошибки —400/403/404/409(таблица ниже).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (архив пресета Kafka-событий не публикует; сделки держат snapshot InstallmentPresetId) |
| External calls | none (chessboard/rewards/identity не вызываются) |
| Cache / projections / search | доменное PurchaseProgramChanged инвалидирует кэш getPurchaseConfig; сделки со ссылкой на пресет не перечитываются (FK deals.installment_preset_id без изменений) |
| Notifications | none (настроечная операция; уведомления не шлются) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | PresetId = Guid.Empty / формат запроса | «Проверьте корректность запроса» | no retry |
CRM_PURCHASE_PROGRAM_FORBIDDEN | 403 | нет crm.purchase-programs.manage / чужой tenant | «Нет доступа к программе покупки» | no retry |
CRM_PURCHASE_PROGRAM_NOT_FOUND | 404 | у застройщика нет живой программы покупки | «Программа покупки не найдена» | no retry |
CRM_INSTALLMENT_PRESET_NOT_FOUND | 404 | пресет не найден в программе tenant либо уже архивирован | «Пресет рассрочки не найден» | no retry |
CRM_INSTALLMENT_PRESET_IN_USE | 409 | INV-PPR-5: пресет выбран в нетерминальных сделках (Any(spec)) | «Пресет используется в активных сделках — сначала смените способ оплаты» | no retry |
CRM_INSTALLMENT_PRESET_CONFLICT | 409 | RowVersion-гонка: параллельная правка/архив пресета (шаг 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не перечитываются и не меняются: они держат собственный snapshotPaymentMethod/InstallmentPresetId, FKdeals.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). EnumDealStatus(спецификация 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; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь по конвенции).
Создание сделки валидирует пресет как живой (DeletedAt == null), поэтому после архива новые сделки его не
выберут; остаточное окно ограничено и не нарушает INV-PPR-5 (сделка хранит snapshot, а не активную ссылку на
конфигурацию).
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, 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.