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

GET /api/v1/agency/payout-orders — Список платёжных поручений (история выплат)

Постраничная история выплат агентства: список всех платёжных поручений с номерами, периодами, суммами и статусами (ожидается / выплачено / отклонено) — экран «История выплат» ЛК руководителя агентства (матрица «Агенство» 5.5).

Назначение

Руководитель/бухгалтер агентства просматривает все платёжные поручения своего агентства — черновики в работе, переданные в бухгалтерию (PendingPayment = «ожидается»), выплаченные (Paid = «выплачено») и отклонённые (Rejected = «отклонено») — с фильтрами по статусу и периоду. Дом полей и статусная модель — domain/payout-order.md; проекция PayoutOrderModelREADME.md. Feature group — payouts.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (руководитель/бухгалтер/администратор агентства)
Auth policy / Permissionsrewards.payouts.manage (op22)
Scope (RBAC)организация: компания Type ∈ {Agency, Broker, Pin}
Record-levelне применяется (все поручения агентства; агенту — отдельный path list-my-payouts)
Tenant isolationTenantId = CompanyId агентства из claims; tenant-интерсептор (не из query)
Auditread-sensitive (суммы выплат): в trace — actor + фильтры + TotalCount; суммы не логируются

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/agency/payout-orders
Success status200
Idempotency headerне применяется (read)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claims (tenant_id)GuidДаRequired; не принимается из queryTenant isolation
StatusqueryPayoutOrderStatus?Нетenum Draft/PendingPayment/Paid/Rejected/Cancelled
CreatedFromqueryDateTimeOffset?Нетнижняя граница CreatedAt (включительно)
CreatedToqueryDateTimeOffset?Нетверхняя граница CreatedAt (исключительно); > CreatedFrom, если обе заданы
PeriodToqueryDateTimeOffset?Нетфильтр по концу расчётного периода: PayoutOrder.PeriodTo <= PeriodTo
Page / PageSizequeryintНетpaging-конвенция — README.md (Page >= 1, PageSize 1..100)

Модель ответа — постраничный список PayoutOrderModel

Постраничный ответ: Items (список PayoutOrderModel), Page, PageSize, TotalCount (paging-конвенция — README.md). Проекция PayoutOrderModel — дом README.md «Общие контрактные модели».

ПолеТипОбязательностьИсточникMaskingОписание
ItemsIReadOnlyList<PayoutOrderModel>Даprojection PayoutOrderEntityсуммы видны только роли с rewards.payouts.manageСтраница поручений (newest-first).
Page / PageSize / TotalCountint / int / longДаpagingПараметры страницы.

Дельта проекции для списка: PayoutOrderModel.Items (позиции PayoutOrderItemModel) в списке отдаётся пустой коллекцией — дочерняя payout_order_items не джойнится ради производительности истории; детализация состава загружается отдельным запросом одного поручения (GetById корня — используется командами состава/утверждения: update-payout-order-items.md, approve-payout-order.md). InvestorUserId в проекции отсутствует (Masking-правила README.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет руководитель/бухгалтер/администратор агентства (тип компании Agency/Broker/Pin); scope — организация (TenantId из claims, не из query); требуется право rewards.payouts.manage. Иначе — FORBIDDEN.
  2. Проверка входа. Status — допустимое значение enum PayoutOrderStatus, если задан; CreatedTo > CreatedFrom, если обе заданы; Page/PageSize — по конвенции; иначе ошибка валидации.
  3. Что читаем. Страница поручений агентства-tenant-а по фильтрам: опц. Status, границы CreatedAt, PeriodTo; сортировка — по CreatedAt DESC (история — свежие сверху); пагинация — по конвенции. Опорный индекс — ix_payout_orders_tenant_status при заданном Status, сортировка поддержана ix_payout_orders_created_at. Позиции поручений (payout_order_items) в список не включаются (детализация состава — отдельным запросом одного поручения).
  4. Проекция. Каждое поручение разворачивается в PayoutOrderModel поле-в-поле (дом README.md): Number, Title, PeriodFrom/To, Status (enum строкой), TotalAmount → MoneyModel, ItemsCount, ApprovedAt, PaidAt, RejectedAt, RejectReason, CancelledAt, CreatedAt, Version; Items = [] (пустая коллекция, см. дельту).
  5. Результат. Постраничный список (Items, Page, PageSize, TotalCount) → HTTP 200; суммы в логах маскируются, в теле доступны роли с permission.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Неверный Status (вне enum), CreatedTo <= CreatedFrom, PageSize вне 1..100«Проверьте параметры фильтра»no retry
FORBIDDEN403Нет rewards.payouts.manage / тип компании не агентство / чужой tenant«Недостаточно прав»no retry
UNAUTHORIZED401Нет/просрочен JWT«Требуется авторизация»no retry

Специфичных REWARDS_*-кодов у read-операции нет; коды выше — общие, дом README.md «Общие ошибки».

Acceptance criteria

  • AC-1 (история выплат). Given у агентства есть поручения в разных статусах; When руководитель запрашивает список без фильтра; Then 200, поручения по CreatedAt DESC (свежие сверху), у каждого — Number, Status, PeriodFrom/To, TotalAmount, ItemsCount, даты переходов; Items = [] (позиции в списке не джойнятся).
  • AC-2 (фильтр по статусу). Given те же данные; When Status = Paid; Then в выдаче только выплаченные поручения; аналогично PendingPayment = «ожидается», Rejected = «отклонено», Draft/Cancelled — рабочие/отменённые.
  • AC-3 (фильтр по периоду). Given поручения с разными CreatedAt/PeriodTo; When заданы CreatedFrom/CreatedTo (или PeriodTo); Then остаются только попавшие в границы; CreatedTo исключителен, CreatedFrom включителен.
  • AC-4 (детализация состава — отдельным запросом). Given элемент списка с ItemsCount = 5; When клиент открывает поручение; Then позиции по агентам грузятся отдельным запросом одного поручения (в списке Items = []) — экономия джойна на истории.
  • AC-5 (пустой результат / за пределами страницы). Given нет поручений под фильтр или Page за последней; Then 200, Items = [], корректный TotalCount (не 404).
  • AC-6 (валидация фильтра). Given Status вне enum, CreatedTo <= CreatedFrom или PageSize вне 1..100; Then ValidationError (400).
  • AC-7 (доступ и изоляция). Given актор без rewards.payouts.manage или не роль агентства — FORBIDDEN (403); Given актор агентства A — в выдаче только поручения A (TenantId из claims, не из query), суммы видны лишь ролям с rewards.payouts.manage, в логи не пишутся.
  • AC-8 (свежесть). Given только что утверждено/выплачено поручение; When сразу запрошена история; Then новый статус уже виден (строгая согласованность внутри rewards — история читается из OLTP по поручениям, а не из событий).

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

  • PayoutOrderModel — общая проекция из README.md: новые optional поля additive; удаление/ переименование/смена типа — breaking (migration + rollout). Новые значения PayoutOrderStatus — по unknown-enum-правилу cross-cutting.
  • Logs: TenantId, фильтры (Status, границы дат), TotalCount (суммы поручений не логируются); metrics: rewards_api_request_duration_seconds{route}; traces: OTel, correlation через traceparent; dashboard rewards-overview; runbook — не применяется (read).
  • НФТ (ADR-0056): единицы поручений на агентство в месяц — история измеряется десятками-сотнями строк; выборка идёт по ix_payout_orders_tenant_status/ix_payout_orders_created_at без джойна позиций; целевая латентность p95 < 250 мс; частота обращений низкая (back-office бухгалтерии/руководителя).

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

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

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