GET /api/v1/my/payouts — Мои выплаты (агент)
Назначение
Отдаёт агенту постраничную историю его выплат: что уже выплачено, что передано в бухгалтерию и ожидает
выплаты, а что было отклонено. Показываются позиции агента в платёжных поручениях его агентства
(PayoutOrderItemEntity) — с суммой, числом распределений и датой выплаты — плюс статус, номер и период
родительского поручения (PayoutOrderEntity). Это record-level срез: позиции отбираются по
AgentUserId = актор (индекс ix_payout_order_items_agent). Дом полей и статусная модель —
domain/payout-order.md; базовые проекции PayoutOrderItemModel /
PayoutOrderModel — README.md. Feature group — my-commission.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agent |
| Feature group | my-commission |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agent/list-my-payouts.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент) |
| Auth policy / Permissions | rewards.payouts.view-own (op30) |
| Scope (RBAC) | собственные записи в рамках своего агентства (компания Type ∈ {Agency, Broker, Pin}) |
| Record-level | PayoutOrderItemEntity.AgentUserId = UserId актора из токена, не из query |
| Tenant isolation | TenantId = CompanyId агентства из токена; вместе с AgentUserId ведут в ix_payout_order_items_agent (tenant_id, agent_user_id) |
| Audit | read-sensitive (суммы выплат): в trace — actor + фильтры + TotalCount; суммы не логируются |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/my/payouts |
| Success status | 200 |
| Idempotency header | нет (безопасное чтение) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
AgentUserId | токен (sub) | Guid | Да | Required; не принимается из query | record-level фильтр |
TenantId | токен (tenant_id) | Guid | Да | Required; не принимается из query | Tenant isolation |
Status | query | PayoutOrderStatus? | Нет | подмножество видимых агенту: PendingPayment/Paid/Rejected; Draft/Cancelled → ValidationError | — |
PeriodFrom | query | DateTimeOffset? | Нет | нижняя граница расчётного периода поручения: PayoutOrder.PeriodFrom >= PeriodFrom | — |
PeriodTo | query | DateTimeOffset? | Нет | верхняя граница: PayoutOrder.PeriodTo <= PeriodTo; > PeriodFrom, если обе заданы | — |
Page / PageSize | query | int | Нет | paging-конвенция — README.md (Page >= 1, PageSize 1..100) | — |
Модель ответа — страница MyPayoutItemModel
Ответ — страница элементов MyPayoutItemModel с полями Items, Page, PageSize, TotalCount
(paging-конвенция — README.md).
MyPayoutItemModel — плоская проекция «позиция + её поручение», определяется как дельта:
PayoutOrderItemModel (README.md) минус AgentUserId (всегда = актор, избыточно)
плюс подмножество полей PayoutOrderModel (PayoutOrderId, Number, Status, PeriodFrom,
PeriodTo).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PayoutOrderItemEntity.Id | — | Идентификатор позиции (строка «моей выплаты»). |
PayoutOrderId | Guid | Да | PayoutOrderItemEntity.PayoutOrderId | — | Родительское поручение. |
Number | string | Да | PayoutOrderEntity.Number (PO-{yyyy}-{seq}) | — | Человекочитаемый номер поручения. |
Status | string (enum PayoutOrderStatus) | Да | PayoutOrderEntity.Status | — | PendingPayment/Paid/Rejected (маппинг 5.5: ожидается/выплачено/отклонено). |
PeriodFrom | DateTimeOffset | Да | PayoutOrderEntity.PeriodFrom | — | Начало расчётного периода поручения. |
PeriodTo | DateTimeOffset | Да | PayoutOrderEntity.PeriodTo | — | Конец периода (исключительно). |
Amount | MoneyModel | Да | PayoutOrderItemEntity.Amount | sensitive (не логируется) | Сумма выплаты агенту по позиции. |
DistributionsCount | int | Да | PayoutOrderItemEntity.DistributionsCount | — | Число строк распределения, вошедших в позицию. |
PaidAt | DateTimeOffset? | Нет | PayoutOrderItemEntity.PaidAt | — | Момент выплаты; null пока поручение не Paid. |
Коллизия PaidAt. У позиции и у поручения поле PaidAt совпадает по значению (INV-7
domain/payout-order.md: MarkPaid проставляет PaidAt поручению, позициям и
строкам распределения в одной транзакции), поэтому в проекции — единственное PaidAt из позиции.
InvestorUserId в модели отсутствует (Masking-правила README.md); полный документ поручения и
чужие позиции агенту не отдаются.
Алгоритм
- Контекст и доступ. Актор — агент; tenant — его агентство (
Type ∈ {Agency, Broker, Pin}); требуется правоrewards.payouts.view-own. Агент видит только свои позиции:AgentUserIdберётся из токена, не из запроса. Тип компании актора вне{Agency, Broker, Pin}→FORBIDDEN. - Проверка входа.
Status, если задан, ∈ видимого агенту подмножества{PendingPayment, Paid, Rejected}(значенияDraft/Cancelledневидимы агенту →ValidationError);PeriodTo > PeriodFrom, если обе заданы;Page/PageSizeв границах конвенции — иначеValidationError. Входных id, требующих существования, нет. - Что читаем. Страница позиций агента, связанных с их родительскими поручениями. Отбор: позиция
принадлежит агенту (
AgentUserId = актор) и его агентству (TenantId), поручение в видимом агенту статусе{PendingPayment, Paid, Rejected}(черновики и отменённые не показываются); дополнительно — заданныйStatusи границы расчётного периода поручения (PeriodFrom/PeriodTo). Сортировка: сначала по дате выплаты (PaidAt, невыплаченные — в конце), затем по концу периода — свежие выплаты сверху. Опорный индекс —ix_payout_order_items_agent(tenant_id, agent_user_id). - Бизнес-правила и маппинг. Проекция «позиция + её поручение» в
MyPayoutItemModelполе-в-поле (дом README.md):Id,PayoutOrderId,Amount,DistributionsCount,PaidAt— из позиции;Number,Status(enum строкой),PeriodFrom,PeriodTo— из поручения;AgentUserIdне проецируется (всегда актор). Переходов состояний нет. - Результат. Страница
MyPayoutItemModel(Items,Page,PageSize,TotalCount) → HTTP200; суммы маскируются в логах (в теле — отдаются владельцу-агенту).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Status вне видимого агенту подмножества, PeriodTo <= PeriodFrom, PageSize вне 1..100 | «Проверьте параметры фильтра» | no retry |
FORBIDDEN | 403 | Нет rewards.payouts.view-own / актор не в агентстве | «Недостаточно прав» | no retry |
UNAUTHORIZED | 401 | Нет/просрочен JWT | «Требуется авторизация» | no retry |
Специфичных REWARDS_*-кодов у read-операции нет (мутаций/переходов нет); коды выше — общие, дом
README.md «Общие ошибки». Record-level изоляция реализуется фильтром, а не отдельной ошибкой:
чужие позиции просто не попадают в выборку (fail-closed).
Предусловия и постусловия
- Предусловия. Агент авторизован, состоит в агентстве (
Type ∈ {Agency, Broker, Pin}) и имеет правоrewards.payouts.view-own. Позиции выплат (PayoutOrderItemEntity) появляются, когда руководитель/ бухгалтер включаетAgent-строки распределения в платёжное поручение (create-payout-order.md / update-payout-order-items.md); агенту видны только поручения в статусахPendingPayment/Paid/Rejected(черновики и отменённые скрыты). - Постусловия. Только чтение — состояние не меняется, событий нет. Возвращена страница
MyPayoutItemModel, отобранная поAgentUserId = актори видимым статусам, отсортированная поPaidAt(невыплаченные — в конце), затем по концу периода.PaidAtнепуст только у позиций выплаченных поручений (INV-7 каскад).InvestorUserIdи чужие позиции в ответ не попадают (masking / record-level).
Acceptance-критерии
- Happy path. Дано: агент с правом
rewards.payouts.view-own, позиции в порученияхPendingPayment/Paid/Rejected. Когда:GET /api/v1/my/payouts. Тогда:200, страницаMyPayoutItemModelс суммой позиции, числом распределений, номером/статусом/периодом поручения; выплаченные (Paid) — сверху с непустымPaidAt. - Фильтр по видимому статусу. Дано:
Status = Paid. Когда:GET. Тогда:200, только позиции выплаченных поручений. - Скрытый статус. Дано:
Status = Draft(илиCancelled). Когда:GET. Тогда:400ValidationError— эти статусы агенту не видны (подмножество{PendingPayment, Paid, Rejected}). - Фильтр периода. Дано: заданы
PeriodFrom/PeriodToрасчётного периода поручения. Когда:GET. Тогда:200, только поручения сPeriodFrom >= PeriodFromиPeriodTo <= PeriodTo. - Некорректный период. Дано:
PeriodTo <= PeriodFrom. Когда:GET. Тогда:400ValidationError. - Пустая выборка. Дано: у агента ещё нет ни одной позиции в видимых поручениях. Когда:
GET. Тогда:200,Items = [],TotalCount = 0— не404. - Только свои позиции. Дано: агент пытается передать
AgentUserIdв query. Когда:GET. Тогда: параметр игнорируется — ключ из токена; чужие позиции не попадают в выборку (fail-closed record-level). - Нет права / не в агентстве. Дано: нет
rewards.payouts.view-ownлибо тип компании актора вне{Agency, Broker, Pin}. Когда:GET. Тогда:403FORBIDDEN.
Совместимость и наблюдаемость
MyPayoutItemModel— дельта-проекция отPayoutOrderItemModel+PayoutOrderModel(README.md): новые optional поля additive; удаление/переименование/смена типа — breaking (migration + rollout). Новые значенияPayoutOrderStatus— по unknown-enum-правилу cross-cutting (агенту остаётся видимо подмножество{PendingPayment, Paid, Rejected}).- Logs:
AgentUserId,TenantId, фильтры (Status, границы периода),TotalCount(суммы выплат не логируются); metrics:rewards_api_request_duration_seconds{route}; traces: сквозной trace черезtraceparent; dashboardrewards-overview.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): GET /api/v1/my/commission-accruals — Мои начисления (агент), GET /api/v1/my/commission-summary — Моя сводка комиссий (агент), Rewards API — реестр операций и общие контрактные модели
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность и статусная модель: domain/payout-order.md (позиция
PayoutOrderItemEntity, индексix_payout_order_items_agent, INV-7 каскадPaidAt, маппинг статусов на матрицу 5.5). - Базовые проекции и paging-конвенция: README.md («
PayoutOrderItemModel», «PayoutOrderModel», «Paging», Masking-правила). - Соседние agent-операции ЛК: list-my-accruals.md (мои начисления), get-my-commission-summary.md (сводка «получим/получили»).
- Сторона агентства (наполнение истории): list-payout-orders.md, mark-payout-order-paid.md, reject-payout-order.md.