POST /api/v1/agency/commission-policies/{policyId}/activate — Активация политики агентства
Перевод комиссионной политики Draft → Active. С этого момента политика применяется ко всем
подтверждениям начислений (Confirm — confirm-commission-accrual.md).
Предыдущая Active-политика агентства атомарно архивируется в той же транзакции — у агентства всегда
не более одной действующей политики (INV-1 SingleActivePolicy,
domain/agency-commission-policy.md).
Назначение
Руководитель/администратор агентства вводит подготовленный черновик распределения комиссии в действие.
Owning-контекст — rewards. Feature group — agency-policy (операция 16 реестра
README.md).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agency |
| Feature group | agency-policy |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agency/activate-agency-policy.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; чужая политика → 404 (INV-7 TenantOwnership) |
| Audit | update: UpdatedByUserId, EffectiveFrom, Version + 1; предыдущая — DeletedByUserId, Version + 1; доменные события AgencyPolicyActivatedEvent / AgencyPolicyArchivedEvent |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/agency/commission-policies/{policyId}/activate |
| Success status | 200 |
| Idempotency header | не применяется (повтор из Active → REWARDS_POLICY_INVALID_STATUS_TRANSITION) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ActorUserId | identity claims (sub) | Guid | Да | Required | — |
PolicyId | route {policyId} | Guid | Да | Required, непустой | — |
ExpectedVersion | body | long | Да | >= 1; сверка с Version агрегата | optimistic concurrency |
Модель ответа AgencyCommissionPolicyModel
Полная проекция AgencyCommissionPolicyEntity — та же модель, что возвращают create/update; дом модели
(поля Id, Name, Description, Status, EffectiveFrom, Rules : AgencyDistributionRulesModel,
CreatedAt, UpdatedAt, Version) — create-agency-policy.md; вложенные
AgencyRetentionRuleModel / AgentRetentionOverrideModel — list-agency-policies.md;
дом полей сущности — domain/agency-commission-policy.md. Здесь —
только дельта значений после активации (остальные поля наследуются от черновика без изменений):
| Поле | Значение после операции | Источник |
|---|---|---|
Status | Active (было Draft) | переход шага 4 |
EffectiveFrom | момент активации (UtcNow, до активации — null) | проставлен на шаге 4 |
Version | Version + 1 — новая база ExpectedVersion для последующих мутаций | version-stamping |
Masking Rules: полный документ распределения — только ролям с rewards.commission-policy.{view,manage} (агенту
отдаётся лишь применённое к нему удержание в его проекциях — Masking-правила README.md).
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель/администратор агентства; scope — организация
агентства (
TenantId); требуется правоrewards.commission-policy.manage. Выборка ограничена своим агентством. - Проверка входа и предусловий.
PolicyIdнепустой,ExpectedVersion >= 1. Политика существует в пределах своего агентства — иначеREWARDS_POLICY_NOT_FOUND. Статус =Draft(единственный допустимый исходный) — иначеREWARDS_POLICY_INVALID_STATUS_TRANSITION.Versionсовпадает сExpectedVersion— иначеREWARDS_CONCURRENCY_CONFLICT. - Текущая активная политика и оверрайды.
- Читается текущая действующая (
Active) политика агентства под блокировкой на запись (сериализует конкурентные активации); её может не быть — первая активация агентства (INV-1). - Если политика содержит персональные оверрайды (
Rules.AgentOverrides), все указанныеAgentUserIdдолжны существовать и быть активными в реплике состава пользователей агентства (INV-3, зависимостьidentity) — недостающие ids возвращаются вREWARDS_POLICY_OVERRIDE_INVALID(перечислением в metadata ошибки).
- Читается текущая действующая (
- Переход и атомарная замена. Активируемая политика переходит
Draft → Active(вActiveеёRules/Nameиммутабельны — INV-4), проставляетсяEffectiveFrom, актор, новая версия. Если была текущая активная политика — она одновременно архивируется (Active → Archived+DeletedAt, согласованность архива INV-5). Обе политики сохраняются в одном согласованном изменении, чтобы у агентства всегда было не более одной действующей политики (INV-1). Гонка первой активации двух черновиков ловится уникальным индексом активной политики и возвращаетREWARDS_POLICY_ACTIVE_EXISTS(retry). - События. Интеграционные Kafka-события не публикуются — политика применяется исключительно
внутри
rewards(приConfirm); фиксируются только доменные фактыAgencyPolicyActivatedEvent/AgencyPolicyArchivedEvent. - Результат. Проекция активированной политики (
AgencyCommissionPolicyModel,Status = Active,EffectiveFrom, новыйVersion) →200.
Диаграмма
Ошибки и failure modes
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED, REWARDS_CONCURRENCY_CONFLICT) —
README.md.
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
REWARDS_POLICY_NOT_FOUND | 404 | Политика не существует / чужой tenant | «Политика не найдена» | no retry |
REWARDS_POLICY_INVALID_STATUS_TRANSITION | 409 | Status != Draft | «Активировать можно только черновик» | no retry |
REWARDS_POLICY_OVERRIDE_INVALID | 400 | AgentOverrides содержит несуществующего/неактивного пользователя агентства (INV-3) | «Персональный оверрайд указывает недоступного сотрудника» + ids в metadata | retry после синка identity-реплики |
REWARDS_POLICY_ACTIVE_EXISTS | 409 | Гонка первой активации: конфликт ux_agency_policies_tenant_active (INV-1) | «Действующая политика уже существует — повторите» | retry |
REWARDS_CONCURRENCY_CONFLICT | 409 | ExpectedVersion/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 активация; ThenREWARDS_POLICY_OVERRIDE_INVALID(400) со списком проблемных id в metadata; после синхронизации реплики — повтор. - AC-4 (недопустимый статус). Given политика уже
ActiveилиArchived; When активация; ThenREWARDS_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; dashboardrewards-overview. - НФТ (ADR-0056): переход двух агрегатов (активируемая + предыдущая) под блокировкой активной политики +
сверка десятков
AgentOverridesпо локальной реплике identity; целевая латентность p95 < 400 мс; частота — редкая (смена политики агентства — единичные операции).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): GET /api/v1/agency/commission-policies — Список комиссионных политик агентства, POST /api/v1/agency/commission-policies — Создание комиссионной политики агентства (черновик), POST /api/v1/agency/commission-policies/{policyId}/archive — Архивирование политики, PUT /api/v1/agency/commission-policies/{policyId} — Изменение комиссионной политики (только Draft), Rewards API — реестр операций и общие контрактные модели
- Use Cases (2): UC-RWD-005. Агентство настраивает распределение и формирует платёжное поручение, US-реестр: Владелец агентства (US-OWN-AG)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность и инварианты: domain/agency-commission-policy.md
(INV-1/3/4/5, stateDiagram,
ux_agency_policies_tenant_active). - Точка применения политики: confirm-commission-accrual.md
(
ResolveRetentionприConfirm). - Жизненный цикл: create-agency-policy.md, update-agency-policy.md, archive-agency-policy.md, list-agency-policies.md.