POST /api/v1/agency/agent-boost-campaigns/{campaignId}/archive — Архивирование boost-кампании
«Удаление» boost-кампании в терминах API: перевод * → Archived + soft-delete (DeletedAt). Физическое
удаление запрещено — на кампанию ссылаются начисления (AppliedBoostCampaignId, restrict) —
domain/agency-commission-policy.md. Архивирование выводит кампанию
из резолюции удержания навсегда: последующие подтверждения начислений (Confirm) для затронутых агентов в
периоде считают долю по базовому правилу активной политики (без boost). Уже применённые начисления не
пересчитываются — их снапшоты AppliedBoostCampaignId остаются живыми.
Назначение
Руководитель/администратор агентства досрочно сворачивает кампанию или убирает неактуальный черновик.
Owning-контекст — rewards, feature group — agent-boost, операция op60 реестра README.md.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | agency |
| Feature group | agent-boost |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agency/archive-agent-boost-campaign.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (руководитель/администратор агентства) |
| Auth policy / Permissions | rewards.commission-policy.manage |
| Scope (RBAC) | организация: компания Type ∈ {Agency, Broker, Pin} |
| Record-level | не применяется (tenant-интерсептор ограничивает выборку своим агентством) |
| Tenant isolation | TenantId = CompanyId агентства из claims (tenant-интерсептор); чужая кампания → 404 |
| Audit | delete (soft): DeletedAt, DeletedByUserId = ActorUserId, UpdatedByUserId, Version + 1; доменное событие AgentBoostCampaignArchivedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/agency/agent-boost-campaigns/{campaignId}/archive |
| Success status | 200 |
| Idempotency header | не применяется (повтор из Archived → REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
CampaignId | route {campaignId} | Guid | Да | Required, != Guid.Empty | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version агрегата | optimistic concurrency |
Модель ответа AgentBoostCampaignModel
Полная проекция под-агрегата AgentBoostCampaignEntity — дом формы:
create-agent-boost-campaign.md; дом полей под-агрегата —
domain/agency-commission-policy.md. Здесь — только дельта
результата архивирования.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Status | string (enum AgentBoostCampaignStatus) | Да | Status | — | Всегда Archived. |
Version | long | Да | Version | — | Новая версия (ExpectedVersion + 1). |
Остальные поля (Id, Name, Description, BoostRule, AgentUserIds, EffectiveFrom, EffectiveTo,
CreatedAt, UpdatedAt) — по дому, значения сохраняются: архив консервирует последнюю версию кампании.
Ставка BoostRule и список агентов — sensitive (Masking-правила README.md).
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании
Agency/Broker/Pin); scope — организация (TenantId из claims); требуется право
rewards.commission-policy.manage. Иначе — FORBIDDEN.
- Проверка входа и предусловий.
- формат:
CampaignId непустой, ExpectedVersion >= 1; иначе ошибка валидации;
- существование: кампания с
CampaignId в tenant-е агентства существует; если нет (или чужой tenant) →
REWARDS_AGENT_BOOST_NOT_FOUND;
- допустимость состояния: статус —
Draft или Active; повтор из Archived недопустим →
REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION;
- конкуренция: версия совпадает с
ExpectedVersion; иначе REWARDS_CONCURRENCY_CONFLICT;
- связи: начисления, ссылающиеся на кампанию (
AppliedBoostCampaignId), архив не блокируют —
ограничение restrict распространяется только на физическое удаление, которого здесь нет; ссылки
остаются валидными.
- Переход и soft-delete. Кампания переходит
* → Archived: одновременно Status = Archived и
DeletedAt = now, проставляется DeletedByUserId = актор, растёт версия. Физического удаления нет —
запись консервируется, ссылки AppliedBoostCampaignId начислений остаются живыми. Если кампания была
Active, она выводится из резолюции: последующие Confirm для её агентов в периоде считают долю по
базовому правилу активной политики (без boost).
- Фиксация факта. Архивирование сопровождается доменным событием
AgentBoostCampaignArchivedEvent
внутри rewards. Интеграционных Kafka-событий нет — boost применяется исключительно внутри rewards,
внешним потребителям его архивирование не нужно
(domain/agency-commission-policy.md, «Доменные и
интеграционные события»).
- Результат. Проекция обновлённой кампании (
Status = Archived, Version = ExpectedVersion + 1) →
HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | none — AgentBoostCampaignArchivedEvent остаётся внутри сервиса; Kafka не публикуется |
| External calls | none |
| Cache / projections / search | none; косвенно — при архиве активной кампании её агенты в периоде считаются по базовому правилу политики при следующих Confirm |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REWARDS_AGENT_BOOST_NOT_FOUND | 404 | Кампания не существует / чужой tenant | «Кампания не найдена» | no retry |
REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION | 409 | Status = Archived (повтор архивации) | «Кампания уже в архиве» | no retry |
REWARDS_CONCURRENCY_CONFLICT | 409 | конфликт версии (ExpectedVersion) | «Кампания изменена другим пользователем» | retry после refresh |
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — README.md.
Acceptance criteria
- AC-1 (архив черновика/активной). Given кампания в статусе
Draft или Active (Version = N);
When руководитель архивирует её с ExpectedVersion = N; Then кампания переходит * → Archived,
одновременно DeletedAt = now, DeletedByUserId = актор, Version = N+1; физического удаления нет —
запись консервируется; ответ 200. Постусловие: ссылки AppliedBoostCampaignId начислений остаются
валидными (уже применённые начисления не пересчитываются).
- AC-2 (архив активной → без boost). Given архивируется действующая кампания на агента
X; When
архивирование; Then boost для X в периоде снимается. Постусловие: следующий Confirm для X
считает долю по базовому правилу активной политики (AppliedBoostCampaignId = null) — задокументированное
поведение, не ошибка.
- AC-3 (повторный архив). Given кампания уже
Archived; When архивирование; Then
REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION (409) — «Кампания уже в архиве».
- AC-4 (конкуренция). Given
ExpectedVersion не совпал с текущей версией (кампанию параллельно
изменили/активировали); When архивирование; Then REWARDS_CONCURRENCY_CONFLICT (409, retry после
refresh).
- AC-5 (чужой tenant / нет). Given кампания другого агентства или несуществующая; When
архивирование; Then
REWARDS_AGENT_BOOST_NOT_FOUND (404, без раскрытия).
Совместимость и наблюдаемость
- Архив терминален — восстановление не проектируется (при необходимости заводится новая кампания через
create-agent-boost-campaign.md +
activate-agent-boost-campaign.md). Досрочное завершение действующей
кампании без полной архивации выполняется сокращением
EffectiveTo (INV-10) — отдельный сценарий правки,
здесь не рассматривается.
- Kafka-контракт не затрагивается (событий нет). Если появится потребитель изменений boost — добавить
публикацию через additive-процедуру событийного реестра (дом domain-дока).
- Logs:
TenantId, CampaignId, исходный статус (ставки/агенты не логируются — Masking-правила README);
metrics: rewards_agent_boost_archive_total{result}; traces: OTel, correlation через traceparent;
dashboard rewards-overview; runbook — не применяется.
- НФТ (ADR-0056): soft-delete одного агрегата (без физического удаления, ссылки
AppliedBoostCampaignId
консервируются); целевая латентность p95 < 300 мс; частота — редкая (сворачивание кампании, back-office).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы