POST /api/v1/my/wallet/statement-exports — Постановка выписки по моему счёту (XLSX)
Асинхронная выгрузка выписки по одному счёту кошелька пользователя в XLSX (ADR-0041): операция создаёт задачу выгрузки, фоновый исполнитель формирует файл и кладёт его в приватное объектное хранилище; статус и короткоживущая ссылка на скачивание — get-wallet-statement-export-job.md. Контракт отчёта (строки/сальдо/обороты/формат) — ../../reports/wallet-statement.md.
Назначение
Холдер счёта (агент — AgentCommission, партнёр — ReferralCommission) выгружает банковскую выписку по
одному своему счёту за период: хронология проводок с дебетом/кредитом и нарастающим сальдо, сальдо на
начало и обороты. Нужна для сверки с бухгалтерией и разбора «из чего сложился остаток». Генерация
асинхронная — большие периоды не блокируют API. Feature group — my-wallet.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | wallet |
| API area | user |
| Feature group | my-wallet |
| Статус | draft |
| Документ | docs/06-services/wallet/api/user/create-wallet-statement-export.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент / партнёр — холдер счёта) |
| Auth policy / Permissions | wallet.transactions.view-own |
| Scope (RBAC) | платформа (личные данные пользователя) |
| Record-level | только счёт с HolderUserId = текущий пользователь; вид — AgentCommission/ReferralCommission |
| Tenant isolation | вне tenant-модели (счета user-centric); граница — HolderUserId из claims (не из body) |
| Audit | ExportSensitive: запись на каждую постановку (актор, AccountKind, период, время); финальный аудит (число строк, исход) — при завершении задачи |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/my/wallet/statement-exports |
| Success status | 202 (+ Location: /api/v1/my/wallet/statement-exports/{jobId}) |
| Idempotency header | не применяется; естественная защита от двойного клика: повтор с теми же (AccountKind, период, OperationKind), пока активна незавершённая задача, возвращает 202 с тем же JobId |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required; не из body — record-level граница | record-level |
AccountKind | body | string (enum WalletAccountKind) | Да | только AgentCommission / ReferralCommission; иной вид → ValidationError (системные/агентские компанейские счета пользователю недоступны) | выбор своего счёта |
PeriodFrom | body | DateTimeOffset | Да | Required; строки: OccurredAt >= PeriodFrom | — |
PeriodTo | body | DateTimeOffset | Да | PeriodTo > PeriodFrom (исключительно); диапазон ≤ 1 года — иначе WALLET_STATEMENT_PERIOD_TOO_WIDE | — |
OperationKind | body | string? (enum WalletOperationKind) | Нет | пусто = все виды; влияет только на строки (сальдо на начало — по всем проводкам до PeriodFrom) | — |
Счёт резолвится по паре (HolderUserId = UserId, AccountKind) — селектор всегда указывает на свой
счёт. Отсутствие счёта (кредитов ещё не было) — не ошибка: пустая выписка с нулевыми OpeningBalance и
оборотами (см. AC-5).
Модель ответа WalletStatementExportJobModel
Дом модели — ../../reports/wallet-statement.md. На постановке:
Status = Queued, DownloadUrl = null, FileName = null.
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь-холдер; scope — платформа; требуется право
wallet.transactions.view-own. - Проверка входа.
AccountKind ∈ {AgentCommission, ReferralCommission};PeriodTo > PeriodFrom; диапазон ≤ 1 года — иначеWALLET_STATEMENT_PERIOD_TOO_WIDE;OperationKind— известное значение enum, если задан. - Дедупликация двойного клика. Если у пользователя уже есть незавершённая задача (
Queued/Running) с теми жеAccountKind, периодом и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 шапки при генерации, недоступность → retry задачи); объектное хранилище (файл) |
| Уведомления | 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_STATEMENT_ROW_LIMIT_EXCEEDED | — (статус задачи Failed) | Выборка превысила 100 000 строк | «Слишком большой период — сузьте выборку» | no retry (сузить период) |
Acceptance criteria
- AC-1 (постановка и генерация). Given актор с
wallet.transactions.view-own,AccountKind = AgentCommissionи период ≤ 1 года; WhenPOST; Then202+LocationсJobId, задачаQueued, записан auditExportSensitive; фоновый исполнитель доводит задачу доCompleted, файл XLSX доступен через get-wallet-statement-export-job.md. - AC-2 (двойной клик). Given активная задача с теми же
AccountKind, периодом иOperationKind; When повторныйPOST; Then202с тем жеJobId— вторая задача не создаётся. - AC-3 (лимит конкурентности). Given у пользователя уже 2 активные задачи; When
POSTс другими фильтрами; ThenWALLET_STATEMENT_CONCURRENCY_LIMIT(429); активные задачи не прерываются. - AC-4 (период шире года). Given
PeriodTo − PeriodFrom= 18 месяцев; WhenPOST; ThenWALLET_STATEMENT_PERIOD_TOO_WIDE(400), задача не создаётся. - AC-5 (счёт ещё не создан). Given у пользователя не было кредитов выбранного вида (счёт lazy не
создан); When
POSTи генерация; Then задачаCompleted, выписка пустая:OpeningBalance = 0, обороты нулевые, строк нет — не ошибка. - AC-6 (недоступный вид счёта). Given
AccountKind = PlatformRevenue(илиAgencyCommission); WhenPOST; ThenValidationError(400) — пользователю доступны толькоAgentCommission/ReferralCommission. - AC-7 (доступ). Given актор без
wallet.transactions.view-own; WhenPOST; ThenFORBIDDEN(403), задача и аудит-запись выгрузки не создаются (пишется запись отказа доступа).
Совместимость и наблюдаемость
- Контракт additive-only: новые optional фильтры и новые значения enum — additive (unknown value от старого клиента невозможен — enum валидируется); смена структуры файла — версия контракта отчёта.
- Logs:
JobId,UserId,AccountKind, период,result(суммы/балансы не логируются); metrics:wallet_statement_export_jobs_total{area=user,result},wallet_statement_export_duration_seconds,wallet_statement_export_rows; traces: OTel, correlation черезtraceparent. - НФТ (ADR-0056): единицы выгрузок на пользователя в месяц; генерация — минуты (фон, не SLA API).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): GET /api/v1/my/wallet/statement-exports/{jobId} — Статус выписки и ссылка на скачивание, Wallet API — реестр операций и общие контрактные модели
- Отчёты (1): Отчет/Export: Выписка по счёту кошелька (wallet-statement)
- registries (1): Реестр API-методов PIN
Связанные документы
- Статус и скачивание: get-wallet-statement-export-job.md.
- Контракт отчёта и модель задачи: ../../reports/wallet-statement.md.
- Дома полей: ../../domain/ledger-transaction.md, ../../domain/wallet-account.md.
- Общие модели и ошибки: ../README.md.
- ADR-0041 (async export pipeline, signed URL, лимиты), ADR-0065 (счета/журнал), ADR-0048 (RUB-only), ADR-0056 (simplicity-first).