Files
2026-08-14 13:48:26 +03:00

7.9 KiB
Raw Permalink Blame History

Контекст

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-пагинацию и ограниченные размеры страниц.

План миграции

  1. Развернуть backend endpoint и OpenAPI-контракт; миграция данных не требуется.
  2. Синхронизировать generated frontend API files и развернуть блок последних событий и маршрут истории.
  3. Для отката удалить frontend-навигацию и deployment endpoint; постоянные пользовательские данные не требуют очистки.