7.9 KiB
Контекст
Portfolio сейчас получает текущее состояние счёта через T-Bank OperationsService и хранит только краткоживущие in-memory Portfolio Snapshots. В WealthLedger нет endpoint операций, типов операций, маршрута или локальной базы операций. Vendored T-Bank proto уже объявляет GetOperationsByCursor. Мотивация приведена в proposal.md, а поведение - в delta-spec brokerage-account-ledger.
Цели и нецели
Цели:
- Добавить защищённый read path с cursor-пагинацией для исполненных операций T-Bank.
- Сохранить соответствие «одна upstream-операция - одна запись» и точные денежные значения.
- Использовать существующие паттерны T-Bank auth, проверки счёта, типизированного OpenAPI и локальных UI-состояний.
Не входит в объём:
- Постоянное хранение, синхронизация или экспорт операций.
- Расчёт последовательных денежных остатков, итогов, налоговой отчётности или исторической оценки портфеля.
- Конвертация валют, рыночные изменения цены или настраиваемое пользователем количество последних событий.
Решения
Прямое чтение из T-Bank с кратким in-memory cache
Сервис журнала будет запрашивать GetOperationsByCursor по требованию и кратко кэшировать точный результат запроса с ключом из User, счёта и нормализованного query. Это соответствует времени жизни текущего Portfolio Snapshot и исключает Prisma schema, backfill и жизненный цикл синхронизации.
Рассмотренная альтернатива: сохранять и синхронизировать страницы операций, как в references/moex-vibe. Отклонена, поскольку запрошенной возможности нужна текущая история только для чтения, а не автономная локальная история или производный расчёт баланса.
Нормализованная страница операций на границе Brokerage Account
GET /api/v1/brokerage-accounts/:accountId/operations будет принимать необязательные включительные границы периода, подмножество категорий и cursor/limit. Ответ содержит id счёта, упорядоченные записи, nextCursor, hasNext и timestamp наблюдения. Он будет использовать существующую семантику принадлежности счёта и T-Bank token, чтобы недоступное connection, невалидный token и недоступный upstream оставались согласованными с Portfolio.
Backend сопоставляет T-Bank enum-типы шести стабильным категориям: trade, income, tax, fee, transfer, other. Он сохраняет исходный тип для точного label и будущего уточнения категорий. Запрашивается только OPERATION_STATE_EXECUTED.
Рассмотренная альтернатива: передавать raw protobuf-объекты операций. Отклонена, поскольку имена protobuf, кодирование денег и соглашения об optional-полях попадут в публичный API.
Одна upstream-операция - одна запись журнала
Mapper сохраняет payment, commission, price, accrued interest и количества отдельными optional-полями одной записи. Комиссия сделки не разделяется на вымышленную запись комиссии. Записи без payment не получают искусственное нулевое значение.
Это предотвращает двойной учёт, когда T-Bank передаёт и поле комиссии операции, и отдельные fee-операции. Decimal strings и ISO-коды валют следуют существующему контракту Portfolio; frontend только форматирует их и не выполняет финансовую арифметику.
Независимая устойчивость обзора и полной истории
Обзор счёта выполнит небольшой запрос журнала с limit=5 вместе с запросом Portfolio. Его loading, empty и error views остаются внутри блока последних событий. Полная страница по /accounts/:accountId/operations владеет состояниями периода, категорий и cursor; изменение фильтров сбрасывает cursor. Она компактно отображает строки без цены, количества и раскрытия деталей.
Рассмотренная альтернатива: tab внутри страницы Portfolio. Отклонена, поскольку controls периода и pagination сделают основной обзор громоздким и свяжут несвязанные error states.
Generated OpenAPI-типы на границе frontend
Swagger-контракт backend синхронизируется до frontend-работ. Frontend entity выводит типы request/response из generated paths и использует общий аутентифицированный API client. UI-labels, category controls и сообщения являются i18n-строками.
Риски и компромиссы
- [T-Bank не передаёт остаток после операции] -> Показывать только фактические изменения; отложить дизайн последовательного баланса до потребности в persisted history и reconciliation.
- [Расширение upstream enum] -> Сопоставлять неизвестные типы с
other, сохраняя raw type и upstream description. - [Повторная навигация создаёт upstream-запросы] -> Краткий cache точного query и frontend query cache сокращают дублирование, не делая данные постоянными.
- [Ошибка последних событий может скрыть Portfolio] -> Оставить состояние локальным для блока.
- [Период дат может содержать много операций] -> Использовать T-Bank cursor-пагинацию и ограниченные размеры страниц.
План миграции
- Развернуть backend endpoint и OpenAPI-контракт; миграция данных не требуется.
- Синхронизировать generated frontend API files и развернуть блок последних событий и маршрут истории.
- Для отката удалить frontend-навигацию и deployment endpoint; постоянные пользовательские данные не требуют очистки.