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