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

GET /api/v1/agency/wallet/agent-balances — Балансы агентов агентства

Назначение

Отдаёт руководителю агентства баланс счёта самого агентства (AgencyCommission) и постраничный список AgentCommission-балансов агентов текущего состава: сколько каждому сотруднику начислено и ещё не выплачено. Управленческий инструмент для планирования платёжных поручений (rewards/domain/payout-order.md): баланс агента — это разница между кредитами по гейту получения денег и уже проведёнными выплатами.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (руководитель агентства / роль с доступом к командным данным)
Auth policy / Permissionswallet.team.view
Scope (RBAC)организация (только своё агентство)
Record-levelсчёт AgencyCommission компании актора + AgentCommission-счета агентов её текущего состава по display-реплике (см. «Границы выборки»)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/agency/wallet/agent-balances
Success status200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / Ограничения
AgencyCompanyIdidentity claimsGuidДакомпания актора — граница выборки
AgentUserIdqueryGuid?Нетточечный фильтр по одному агенту
Page / PageSizequeryintНетpaging — ../README.md

Границы выборки

Текущий состав агентов агентства резолвится по локальной display-реплике холдеров (../../domain/user-display-replica.md): агенты компании актора — строки реплики с CompanyId = компания актора. Синхронного вызова identity на read-path нет — состав реплицируется по событию pin.identity.user-upserted (смена агентства зеркалит CompanyId, ADR-0070), консюмер ../../consumers/identity-users.md. Реплика eventual: только что вступивший/вышедший агент попадает в ростер после применения snapshot-а (секунды, при лаге — минуты); это допустимая косметическая гонка на управленческом чтении, а не риск «утечки» — сами балансы ведёт журнал независимо от ростера, а доступ агента к своим данным отсекает identity (JWT).

Балансы user-centric и переживают смену агентства (../../domain/wallet-account.md): руководитель видит весь остаток AgentCommission-счёта агента текущего состава, включая накопленное в прежних агентствах. Разрез «сколько должно именно наше агентство» — это свободные Agent-строки распределений в rewards (rewards/domain/commission-accrual.md), не баланс кошелька.

Модель ответа AgencyWalletModel

ПолеТипОбязательностьИсточникОписание
AgencyBalanceWalletBalanceModel?Нетсчёт AgencyCommission (HolderCompanyId = компания актора)Баланс счёта агентства; null — счёт ещё не создан (кредитов не было). Дом модели — ../README.md.
AgencyDisplayNamestring?Нетdisplay-реплика (CompanyName строк агентов компании актора)Название агентства — подпись счёта AgencyCommission; null — компания ещё не реплицирована (user-display-replica.md).
Agentsстраница AgencyAgentBalanceModelДасчета агентов составаБалансы агентов (paging-конверт — ../README.md).

AgencyAgentBalanceModel

ПолеТипОбязательностьИсточникОписание
AgentUserIdGuidДаHolderUserId счётаАгент — холдер счёта.
HolderDisplayNamestring?Нетdisplay-реплика (DisplayName по AgentUserId)ФИО агента для подписи строки; null — реплика ещё не наполнена (клиент показывает id). Резолв из реплики, без синхронного identity (user-display-replica.md).
BalanceWalletBalanceModelДасчёт AgentCommissionБаланс счёта агента.
LastTransactionAtDateTimeOffset?Нетжурнал (последняя проводка счёта)Момент последнего движения — сигнал «давно не было выплат».

Сортировка — Balance по убыванию (наибольшие невыплаченные остатки сверху). Агенты состава без счёта (ни одного начисления, прошедшего гейт денег) в списке отсутствуют — их баланс равен нулю.

Алгоритм

  1. Определить компанию актора; permission wallet.team.view в scope организации — иначе FORBIDDEN.
  2. Проверить фильтры и paging → при нарушении ValidationError.
  3. Получить текущий состав агентов компании из display-реплики (строки с CompanyId = компания актора) — без синхронного identity. Заданный AgentUserId не входит в ростер реплики → FORBIDDEN (record-level).
  4. Выбрать счёт AgencyCommission компании и AgentCommission-счета агентов ростера (при заданном AgentUserId — только его счёт), подтянуть подписи из реплики (DisplayName агентов, CompanyName агентства), спроецировать в модель ответа, вернуть страницу. Отсутствие строки реплики → подпись null (клиент показывает id), не ошибка. История движений конкретного агента руководителю не отдаётся (только сам агент — wallet.transactions.view-own); детализация «за что начислено» — в rewards по своим permissions.

Ошибки и failure modes

Специфических ошибок нет: ростер берётся из локальной display-реплики, синхронного вызова identity на read-path больше нет (прежний WALLET_MEMBERSHIP_UNAVAILABLE/503 устранён). Только общие ошибки области (../README.md): ValidationError (400), UNAUTHORIZED (401), FORBIDDEN (403 — нет permission / агент вне ростера реплики / выборка вне своей организации). Пустой/устаревший ростер (лаг консюмера) ошибкой не является — 200 с текущим состоянием реплики.

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

  • Новые optional-поля модели — additive.
  • Logs: запрос без сумм (AgencyCompanyId, число строк); metrics: латентность p95; traces — traceparent.

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

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

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