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

POST /api/v1/agency/commission-policies/{policyId}/activate — Активация политики агентства

Перевод комиссионной политики Draft → Active. С этого момента политика применяется ко всем подтверждениям начислений (Confirmconfirm-commission-accrual.md). Предыдущая Active-политика агентства атомарно архивируется в той же транзакции — у агентства всегда не более одной действующей политики (INV-1 SingleActivePolicy, domain/agency-commission-policy.md).

Назначение

Руководитель/администратор агентства вводит подготовленный черновик распределения комиссии в действие. Owning-контекст — rewards. Feature group — agency-policy (операция 16 реестра README.md).

Metadata

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

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

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

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/agency/commission-policies/{policyId}/activate
Success status200
Idempotency headerне применяется (повтор из ActiveREWARDS_POLICY_INVALID_STATUS_TRANSITION)
Correlationсквозной trace через заголовок traceparent

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

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

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

Полная проекция AgencyCommissionPolicyEntity — та же модель, что возвращают create/update; дом модели (поля Id, Name, Description, Status, EffectiveFrom, Rules : AgencyDistributionRulesModel, CreatedAt, UpdatedAt, Version) — create-agency-policy.md; вложенные AgencyRetentionRuleModel / AgentRetentionOverrideModellist-agency-policies.md; дом полей сущности — domain/agency-commission-policy.md. Здесь — только дельта значений после активации (остальные поля наследуются от черновика без изменений):

ПолеЗначение после операцииИсточник
StatusActive (было Draft)переход шага 4
EffectiveFromмомент активации (UtcNow, до активации — null)проставлен на шаге 4
VersionVersion + 1 — новая база ExpectedVersion для последующих мутацийversion-stamping

Masking Rules: полный документ распределения — только ролям с rewards.commission-policy.{view,manage} (агенту отдаётся лишь применённое к нему удержание в его проекциях — Masking-правила README.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/администратор агентства; scope — организация агентства (TenantId); требуется право rewards.commission-policy.manage. Выборка ограничена своим агентством.
  2. Проверка входа и предусловий. PolicyId непустой, ExpectedVersion >= 1. Политика существует в пределах своего агентства — иначе REWARDS_POLICY_NOT_FOUND. Статус = Draft (единственный допустимый исходный) — иначе REWARDS_POLICY_INVALID_STATUS_TRANSITION. Version совпадает с ExpectedVersion — иначе REWARDS_CONCURRENCY_CONFLICT.
  3. Текущая активная политика и оверрайды.
    • Читается текущая действующая (Active) политика агентства под блокировкой на запись (сериализует конкурентные активации); её может не быть — первая активация агентства (INV-1).
    • Если политика содержит персональные оверрайды (Rules.AgentOverrides), все указанные AgentUserId должны существовать и быть активными в реплике состава пользователей агентства (INV-3, зависимость identity) — недостающие ids возвращаются в REWARDS_POLICY_OVERRIDE_INVALID (перечислением в metadata ошибки).
  4. Переход и атомарная замена. Активируемая политика переходит Draft → ActiveActive её Rules/Name иммутабельны — INV-4), проставляется EffectiveFrom, актор, новая версия. Если была текущая активная политика — она одновременно архивируется (Active → Archived + DeletedAt, согласованность архива INV-5). Обе политики сохраняются в одном согласованном изменении, чтобы у агентства всегда было не более одной действующей политики (INV-1). Гонка первой активации двух черновиков ловится уникальным индексом активной политики и возвращает REWARDS_POLICY_ACTIVE_EXISTS (retry).
  5. События. Интеграционные Kafka-события не публикуются — политика применяется исключительно внутри rewards (при Confirm); фиксируются только доменные факты AgencyPolicyActivatedEvent / AgencyPolicyArchivedEvent.
  6. Результат. Проекция активированной политики (AgencyCommissionPolicyModel, Status = Active, EffectiveFrom, новый Version) → 200.

Диаграмма

Ошибки и failure modes

Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) — README.md.

КодHTTP statusУсловиеСообщениеRetry
REWARDS_POLICY_NOT_FOUND404Политика не существует / чужой tenant«Политика не найдена»no retry
REWARDS_POLICY_INVALID_STATUS_TRANSITION409Status != Draft«Активировать можно только черновик»no retry
REWARDS_POLICY_OVERRIDE_INVALID400AgentOverrides содержит несуществующего/неактивного пользователя агентства (INV-3)«Персональный оверрайд указывает недоступного сотрудника» + ids в metadataretry после синка identity-реплики
REWARDS_POLICY_ACTIVE_EXISTS409Гонка первой активации: конфликт ux_agency_policies_tenant_active (INV-1)«Действующая политика уже существует — повторите»retry
REWARDS_CONCURRENCY_CONFLICT409ExpectedVersion/xmin конфликт«Политика изменена — обновите данные»retry после refresh

Acceptance criteria

  • AC-1 (первая активация агентства). Given у агентства нет активной политики и есть черновик (Draft, Version = N); When руководитель активирует его с ExpectedVersion = N; Then политика переходит Draft → Active, EffectiveFrom = UtcNow, Version = N+1, предыдущей активной нет; ответ 200. Постусловие: у агентства ровно одна Active-политика (INV-1); последующие Confirm начислений применяют её.
  • AC-2 (атомарная замена). Given активная политика P0 и черновик P1; When активируется P1; Then в одном согласованном изменении P1 → Active и P0 → Archived (DeletedAt проставлен); у агентства по-прежнему ровно одна активная (INV-1). Постусловие: уже подтверждённые начисления не пересчитываются — их AppliedPolicyId сохраняет ссылку на прежнюю версию.
  • AC-3 (оверрайд на недоступного сотрудника). Given черновик с Rules.AgentOverrides, где хотя бы один AgentUserId отсутствует/неактивен в реплике состава агентства (INV-3); When активация; Then REWARDS_POLICY_OVERRIDE_INVALID (400) со списком проблемных id в metadata; после синхронизации реплики — повтор.
  • AC-4 (недопустимый статус). Given политика уже Active или Archived; When активация; Then REWARDS_POLICY_INVALID_STATUS_TRANSITION (409) — активировать можно только Draft.
  • AC-5 (гонка первой активации). Given два параллельных запроса активируют разные черновики при отсутствии активной; When оба проходят проверку; Then один побеждает (уникальный индекс активной политики), второй получает REWARDS_POLICY_ACTIVE_EXISTS (409, retry).
  • AC-6 (устаревшая версия / чужой tenant). Given ExpectedVersion не совпал с текущим — REWARDS_CONCURRENCY_CONFLICT (409); политика другого агентства — REWARDS_POLICY_NOT_FOUND (404, без раскрытия).
  • AC-7 (гонка активации и подтверждения начисления). Given одновременно идут активация новой политики P1 и подтверждение начисления (Confirm), считающее распределение по активной политике; When обе операции борются за блокировку активной политики; Then они сериализуются: если Confirm успел прочитать P0 до её архивации — начисление зафиксирует AppliedPolicyId = P0; если после активации — P1. Промежуточного состояния «две активные политики» не наблюдается (INV-1); уже подтверждённые начисления при активации P1 не пересчитываются.

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

  • Kafka-контракт не затрагивается (событий нет). Если появится потребитель изменений политики — добавить pin.rewards.agency-policy-changed.v1 через additive-процедуру реестра (дом domain-дока), это отдельное breaking-независимое расширение.
  • Logs: TenantId, PolicyId, PreviousPolicyId, OverridesCount (ставки удержания не логируются); metrics: rewards_policy_activate_total{result}, rewards_policy_active_exists_total (гонка INV-1); traces: OTel; dashboard rewards-overview.
  • НФТ (ADR-0056): переход двух агрегатов (активируемая + предыдущая) под блокировкой активной политики + сверка десятков AgentOverrides по локальной реплике identity; целевая латентность p95 < 400 мс; частота — редкая (смена политики агентства — единичные операции).

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

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

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