POST /api/v1/agency/wallet/statement-exports — Постановка выписки по счёту агентства/агента (XLSX)
Асинхронная выгрузка выписки по одному счёту кошелька в scope агентства в XLSX (ADR-0041): операция создаёт задачу выгрузки, фоновый исполнитель формирует файл и кладёт его в приватное объектное хранилище; статус и короткоживущая ссылка на скачивание — get-wallet-statement-export-job.md. Контракт отчёта — ../../reports/wallet-statement.md.
Назначение
Руководитель агентства выгружает банковскую выписку по одному счёту своего периметра: либо счёт самого
агентства (AgencyCommission), либо AgentCommission-счёт агента текущего состава — хронология проводок с
дебетом/кредитом и нарастающим сальдо. Нужна для сверки выплат агентам и управленческого контроля. Feature
group — agency-wallet.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | wallet |
| API area | agency |
| Feature group | agency-wallet |
| Статус | draft |
| Документ | docs/06-services/wallet/api/agency/create-wallet-statement-export.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (руководитель агентства / роль с доступом к командным данным) |
| Auth policy / Permissions | wallet.team.view |
| Scope (RBAC) | организация (только своё агентство) |
| Record-level | счёт AgencyCommission компании актора (AgentUserId не задан) либо AgentCommission-счёт агента текущего состава компании актора (AgentUserId задан) |
| Tenant isolation | AgencyCompanyId из claims (не из body); состав агентов резолвится по данным членства identity (fail closed) |
| Audit | ExportSensitive: запись на каждую постановку (актор, счёт/агент, период, время); финальный аудит — при завершении задачи |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/agency/wallet/statement-exports |
| Success status | 202 (+ Location: /api/v1/agency/wallet/statement-exports/{jobId}) |
| Idempotency header | не применяется; естественная защита от двойного клика: повтор с теми же (AgentUserId, период, OperationKind), пока активна незавершённая задача, возвращает 202 с тем же JobId |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
AgencyCompanyId | identity claims (tenant_id) | Guid | Да | Required; не из body — граница периметра | tenant isolation |
ActorUserId | identity claims (sub) | Guid | Да | Required | audit |
AgentUserId | body | Guid? | Нет | не задан → счёт AgencyCommission компании актора; задан → AgentCommission-счёт этого агента (агент обязан входить в текущий состав компании — иначе FORBIDDEN) | выбор счёта в периметре |
PeriodFrom | body | DateTimeOffset | Да | Required; строки: OccurredAt >= PeriodFrom | — |
PeriodTo | body | DateTimeOffset | Да | PeriodTo > PeriodFrom (исключительно); диапазон ≤ 1 года — иначе WALLET_STATEMENT_PERIOD_TOO_WIDE | — |
OperationKind | body | string? (enum WalletOperationKind) | Нет | пусто = все виды; влияет только на строки | — |
Выбор счёта: AgentUserId = null — счёт AgencyCommission (HolderCompanyId = компания актора);
AgentUserId задан — AgentCommission-счёт агента. Руководитель видит весь остаток
AgentCommission-счёта агента текущего состава, включая накопленное в прежних агентствах (счета
user-centric — ../../domain/wallet-account.md,
list-agency-agent-balances.md). Отсутствие счёта (кредитов ещё не было) —
не ошибка: пустая выписка (AC-5).
Модель ответа WalletStatementExportJobModel
Дом модели — ../../reports/wallet-statement.md. На постановке:
Status = Queued, DownloadUrl = null, FileName = null.
Алгоритм
- Контекст и доступ. Операцию выполняет руководитель агентства; scope — организация
(
AgencyCompanyIdиз claims); требуется правоwallet.team.view. - Проверка входа.
PeriodTo > PeriodFrom; диапазон ≤ 1 года — иначеWALLET_STATEMENT_PERIOD_TOO_WIDE;OperationKind— известное значение enum, если задан. - Резолюция счёта и record-level. Если
AgentUserIdзадан — получить текущий состав агентов компании изidentity(недоступен →WALLET_MEMBERSHIP_UNAVAILABLE,503, fail closed); агент не входит в состав →FORBIDDEN; счёт =AgentCommissionагента. ЕслиAgentUserIdне задан — счёт =AgencyCommissionкомпании актора. - Дедупликация двойного клика. Если у актора уже есть незавершённая задача (
Queued/Running) с теми же счётом (AgentUserId), периодом иOperationKind— новая не создаётся, возвращается202с еёJobId. - Лимит конкурентности. Активных задач (
Queued/Running) у актора — не более 2; превышение →WALLET_STATEMENT_CONCURRENCY_LIMIT. - Постановка задачи. В одном согласованном изменении создаются задача выгрузки (
Status = Queued, резолвленныйAccountIdили пометка «счёт ещё не создан», снапшот фильтров, актор) и audit-записьExportSensitive. Ответ —202+Location. - Фоновая генерация (вне запроса). Исполнитель формирует выписку по контракту
../../reports/wallet-statement.md, проверяет лимит 100 000
строк (превышение → задача
FailedсWALLET_STATEMENT_ROW_LIMIT_EXCEEDED), сериализует XLSX и кладёт файл в приватное хранилище; задача →Completed(илиFailed— частичный файл не публикуется). Retention файла — 7 дней, после —Expired.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none (задача выгрузки — внутренняя сущность wallet) |
| Внешние вызовы | резолюция состава агентства (identity, на постановке при заданном AgentUserId); фоновый исполнитель: identity (шапка), объектное хранилище (файл) |
| Уведомления | none (клиент опрашивает статус задачи) |
Ошибки и failure modes
Общие коды (ValidationError, FORBIDDEN, UNAUTHORIZED) — дом ../README.md «Общие
ошибки». Дельта операции (WALLET_STATEMENT_ROW_LIMIT_EXCEEDED фиксируется в задаче, не в HTTP-ответе):
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
WALLET_STATEMENT_PERIOD_TOO_WIDE | 400 | PeriodTo − PeriodFrom > 1 года | «Период выписки не может превышать один год» | no retry (сузить период) |
WALLET_STATEMENT_CONCURRENCY_LIMIT | 429 | У актора уже 2 активные задачи (Queued/Running) | «Дождитесь завершения текущих выгрузок» | retry после завершения |
WALLET_MEMBERSHIP_UNAVAILABLE | 503 | Состав агентства недоступен (identity не отвечает) при заданном AgentUserId — fail closed | retry | |
WALLET_STATEMENT_ROW_LIMIT_EXCEEDED | — (статус задачи Failed) | Выборка превысила 100 000 строк | «Слишком большой период — сузьте выборку» | no retry (сузить период) |
FORBIDDEN (403) — нет wallet.team.view / AgentUserId вне текущего состава компании актора.
Acceptance criteria
- AC-1 (счёт агентства). Given актор с
wallet.team.view,AgentUserIdне задан, период ≤ 1 года; WhenPOST; Then202+LocationсJobId, задача по счётуAgencyCommissionкомпании актора доводится доCompleted, файл доступен через get-wallet-statement-export-job.md. - AC-2 (счёт агента состава). Given
AgentUserIdагента текущего состава; WhenPOST; Then202, выписка поAgentCommission-счёту агента (весь остаток, включая прежние агентства). - AC-3 (агент вне состава). Given
AgentUserIdпользователя, не входящего в текущий состав компании актора; WhenPOST; ThenFORBIDDEN(403), задача не создаётся. - AC-4 (состав недоступен). Given
AgentUserIdзадан,identityне отвечает; WhenPOST; ThenWALLET_MEMBERSHIP_UNAVAILABLE(503, fail closed) — задача не создаётся. - AC-5 (счёт ещё не создан). Given у выбранного холдера не было кредитов (счёт lazy не создан);
When
POSTи генерация; Then задачаCompleted, выписка пустая (OpeningBalance = 0, строк нет). - AC-6 (период шире года). Given
PeriodTo − PeriodFrom= 18 месяцев; WhenPOST; ThenWALLET_STATEMENT_PERIOD_TOO_WIDE(400), задача не создаётся. - AC-7 (двойной клик). Given активная задача с тем же счётом, периодом и
OperationKind; When повторныйPOST; Then202с тем жеJobId.
Совместимость и наблюдаемость
- Контракт additive-only: новые optional фильтры и значения enum — additive; смена структуры файла — версия контракта отчёта.
- Logs:
JobId,AgencyCompanyId,AgentUserId?, период,result(суммы/балансы не логируются); metrics:wallet_statement_export_jobs_total{area=agency,result},wallet_statement_export_duration_seconds,wallet_statement_export_rows; traces: OTel, correlation черезtraceparent. - НФТ (ADR-0056): единицы выгрузок на агентство в месяц; генерация — минуты (фон, не SLA API).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): GET /api/v1/agency/wallet/statement-exports/{jobId} — Статус выписки и ссылка на скачивание, Wallet API — реестр операций и общие контрактные модели
- Отчёты (1): Отчет/Export: Выписка по счёту кошелька (wallet-statement)
- registries (1): Реестр API-методов PIN
Связанные документы
- Статус и скачивание: get-wallet-statement-export-job.md.
- Контракт отчёта и модель задачи: ../../reports/wallet-statement.md.
- Балансы команды (границы состава): list-agency-agent-balances.md.
- Дома полей: ../../domain/ledger-transaction.md, ../../domain/wallet-account.md.
- Общие модели и ошибки: ../README.md; зависимость от
identity(состав агентства): ../../README.md. - ADR-0041, ADR-0065 (счета/журнал), ADR-0048 (RUB-only), ADR-0052 (tenancy), ADR-0056 (simplicity-first).