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

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.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areaagency
Feature groupagent-boost
Статусdraft
Документdocs/06-services/rewards/api/agency/archive-agent-boost-campaign.md

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

ПроверкаЗначение
Actor typeUser (руководитель/администратор агентства)
Auth policy / Permissionsrewards.commission-policy.manage
Scope (RBAC)организация: компания Type ∈ {Agency, Broker, Pin}
Record-levelне применяется (tenant-интерсептор ограничивает выборку своим агентством)
Tenant isolationTenantId = CompanyId агентства из claims (tenant-интерсептор); чужая кампания → 404
Auditdelete (soft): DeletedAt, DeletedByUserId = ActorUserId, UpdatedByUserId, Version + 1; доменное событие AgentBoostCampaignArchivedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agency/agent-boost-campaigns/{campaignId}/archive
Success status200
Idempotency headerне применяется (повтор из ArchivedREWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ActorUserIdidentity claims (sub)GuidДаRequired
CampaignIdroute {campaignId}GuidДаRequired, != Guid.Empty
ExpectedVersionbodylongДа>= 1; сверка с Version агрегатаoptimistic concurrency

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

Полная проекция под-агрегата AgentBoostCampaignEntityдом формы: create-agent-boost-campaign.md; дом полей под-агрегата — domain/agency-commission-policy.md. Здесь — только дельта результата архивирования.

ПолеТипОбязательностьИсточникMaskingОписание
Statusstring (enum AgentBoostCampaignStatus)ДаStatusВсегда Archived.
VersionlongДаVersionНовая версия (ExpectedVersion + 1).

Остальные поля (Id, Name, Description, BoostRule, AgentUserIds, EffectiveFrom, EffectiveTo, CreatedAt, UpdatedAt) — по дому, значения сохраняются: архив консервирует последнюю версию кампании. Ставка BoostRule и список агентов — sensitive (Masking-правила README.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/администратор агентства (тип компании Agency/Broker/Pin); scope — организация (TenantId из claims); требуется право rewards.commission-policy.manage. Иначе — FORBIDDEN.
  2. Проверка входа и предусловий.
    • формат: 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 распространяется только на физическое удаление, которого здесь нет; ссылки остаются валидными.
  3. Переход и soft-delete. Кампания переходит * → Archived: одновременно Status = Archived и DeletedAt = now, проставляется DeletedByUserId = актор, растёт версия. Физического удаления нет — запись консервируется, ссылки AppliedBoostCampaignId начислений остаются живыми. Если кампания была Active, она выводится из резолюции: последующие Confirm для её агентов в периоде считают долю по базовому правилу активной политики (без boost).
  4. Фиксация факта. Архивирование сопровождается доменным событием AgentBoostCampaignArchivedEvent внутри rewards. Интеграционных Kafka-событий нет — boost применяется исключительно внутри rewards, внешним потребителям его архивирование не нужно (domain/agency-commission-policy.md, «Доменные и интеграционные события»).
  5. Результат. Проекция обновлённой кампании (Status = Archived, Version = ExpectedVersion + 1) → HTTP 200.

Диаграмма

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

ТипДетали
Integration eventsnone — AgentBoostCampaignArchivedEvent остаётся внутри сервиса; Kafka не публикуется
External callsnone
Cache / projections / searchnone; косвенно — при архиве активной кампании её агенты в периоде считаются по базовому правилу политики при следующих Confirm
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
REWARDS_AGENT_BOOST_NOT_FOUND404Кампания не существует / чужой tenant«Кампания не найдена»no retry
REWARDS_AGENT_BOOST_INVALID_STATUS_TRANSITION409Status = Archived (повтор архивации)«Кампания уже в архиве»no retry
REWARDS_CONCURRENCY_CONFLICT409конфликт версии (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).

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

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

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