GET /api/v1/my/commission-summary — Моя сводка комиссий (агент)
Назначение
Отдаёт агенту в личном кабинете денежную сводку по его завершённым сделкам: сколько он получит и получил,
в разрезе подтверждения и выплаты. По каждому статусу — одна сумма и количество начислений. База
суммирования — доля агента (а не полная комиссия по сделке): PendingConfirmation («получу, ожидает
подтверждения»), Confirmed («получу, подтверждено, деньги от застройщика ещё не получены»),
MoneyReceived («деньги получены, ожидает выплаты» — гейт ADR-0067 пройден), Paid («получил») и
Cancelled (справочно). Для Confirmed/MoneyReceived/Paid доля агента уже зафиксирована строкой распределения
(commission-accrual.md, INV-4); для PendingConfirmation строк
распределения ещё нет, поэтому доля прогнозируется по активной политике агентства
(agency-commission-policy.md). Экран-компаньон списка
list-my-accruals.md; агрегатная параллель agency-сводки
get-agency-commission-summary.md. Feature group — my-commission.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agent |
| Feature group | my-commission |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agent/get-my-commission-summary.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент) |
| Auth policy / Permissions | rewards.commission.view-own (op29 — реестр README.md) |
| Scope (RBAC) | организация: компания Type ∈ {Agency, Broker, Pin} (агентство агента) |
| Record-level | AgentUserId актора из токена, не из query — агент видит только свои начисления |
| Tenant isolation | TenantId = CompanyId агентства из токена; изоляция по tenant применяется на уровне хранилища, доступ к своим начислениям — явным фильтром по AgentUserId |
| Audit | read-sensitive (суммы комиссий): в trace — actor + период; суммы не логируются |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/my/commission-summary |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | токен (tenant_id) | Guid | Да | Required; не из query | Tenant isolation |
AgentUserId | токен (sub) | Guid | Да | Required; не из query | Record-level (own) |
DealCompletedFrom | query | DateTimeOffset? | Нет | нижняя граница периода по DealCompletedAt (включительно) | — |
DealCompletedTo | query | DateTimeOffset? | Нет | верхняя граница (исключительно); > DealCompletedFrom, если обе заданы | — |
Периода нет → сводка по всем начислениям агента. Paging не применяется — ответ — единичный агрегат, не список.
Модель ответа CommissionSummaryModel
Дом модели — README.md (10 полей: Pending/Confirmed/MoneyReceived/Paid/Cancelled ×
*Total : MoneyModel + *Count : int). Здесь — дельта агентской области: база суммирования и
формула прогноза Pending.
| Аспект | Значение (agent-area) |
|---|---|
База Confirmed/MoneyReceived/Paid | Amount.Value строки CommissionDistributionEntity с RecipientType = Agent, RecipientUserId = AgentUserId (точная доля агента; строки существуют с момента Confirm) |
База PendingConfirmation | Прогноз ForecastAgentShare (строк распределения ещё нет) — формула ниже |
База Cancelled (справочно) | COALESCE(Amount строки Agent, ForecastAgentShare) — отменённые из Confirmed берут точную долю, отменённые из PendingConfirmation — прогноз |
Currency всех *Total | RUB (платформа — single-currency, INV-6 REWARDS_CURRENCY_MISMATCH); пустой бакет → { Value: 0, Currency: RUB }, Count = 0 |
Distributions / gross-комиссия / AgencyRetained | не отдаются — агентская проекция несёт только собственную долю (Masking-правила README.md) |
Формула прогноза доли агента ForecastAgentShare (дельта, только PendingConfirmation)
Для начисления без строк распределения доля агента прогнозируется по правилу удержания активной политики агентства (agency-commission-policy.md):
rule = policy.Rules.AgentOverrides[AgentUserId] ?? policy.Rules.DefaultRule
AgencyRetention = rule.Kind == Percent ? round(CommissionAmount.Value * rule.Value / 100, 2, banker's)
: rule.Kind == FixedAmount ? min(rule.Value, CommissionAmount.Value) -- guard rule.Currency == CommissionAmount.Currency
: 0 -- нет активной политики (INV-6 fallback)
ForecastAgentShare = CommissionAmount.Value - AgencyRetention
Прогноз не авторитетен: используется текущая активная политика; фактическая доля фиксируется
строкой распределения при Confirm и может отличаться, если политику сменят до подтверждения (дельта
задокументирована как прогноз). Нет активной политики → удержание 0, вся комиссия агенту (INV-6).
Алгоритм
- Контекст и доступ. Актор — агент; tenant — его агентство (
Type ∈ {Agency, Broker, Pin}); требуется правоrewards.commission.view-own. Агент видит только свои начисления:AgentUserIdберётся из токена, не из запроса. - Проверка входа. Если заданы обе границы периода —
DealCompletedTo > DealCompletedFrom, иначеValidationError. Тип компании актора вне{Agency, Broker, Pin}→FORBIDDEN. Входных id, требующих существования, нет (self-scope). - Что читаем. Активная политика агентства (единственная,
Status = Active; если её нет — fallback INV-6: удержание 0, вся комиссия агенту) — из неё определяется ставка удержания для агента (персональный оверрайд или правило по умолчанию). Начисления агента группируются по статусу с суммой доли и количеством в каждом бакете; при заданном периоде — с ограничением поDealCompletedAt(нижняя граница включительно, верхняя исключительно). Построчная выгрузка не выполняется — только агрегаты по статусу (ADR-0056). - Бизнес-правила и маппинг. Слагаемое на начисление — доля агента: для
Confirmed/MoneyReceived/Paidберётся точная сумма строки распределенияAgent; дляPendingConfirmation— прогнозForecastAgentShareпо активной политике; дляCancelled— точная доля, если она была зафиксирована, иначе прогноз. Суммы и количества раскладываются вCommissionSummaryModel:PendingConfirmationTotal/Count,ConfirmedTotal/Count,MoneyReceivedTotal/Count,PaidTotal/Count,CancelledTotal/Count; отсутствующий бакет →{ Value: 0, Currency: RUB }/0. Прогнозная ветвьPendingнеавторитетна. - Результат. Сводка
CommissionSummaryModel(5 парTotal+Count) → HTTP200.
Диаграмма
Ошибки и failure modes
Общие коды (ValidationError 400, FORBIDDEN 403, UNAUTHORIZED 401) — README.md.
Специфичное для операции:
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
REWARDS_CURRENCY_MISMATCH | 409 | Прогноз Pending: валюта FixedAmount-удержания активной политики ≠ валюта CommissionAmount начисления (практически недостижимо — платформа single-currency RUB, INV-6) | «Несовместимая валюта в расчёте прогноза» | no retry |
Пустая выборка (нет начислений в периоде) — не ошибка: 200 со всеми бакетами { Value: 0 } / Count = 0.
Предусловия и постусловия
- Предусловия. Актор — агент в агентстве (
Type ∈ {Agency, Broker, Pin}) с правомrewards.commission.view-own. У агентства может быть активная политика удержания (единственная,Status = Active) — либо нет, тогда действует fallback INV-6 (удержание0). По агенту могут существовать начисления в любых статусах. - Постусловия. Только чтение: состояние не меняется, событий нет. Возвращён единичный агрегат
CommissionSummaryModel(5 пар*Total/*Count) по доле агента:Confirmed/MoneyReceived/Paid— точные суммыAgent-строк,PendingConfirmation— прогноз по текущей активной политике (неавторитетный),Cancelled— справочно. Gross-комиссия сделки,AgencyRetained,Distributionsне раскрываются (masking).
Интеграционная последовательность. Операция синхронна и читает два источника внутри rewards: активную
политику агентства (для прогноза PendingConfirmation) и начисления агента (наполняются consumer-ом
завершения сделки crm-deal-completed.md; подтверждение и выплата
меняют статусы). Прогноз Pending берёт текущую активную политику: если её сменят до Confirm,
фактическая доля из строки распределения может отличаться от показанного прогноза — расхождение
задокументировано как ожидаемое.
Acceptance-критерии
- Happy path. Дано: агент с правом
rewards.commission.view-own, начисления в статусахPendingConfirmation/Confirmed/MoneyReceived/Paid. Когда:GET /api/v1/my/commission-summaryбез периода. Тогда:200CommissionSummaryModelс 5 парами*Total/*Count;Confirmed/MoneyReceived/Paid— точная доля изAgent-строки распределения,PendingConfirmation— прогнозForecastAgentShareпо активной политике. - Фильтр периода. Дано: заданы
DealCompletedFrom/DealCompletedTo. Когда:GET. Тогда: суммируются только начисления сDealCompletedAtв[From, To)(нижняя включительно, верхняя исключительно). - Некорректный период. Дано:
DealCompletedTo <= DealCompletedFrom. Когда:GET. Тогда:400ValidationError. - Прогноз Pending неавторитетен. Дано: начисление
PendingConfirmation, после снятия сводки политику агентства сменили. Когда: сделку подтверждают. Тогда: фактическая доля из строки распределения может отличаться от ранее показанного прогноза — расхождение задокументировано как ожидаемое. - Нет активной политики. Дано: у агентства нет политики в статусе
Active. Когда:GET. Тогда: прогнозPendingсчитается с удержанием0— вся комиссия агенту (INV-6 fallback), ошибки нет. - Пустая выборка. Дано: у агента нет начислений (в периоде). Когда:
GET. Тогда:200, все бакеты{ Value: 0, Currency: RUB }/Count = 0— не404. - Только своя доля. Дано: агент запрашивает сводку. Когда:
GET. Тогда:AgentUserIdберётся из токена; gross-комиссия сделки,AgencyRetainedиDistributionsв ответ не попадают (masking). - Нет права / не в агентстве. Дано: тип компании актора вне
{Agency, Broker, Pin}либо нет права. Когда:GET. Тогда:403FORBIDDEN.
Совместимость и наблюдаемость
- Модель — общий
CommissionSummaryModel: новые поля additive nullable; удаление/переименование/смена типа — breaking (migration + rollout). Прогнозная методикаPending— часть контракта области; смена формулы удержания — breaking для клиента сводки. - Logs:
TenantId,AgentUserId, период,TotalCountбакетов (суммы не логируются — Sensitive); metrics:rewards_api_request_duration_seconds{route}; traces: сквозной trace черезtraceparent; dashboardrewards-overview; runbook при деградации агрегации —docs/06-services/rewards/runbooks/(по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Projection: AgentPerformanceEntity
- API (10): GET /api/v1/agency/agent-boost-campaigns — Список boost-кампаний агентов, GET /api/v1/agency/agents/{agentUserId}/commission-summary — Сводка по конкретному агенту, GET /api/v1/agency/commission-policies — Список комиссионных политик агентства, GET /api/v1/agency/commission-summary — Сводка комиссий агентства, GET /api/v1/my/agent-performance — Моя личная статистика (агент, user-centric), GET /api/v1/my/commission-accruals — Мои начисления (агент), GET /api/v1/my/payouts — Мои выплаты (агент), POST /api/v1/my/commission-accruals/{accrualId}/adjustment-requests — Заявка агента на корректировку своей доли, PUT /api/v1/agency/commission-policies/{policyId} — Изменение комиссионной политики (только Draft), Rewards API — реестр операций и общие контрактные модели
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: domain/commission-accrual.md (распределение агента,
индекс
ix_commission_accruals_agent, статусы), domain/agency-commission-policy.md (правило удержания, INV-6 fallback). - Модель
CommissionSummaryModel,MoneyModel, Masking-правила — README.md. - Смежные операции: list-my-accruals.md (построчный источник тех же данных), list-my-payouts.md, get-agency-commission-summary.md (агрегатная параллель по комиссии агентства).