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

POST /api/v1/reward-rules/{ruleId}/archive — Архивирование правила вознаграждения

«Удаление» правила в терминах API: перевод Draft|Active → Archived + soft-delete (DeletedAt). Физическое удаление запрещено — на правило ссылаются начисления (AppliedRuleId, restrict) — domain/reward-rule.md, INV-6.

Назначение

Менеджер застройщика навсегда выводит правило из работы (исторические начисления сохраняются). Owning-контекст — rewards. Feature group — rules.

Metadata

ПолеЗначение
Сервис/контекстrewards
API areadeveloper-admin
Feature grouprules
Статусdraft
Документdocs/06-services/rewards/api/admin/archive-reward-rule.md

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

ПроверкаЗначение
Actor typeUser (менеджер компании-застройщика)
Auth policy / Permissionsrewards.rules.manage
Scope (RBAC)организация: компания Type = Developer
Record-levelне применяется
Tenant isolationTenantId из claims; чужое правило → 404
Auditdelete (soft): DeletedAt, DeletedByUserId = ActorUserId, Version + 1, доменное событие RewardRuleArchivedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/reward-rules/{ruleId}/archive
Success status200
Idempotency headerне применяется (повтор из ArchivedREWARDS_RULE_INVALID_STATUS_TRANSITION)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequiredTenant isolation
ActorUserIdidentity claims (sub)GuidДаRequired
RuleIdroute {ruleId}GuidДаRequired, непустой
ExpectedVersionbodylongДа>= 1optimistic concurrency

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

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

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

  • Предусловия. Актор — менеджер застройщика с rewards.rules.manage; правило существует в его tenant-е и находится в статусе Draft или Active; клиент передаёт актуальный ExpectedVersion.
  • Постусловия (успех). Правило в статусе Archived и одновременно помечено удалённым (DeletedAt заполнён — INV-6); Version увеличен; правило навсегда выведено из подбора; исторические начисления сохраняют ссылку AppliedRuleId (физического удаления нет). Если правило было Active — опубликовано RewardRuleChangedKafkaEvent со Status = Archived; если было Draft — события нет.
  • Постусловия (отказ). Правило без изменений; событие не публикуется.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер компании-застройщика; scope — организация застройщика (TenantId); требуется право rewards.rules.manage.
  2. Проверка входа и предусловий. RuleId непустой, ExpectedVersion >= 1. Правило существует и принадлежит tenant-у застройщика — иначе REWARDS_RULE_NOT_FOUND. Статус ∈ {Draft, Active}; из ArchivedREWARDS_RULE_INVALID_STATUS_TRANSITION. Version совпадает с ExpectedVersion — иначе REWARDS_CONCURRENCY_CONFLICT. Наличие начислений архив не блокирует: ссылки на правило остаются живыми (физического удаления нет).
  3. Переход и запись. Правило переходит в Archived и одновременно помечается удалённым (DeletedAt) — согласованность архива (INV-6); фиксируются актор и новая версия. Soft-delete: правило остаётся в хранилище (на него ссылаются исторические начисления).
  4. События. Если правило было Active — публикуется RewardRuleChangedKafkaEvent (топик pin.rewards.reward-rule-changed.v1, ключ = RuleId; payload по дому, Status = Archived) — catalog пересчитывает комиссию. Для архивации из Draft интеграционного события нет (правило не было видно снаружи).
  5. Результат. Id, Status = Archived, новый Version200.

Диаграмма

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

ТипДетали
Integration eventsRewardRuleChangedKafkaEvent / pin.rewards.reward-rule-changed.v1 / key = RuleId — только при архивации из Active
External callsnone
Cache / projections / searchcatalog пересчитывает комиссию по событию
Notificationsnone

Окно eventual-consistency: при архивации из Active событие RewardRuleChangedKafkaEvent (Status = Archived) уходит через outbox атомарно с переходом (ключ = RuleId, порядок событий по правилу сохраняется). catalog асинхронно убирает/пересчитывает денормализованную комиссию объектов недвижимости, попадавших под правило — окно от коммита до обновления витрины измеряется секундами. В этом промежутке витрина может кратко показывать прежнюю комиссию, тогда как внутренний расчёт rewards (../internal/calculate-property-object-commission.md) правило уже не применяет; после снятия объект недвижимости может перейти под другое активное правило меньшего приоритета либо остаться без комиссии. При архивации из Draft интеграционного события нет и витрина не затрагивается (черновик не был виден снаружи). Уже созданные начисления не пересчитываются — они хранят snapshot правила (решение №4).

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
REWARDS_RULE_NOT_FOUND404Правило не существует / чужой tenant«Правило не найдено»no retry
REWARDS_RULE_INVALID_STATUS_TRANSITION409Уже Archived«Правило уже в архиве»no retry
REWARDS_CONCURRENCY_CONFLICT409ExpectedVersion/xmin конфликт«Правило изменено другим пользователем»retry после refresh

Acceptance-критерии

  • Архив действующего. Дано: правило в Active, ExpectedVersion актуален. Когда: POST .../archive. Тогда: 200, Status = Archived, DeletedAt заполнён (INV-6), Version + 1; опубликовано RewardRuleChangedKafkaEvent со Status = Archived; catalog пересчитает комиссию.
  • Архив черновика. Дано: правило в Draft. Когда: POST .../archive. Тогда: 200, Status = Archived; интеграционного события нет (черновик не был виден снаружи).
  • Повторный архив. Дано: правило уже Archived. Когда: POST .../archive. Тогда: 409 REWARDS_RULE_INVALID_STATUS_TRANSITION.
  • Начисления сохраняются. Дано: на правило ссылаются начисления (AppliedRuleId). Когда: POST .../archive. Тогда: 200; начисления и их ссылка на правило остаются живыми (restrict, INV-6).
  • Чужой tenant. Дано: правило принадлежит другому застройщику. Когда: POST .../archive. Тогда: 404 REWARDS_RULE_NOT_FOUND (существование чужого правила не раскрывается).
  • Конкурентная версия. Дано: ExpectedVersion устарел. Когда: POST. Тогда: 409 REWARDS_CONCURRENCY_CONFLICT.

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

  • Архив терминален — восстановление не проектируется (создаётся новое правило копированием формы).
  • Logs: TenantId, RuleId, исходный статус; metrics: rewards_rule_archive_total{result}; traces: OTel; dashboard rewards-overview; runbook — не применяется.

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

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

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