GET /api/v1/agency/commission-policies — Список комиссионных политик агентства
Список политик распределения комиссии агентства (активная, черновики, архив-история) — экран
«Комиссионная политика» ЛК руководителя агентства (матрица «Агенство» 5.2).
Назначение
Руководитель агентства видит действующую политику и историю версий (история = цепочка архивированных
записей, дом domain/agency-commission-policy.md «Audit»).
Feature group — agency-policy.
| Поле | Значение |
|---|
| Сервис/контекст | rewards |
| API area | agency |
| Feature group | agency-policy |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agency/list-agency-policies.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (руководитель/администратор агентства) |
| Auth policy / Permissions | rewards.commission-policy.view |
| Scope (RBAC) | организация: компания Type ∈ {Agency, Broker, Pin} |
| Record-level | не применяется |
| Tenant isolation | TenantId = CompanyId агентства из claims; tenant-интерсептор |
| Audit | none (read; сами ставки удержания — Internal-класс внутри агентства) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/agency/commission-policies |
| Success status | 200 |
| Idempotency header | не применяется (read) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
Status | query | AgencyCommissionPolicyStatus? | Нет | enum Draft/Active/Archived | — |
Page / PageSize | query | int | Нет | paging-конвенция — README.md | — |
Модель ответа — постраничный список AgencyPolicyModel
Постраничный ответ (Items, Page, PageSize, TotalCount — paging-конвенция README.md).
Полный документ правил (DefaultRule + AgentOverrides со ставками удержания) отдаётся только ролям с
rewards.commission-policy.view/rewards.commission-policy.manage (руководитель/администратор агентства); агент такой роли не
имеет и на этот эндпоинт не попадает — свою персональную ставку он видит лишь в проекции «моя комиссия»
(get-my-commission-summary.md, Masking-правила
README.md).
AgencyPolicyModel (проекция AgencyCommissionPolicyEntity)
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | Id | — | Id политики. |
Name | string | Да | Name | — | Название («Стандарт 30/70»). |
Description | string? | Нет | Description | — | Комментарий. |
Status | string (enum AgencyCommissionPolicyStatus) | Да | Status | — | Draft/Active/Archived. |
EffectiveFrom | DateTimeOffset? | Нет | EffectiveFrom | — | Момент активации. |
DefaultRule | AgencyRetentionRuleModel | Да | Rules.DefaultRule | полный документ — только роли с rewards.commission-policy.view | Правило по умолчанию. |
AgentOverrides | IReadOnlyList<AgentRetentionOverrideModel> | Да (может быть пустым) | Rules.AgentOverrides | — | Персональные оверрайды. |
CreatedAt / UpdatedAt | DateTimeOffset | Да | audit-поля | — | Аудит. |
Version | long | Да | Version | — | Для ExpectedVersion мутаций. |
AgencyRetentionRuleModel (проекция AgencyRetentionRule из jsonb-дома)
| Поле | Тип | Обязательность | Ограничения |
|---|
Kind | string (enum AgencyRetentionKind) | Да | Percent/FixedAmount (удержание агентства) / AgentPercentOfPool/AgentPercentOfPrice (прямая доля агента) |
Value | decimal | Да | процентные Kind (Percent/AgentPercentOfPool/AgentPercentOfPrice): 0..100; FixedAmount: 0..100000000, scale 2 |
CurrencyId | short? | Да при FixedAmount, иначе null | ссылка на валюту справочника (RUB-only — ADR-0048); display-код валюты клиент берёт из справочника (../../../handbook/api/public/lookup-references.md) |
AgentRetentionOverrideModel
| Поле | Тип | Обязательность | Ограничения |
|---|
AgentUserId | Guid | Да | пользователь агентства |
Rule | AgencyRetentionRuleModel | Да | — |
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель/администратор агентства; scope — организация
(
TenantId из claims); требуется право rewards.commission-policy.view.
- Проверка входа.
Status — допустимое значение enum AgencyCommissionPolicyStatus, если задан;
Page/PageSize — по конвенции; иначе ошибка валидации.
- Что читаем. Страница политик агентства-tenant-а: опц. фильтр по
Status; сортировка —
детерминированная: Active-политика (не более одной, INV-1) всегда первой, затем остальные
(Draft/Archived) по CreatedAt DESC — свежие черновики и последние архивы выше (архив = история
версий, дом domain/agency-commission-policy.md «Audit»).
Фильтр Status = Active вернёт 0 или 1 запись; выборка ведётся по ix_agency_policies_tenant_status.
У агентства, ещё не заводившего политику, страница пуста (Items = [], TotalCount = 0) — это не
ошибка (означает fallback INV-6 при подтверждении начислений).
- Проекция. Каждая политика разворачивается в
AgencyPolicyModel; документ правил Rules
разворачивается в DefaultRule + AgentOverrides (ContractVersion наружу не отдаётся).
- Результат. Постраничный список
AgencyPolicyModel (Items, Page, PageSize, TotalCount) →
HTTP 200. Данные строго согласованы внутри rewards: список отражает последний коммит
create/update/activate/archive немедленно (агрегатное чтение OLTP, не проекция) — задержки
eventual-consistency нет.
Диаграмма
Ошибки и failure modes
Специфичных REWARDS_*-кодов у read-операции нет (нет мутаций и concurrency). Применяются общие коды —
дом README.md «Общие ошибки»:
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | Status вне enum AgencyCommissionPolicyStatus; PageSize вне 1..100 | «Проверьте параметры фильтра» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
FORBIDDEN | 403 | нет rewards.commission-policy.view / тип компании вне {Agency, Broker, Pin} / чужой tenant | «Недостаточно прав» | no retry |
Acceptance criteria
- AC-1 (список политик). Given у агентства есть активная политика и несколько черновиков/архивных;
When руководитель запрашивает список без фильтра; Then
200, Active первой, далее
Draft/Archived по CreatedAt DESC; для каждой политики отданы DefaultRule и AgentOverrides;
ContractVersion в проекции отсутствует.
- AC-2 (фильтр по статусу). Given те же данные; When запрос с
Status = Active; Then
возвращается ровно одна запись (или ноль, если политики нет) — INV-1; Status = Archived возвращает
историю версий по CreatedAt DESC.
- AC-3 (нет политики → пусто). Given агентство ещё не заводило политику; When запрос списка;
Then
200, Items = [], TotalCount = 0 (не 404); UI трактует это как «нет удержания» (fallback
INV-6 при подтверждении начислений).
- AC-4 (пагинация за пределами). Given
TotalCount = 3; When запрошена Page за последней;
Then 200 с пустым Items и корректным TotalCount (без ошибки).
- AC-5 (недостаточно прав). Given актор без
rewards.commission-policy.view или не роль агентства; When
запрос; Then FORBIDDEN (403), список не раскрывается.
- AC-6 (изоляция tenant). Given актор агентства A; When запрос; Then в выдаче только политики
A —
TenantId берётся из claims, не из query; политики агентства B недоступны.
- AC-7 (свежесть). Given только что выполнена активация новой политики; When сразу запрошен
список; Then новая
Active уже первой, предыдущая — Archived (строгая согласованность внутри
rewards, без окна eventual-consistency).
Совместимость и наблюдаемость
- Новые поля правил (например, будущие условия по типу сделки) — additive nullable; удаление/переименование/
смена типа — breaking. Новые значения enum
AgencyCommissionPolicyStatus в query — по unknown-enum-правилу
cross-cutting (ValidationError).
- Logs:
TenantId, применённый Status, TotalCount (ставки удержания не логируются — Internal-класс);
metrics: rewards_api_request_duration_seconds{route}; traces: OTel, correlation через traceparent;
dashboard rewards-overview.
- НФТ (ADR-0056): единицы политик на агентство, десятки строк оверрайдов — выборка тривиальна; целевая
латентность p95 < 200 мс; операция back-office (руководители агентств — низкая частота, подмножество
≤20k менеджеров/агентов платформы).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы