feat: add operations list
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-14
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
## Контекст
|
||||||
|
|
||||||
|
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; постоянные пользовательские данные не требуют очистки.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
## Зачем
|
||||||
|
|
||||||
|
Текущий экран счёта показывает только состояние Portfolio на момент запроса. Пользователь не может понять, какими операциями изменялись деньги и инструменты счёта, включая выплаты купонов и дивидендов, налоги и комиссии.
|
||||||
|
|
||||||
|
## Что меняется
|
||||||
|
|
||||||
|
- Добавить read-only журнал операций одного Brokerage Account на основе исполненных операций T-Bank.
|
||||||
|
- Добавить краткий блок пяти последних операций на странице счёта и отдельную страницу полного журнала.
|
||||||
|
- Добавить выбор периода, фильтрацию по категориям и cursor-пагинацию полного журнала.
|
||||||
|
- Показывать исходные денежные значения и детали одной upstream-операции без синтетического разделения комиссий, конвертации валют или расчёта остатка после операции.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Новые возможности
|
||||||
|
- `brokerage-account-ledger`: просмотр исполненной истории операций и последних событий одного брокерского счёта.
|
||||||
|
|
||||||
|
### Изменяемые возможности
|
||||||
|
|
||||||
|
_Нет._
|
||||||
|
|
||||||
|
## Влияние
|
||||||
|
|
||||||
|
- Backend: T-Bank OperationsService, новый защищённый endpoint операций и его OpenAPI-контракт.
|
||||||
|
- Frontend: generated API types, entity операций, маршрут истории и блок последних событий на странице счёта.
|
||||||
|
- Документация: глоссарий WealthLedger и описание завершенной feature.
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Позволяет пользователю просматривать исполненные операции одного брокерского счёта, чтобы понимать фактические денежные и инструментальные движения.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: User can retrieve an account ledger
|
||||||
|
Система ДОЛЖНА предоставлять аутентифицированный журнал только для чтения одного Brokerage Account, принадлежащего текущему User. Журнал ДОЛЖЕН содержать только исполненные операции T-Bank и при отсутствии периода по умолчанию охватывать интервал от начала текущего календарного года до момента запроса.
|
||||||
|
|
||||||
|
#### Scenario: Получение журнала с периодом по умолчанию
|
||||||
|
- **WHEN** аутентифицированный User запрашивает журнал принадлежащего ему Brokerage Account без периода
|
||||||
|
- **THEN** система возвращает его исполненные операции от начала текущего календарного года до момента запроса
|
||||||
|
|
||||||
|
#### Scenario: Отклонение недоступного счёта
|
||||||
|
- **WHEN** аутентифицированный User запрашивает журнал счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||||
|
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает операции
|
||||||
|
|
||||||
|
### Requirement: User can browse a filtered ledger
|
||||||
|
Система ДОЛЖНА позволять User ограничивать журнал включительным периодом дат и любым подмножеством категорий trade, income, tax, fee, transfer, other. Система ДОЛЖНА разбивать упорядоченный результат на страницы с непрозрачным cursor и возвращать признак доступности следующей страницы.
|
||||||
|
|
||||||
|
#### Scenario: Фильтрация доходных операций за период
|
||||||
|
- **WHEN** User запрашивает период дат и категорию income
|
||||||
|
- **THEN** результат содержит только исполненные доходные операции с датой в пределах этого периода
|
||||||
|
|
||||||
|
#### Scenario: Продолжение истории
|
||||||
|
- **WHEN** User запрашивает cursor следующей страницы, возвращённый предыдущим ответом журнала
|
||||||
|
- **THEN** система возвращает следующую страницу без повторения записей предыдущей страницы
|
||||||
|
|
||||||
|
### Requirement: Ledger entries preserve broker operation meaning
|
||||||
|
Система ДОЛЖНА представлять одну upstream-операцию одной записью журнала и ДОЛЖНА передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля ДОЛЖНЫ сохранять исходную валюту и точное десятичное значение.
|
||||||
|
|
||||||
|
#### Scenario: Отображение купонной выплаты
|
||||||
|
- **WHEN** T-Bank возвращает исполненную купонную операцию с выплатой и данными инструмента
|
||||||
|
- **THEN** запись журнала определяет её как income и передаёт исходную сумму выплаты, валюту и данные инструмента
|
||||||
|
|
||||||
|
#### Scenario: Отображение комиссии сделки без синтетической записи
|
||||||
|
- **WHEN** T-Bank возвращает исполненную сделку, содержащую выплату и комиссию
|
||||||
|
- **THEN** журнал содержит одну запись сделки с комиссией в качестве её детали
|
||||||
|
|
||||||
|
#### Scenario: Отображение движения бумаг без денег
|
||||||
|
- **WHEN** T-Bank возвращает исполненную операцию, изменяющую состав инструментов, но без денежной выплаты
|
||||||
|
- **THEN** запись журнала передаёт доступное количество и не содержит искусственной денежной выплаты с нулевым значением
|
||||||
|
|
||||||
|
### Requirement: Account overview includes recent ledger events
|
||||||
|
Обзор счёта ДОЛЖЕН показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Неуспешный или пустой запрос последних записей ДОЛЖЕН оставаться локальным состоянием блока последних событий и НЕ ДОЛЖЕН заменять успешно отображённый обзор Portfolio.
|
||||||
|
|
||||||
|
#### Scenario: Отображение последних событий
|
||||||
|
- **WHEN** загружается обзор счёта и его журнал содержит исполненные операции
|
||||||
|
- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал
|
||||||
|
|
||||||
|
#### Scenario: Последние записи недоступны
|
||||||
|
- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой
|
||||||
|
- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления
|
||||||
|
|
||||||
|
### Requirement: Full ledger presents compact rows
|
||||||
|
Страница полного журнала ДОЛЖНА показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте. Она ДОЛЖНА обозначать записи без движения денег и НЕ ДОЛЖНА показывать цену или количество.
|
||||||
|
|
||||||
|
#### Scenario: Отображение записи только с инструментом
|
||||||
|
- **WHEN** полный журнал отображает запись без денежной выплаты
|
||||||
|
- **THEN** страница определяет её как запись без движения денег
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## 1. Backend-контракт журнала
|
||||||
|
|
||||||
|
- [x] 1.1 Добавить структурные типы request/response операций T-Bank и метод клиента `GetOperationsByCursor`, включая тесты клиента на передачу запроса и upstream-ошибки.
|
||||||
|
- [x] 1.2 Добавить DTO журнала и mapper, сохраняющий одну upstream-операцию одной записью, точные денежные строки, optional-детали и шесть стабильных категорий.
|
||||||
|
- [x] 1.3 Добавить тесты mapper для купонов, дивидендов, налогов, комиссий, сделок с комиссией, неизвестных типов и движений только инструментов.
|
||||||
|
|
||||||
|
## 2. Backend read path журнала
|
||||||
|
|
||||||
|
- [x] 2.1 Добавить валидируемые query-параметры журнала для периода, категорий, cursor и ограниченного limit.
|
||||||
|
- [x] 2.2 Добавить аутентифицированный сервис журнала Brokerage Account с проверкой принадлежности счёта, запросами исполненного состояния, кратким cache точного query и существующей семантикой ошибок T-Bank.
|
||||||
|
- [x] 2.3 Добавить `GET /brokerage-accounts/:accountId/operations`, Swagger responses и module wiring.
|
||||||
|
- [x] 2.4 Добавить тесты сервиса и controller для периода по умолчанию, включительных фильтров периода/категорий, продолжения по cursor, недоступного счёта и ошибок T-Bank.
|
||||||
|
|
||||||
|
## 3. Frontend API-интеграция
|
||||||
|
|
||||||
|
- [x] 3.1 Синхронизировать frontend OpenAPI snapshot и generated types из backend-контракта.
|
||||||
|
- [x] 3.2 Добавить public entity `broker-operation` с API/query hook на общем типизированном client и стабильным cache key.
|
||||||
|
- [x] 3.3 Добавить unit-тесты формирования query-параметров и поведения состояний query.
|
||||||
|
|
||||||
|
## 4. UI журнала счёта
|
||||||
|
|
||||||
|
- [x] 4.1 Добавить защищённый маршрут `/accounts/:accountId/operations` и страницу журнала счёта с изолированными loading, error и empty states.
|
||||||
|
- [x] 4.2 Добавить controls периода, выбор категорий и cursor-пагинацию; сбрасывать pagination при каждом изменении query.
|
||||||
|
- [x] 4.3 Отображать компактные строки операций в исходных валютах и обозначать записи без движения денег без цены, количества и раскрытия деталей.
|
||||||
|
- [x] 4.4 Добавить в обзор счёта блок из пяти последних событий и ссылку полного журнала, не связывая его состояние с загрузкой Portfolio.
|
||||||
|
- [x] 4.5 Добавить русские i18n-сообщения и UI-тесты фильтрации, страниц, раскрытия деталей, empty/error states и устойчивости последних событий.
|
||||||
|
|
||||||
|
## 5. Документация и проверка
|
||||||
|
|
||||||
|
- [x] 5.1 Добавить `Account Ledger` и `Ledger Entry` в глоссарий WealthLedger.
|
||||||
|
- [x] 5.2 Задокументировать завершённое поведение журнала в `docs/features`.
|
||||||
|
- [x] 5.3 Выполнить backend lint, tests и build; выполнить frontend verification и `openspec validate add-brokerage-account-ledger --type change --store wealth-ledger-specs`.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Brokerage Account Ledger Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Позволяет пользователю просматривать исполненные операции одного брокерского счёта, чтобы понимать фактические денежные и инструментальные движения.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: User can retrieve an account ledger
|
||||||
|
Система ДОЛЖНА предоставлять аутентифицированный журнал только для чтения одного Brokerage Account, принадлежащего текущему User. Журнал ДОЛЖЕН содержать только исполненные операции T-Bank и при отсутствии периода по умолчанию охватывать интервал от начала текущего календарного года до момента запроса.
|
||||||
|
|
||||||
|
#### Scenario: Получение журнала с периодом по умолчанию
|
||||||
|
- **WHEN** аутентифицированный User запрашивает журнал принадлежащего ему Brokerage Account без периода
|
||||||
|
- **THEN** система возвращает его исполненные операции от начала текущего календарного года до момента запроса
|
||||||
|
|
||||||
|
#### Scenario: Отклонение недоступного счёта
|
||||||
|
- **WHEN** аутентифицированный User запрашивает журнал счёта, который не является открытым поддерживаемым счётом, доступным через его T-Bank connection
|
||||||
|
- **THEN** система возвращает существующий ответ о недоступности счёта и не раскрывает операции
|
||||||
|
|
||||||
|
### Requirement: User can browse a filtered ledger
|
||||||
|
Система ДОЛЖНА позволять User ограничивать журнал включительным периодом дат и любым подмножеством категорий trade, income, tax, fee, transfer, other. Система ДОЛЖНА разбивать упорядоченный результат на страницы с непрозрачным cursor и возвращать признак доступности следующей страницы.
|
||||||
|
|
||||||
|
#### Scenario: Фильтрация доходных операций за период
|
||||||
|
- **WHEN** User запрашивает период дат и категорию income
|
||||||
|
- **THEN** результат содержит только исполненные доходные операции с датой в пределах этого периода
|
||||||
|
|
||||||
|
#### Scenario: Продолжение истории
|
||||||
|
- **WHEN** User запрашивает cursor следующей страницы, возвращённый предыдущим ответом журнала
|
||||||
|
- **THEN** система возвращает следующую страницу без повторения записей предыдущей страницы
|
||||||
|
|
||||||
|
### Requirement: Ledger entries preserve broker operation meaning
|
||||||
|
Система ДОЛЖНА представлять одну upstream-операцию одной записью журнала и ДОЛЖНА передавать её timestamp, тип операции, категорию, описание, доступные идентификаторы инструмента, денежную выплату, количество, цену, НКД и комиссию. Денежные поля ДОЛЖНЫ сохранять исходную валюту и точное десятичное значение.
|
||||||
|
|
||||||
|
#### Scenario: Отображение купонной выплаты
|
||||||
|
- **WHEN** T-Bank возвращает исполненную купонную операцию с выплатой и данными инструмента
|
||||||
|
- **THEN** запись журнала определяет её как income и передаёт исходную сумму выплаты, валюту и данные инструмента
|
||||||
|
|
||||||
|
#### Scenario: Отображение комиссии сделки без синтетической записи
|
||||||
|
- **WHEN** T-Bank возвращает исполненную сделку, содержащую выплату и комиссию
|
||||||
|
- **THEN** журнал содержит одну запись сделки с комиссией в качестве её детали
|
||||||
|
|
||||||
|
#### Scenario: Отображение движения бумаг без денег
|
||||||
|
- **WHEN** T-Bank возвращает исполненную операцию, изменяющую состав инструментов, но без денежной выплаты
|
||||||
|
- **THEN** запись журнала передаёт доступное количество и не содержит искусственной денежной выплаты с нулевым значением
|
||||||
|
|
||||||
|
### Requirement: Account overview includes recent ledger events
|
||||||
|
Обзор счёта ДОЛЖЕН показывать пять последних исполненных записей журнала этого счёта и действие перехода к его полному журналу. Неуспешный или пустой запрос последних записей ДОЛЖЕН оставаться локальным состоянием блока последних событий и НЕ ДОЛЖЕН заменять успешно отображённый обзор Portfolio.
|
||||||
|
|
||||||
|
#### Scenario: Отображение последних событий
|
||||||
|
- **WHEN** загружается обзор счёта и его журнал содержит исполненные операции
|
||||||
|
- **THEN** обзор показывает не более пяти последних записей и ссылку на полный журнал
|
||||||
|
|
||||||
|
#### Scenario: Последние записи недоступны
|
||||||
|
- **WHEN** обзор счёта успешно загружается, но запрос последних записей завершается ошибкой
|
||||||
|
- **THEN** обзор Portfolio остаётся доступным, а блок последних событий отображает собственное состояние восстановления
|
||||||
|
|
||||||
|
### Requirement: Full ledger presents compact rows
|
||||||
|
Страница полного журнала ДОЛЖНА показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте. Она ДОЛЖНА обозначать записи без движения денег и НЕ ДОЛЖНА показывать цену или количество.
|
||||||
|
|
||||||
|
#### Scenario: Отображение записи только с инструментом
|
||||||
|
- **WHEN** полный журнал отображает запись без денежной выплаты
|
||||||
|
- **THEN** страница определяет её как запись без движения денег
|
||||||
Reference in New Issue
Block a user