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

POST /api/v1/reward-rules/{ruleId}/deactivate — Возврат правила в черновик

Перевод правила Active → Draft для правки: правило немедленно выводится из подбора комиссии (RewardRuleResolver его больше не видит), уже созданные начисления не затрагиваются (в них snapshot).

Назначение

Менеджер застройщика снимает действующее правило для редактирования (Active-правила иммутабельны, INV-5 domain/reward-rule.md). Owning-контекст — rewards. Feature group — rules.

Metadata

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

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

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

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/reward-rules/{ruleId}/deactivate
Success status200
Idempotency headerне применяется (повтор из DraftREWARDS_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

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

ПолеТипОбязательностьИсточникMaskingОписание
RuleRewardRuleModelДаобновлённый агрегатПроекция после перехода (Status = Draft).

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

  • Предусловия. Актор — менеджер застройщика с rewards.rules.manage; правило существует в его tenant-е и находится в статусе Active; клиент передаёт актуальный ExpectedVersion.
  • Постусловия (успех). Правило в статусе Draft, Version увеличен; приоритет освобождён (доступен другому правилу для активации); период правила освобождает слот его сигнатуры специфичности (другое правило той же специфичности может занять пересекающийся период, INV-9); правило выведено из подбора комиссии; опубликовано RewardRuleChangedKafkaEvent со Status = Draft. Уже созданные начисления не затронуты — они хранят snapshot правила (решение №4).
  • Постусловия (отказ). Правило остаётся Active/Draft/Archived без изменений, событие не публикуется.

Алгоритм

  1. Контекст и доступ. Операцию выполняет менеджер компании-застройщика; scope — организация застройщика (TenantId); требуется право rewards.rules.manage.
  2. Проверка входа и предусловий. RuleId непустой, ExpectedVersion >= 1. Правило существует и принадлежит tenant-у застройщика — иначе REWARDS_RULE_NOT_FOUND. Статус = Active — иначе REWARDS_RULE_INVALID_STATUS_TRANSITION. Version совпадает с ExpectedVersion — иначе REWARDS_CONCURRENCY_CONFLICT. Уже созданные начисления не проверяются: они хранят snapshot и не ломаются (решение №4 ../README.md).
  3. Переход и запись. Правило переходит Active → Draft; фиксируются актор и новая версия. Приоритет освобождается (среди активных правило больше не учитывается).
  4. События. Публикуется RewardRuleChangedKafkaEvent (топик pin.rewards.reward-rule-changed.v1, ключ = RuleId; payload по дому, Status = Draft) — catalog убирает/пересчитывает комиссию объектов недвижимости, попадавших под правило.
  5. Результат. Проекция обновлённого правила (RewardRuleModel, Status = Draft) → 200.

Диаграмма

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

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

Окно eventual-consistency: событие уходит через outbox атомарно с переходом Active → Draft (ключ = RuleId). catalog асинхронно убирает/пересчитывает комиссию объектов недвижимости, попадавших под правило — окно секунды. В промежутке витрина может кратко показывать прежнюю комиссию, тогда как внутренний расчёт rewards уже её не применяет; после снятия правила объект недвижимости может перейти под другое активное правило меньшего приоритета либо остаться без комиссии.

Ошибки и failure modes

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

Acceptance-критерии

  • Happy path. Дано: правило в Active, ExpectedVersion актуален. Когда: POST .../deactivate. Тогда: 200, Status = Draft, Version + 1, приоритет освобождён; опубликовано RewardRuleChangedKafkaEvent; catalog пересчитает комиссию в окне секунд.
  • Не действующее правило. Дано: правило в Draft или Archived. Когда: POST .../deactivate. Тогда: 409 REWARDS_RULE_INVALID_STATUS_TRANSITION.
  • Начисления не затронуты. Дано: по правилу уже есть начисления. Когда: POST .../deactivate. Тогда: 200; существующие начисления сохраняют свои суммы и snapshot (не пересчитываются).
  • Освобождение приоритета. Дано: правило A Active с Priority = P. Когда: A деактивировано, затем правило B с Priority = P активируется. Тогда: активация B успешна (приоритет P свободен).
  • Конкурентная версия. Дано: ExpectedVersion устарел. Когда: POST. Тогда: 409 REWARDS_CONCURRENCY_CONFLICT.

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

  • Изменений контракта нет — additive-only, как у остальных rule-операций.
  • Logs: TenantId, RuleId; metrics: rewards_rule_deactivate_total{result}; traces: OTel; dashboard rewards-overview; runbook — не применяется.

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

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

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