166 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Улучшение отображения брокерского портфеля
Дата: 2026-06-17
Статус: согласовано к планированию
## Контекст
Страница брокерского счета находится в `apps/frontend/src/pages/broker/BrokerAccountDetailPage.tsx`.
Сейчас она показывает все позиции одной таблицей, выводит колонку `Доходность`, не показывает отдельную
колонку текущей цены, не делает тикеры кликабельными и загружает операции одним запросом с `limit: 100`.
Данные для страницы уже есть в текущем frontend-контракте:
- `BrokerPosition.instrumentType`, `ticker`, `name`, `quantity`, `currentPrice`, `currentValue`;
- `BrokerOperation.type`, `category`, `description`, `ticker`, `instrumentType`, `payment`, `price`;
- `BrokerOperationsPage.nextCursor` и `hasNext` для cursor-пагинации.
Бэкенд и публичный API для этой задачи менять не нужно.
## Цель
Сделать страницу брокерского портфеля легче для чтения: разделить классы инструментов, убрать
лишнюю доходность из таблицы позиций, добавить текущую цену, сделать переходы к карточкам инструментов
и явно показать финансовый смысл операций.
## Область изменений
В рамках задачи меняется только frontend брокерской страницы:
- `apps/frontend/src/pages/broker/BrokerAccountDetailPage.tsx`;
- новые helper-функции и компоненты внутри `apps/frontend/src/pages/broker/`;
- тесты в `apps/frontend/src/pages/broker/` и существующий `BrokerPages.test.tsx`.
`apps/docs`, backend DTO, OpenAPI и codegen не меняются.
## Позиции
Позиции брокерского портфеля показываются отдельными секциями:
- `Акции` для `instrumentType === 'share'`;
- `Облигации` для `instrumentType === 'bond'`;
- `Другие инструменты`, если в портфеле есть позиции с другим или неизвестным типом.
Пустые секции не отображаются.
Таблица каждой секции использует компактные колонки:
| Колонка | Источник | Поведение |
| ---------- | -------------- | ---------------------------------------------------------- |
| Тикер | `ticker` | Если известен маршрут инструмента, тикер является ссылкой. |
| Название | `name` | Если имени нет, показывается `-`. |
| Количество | `quantity` | Если количества нет, показывается `-`. |
| Цена | `currentPrice` | Деньги форматируются как `ru-RU` currency. |
| Стоимость | `currentValue` | Деньги форматируются как `ru-RU` currency. |
Колонка `Доходность` удаляется из таблиц позиций брокерского портфеля. Доходность остается доступной
в summary над таблицами, где она не смешивается с построчным составом портфеля.
### Ссылки на инструменты
Маршрутизация повторяет ручной портфель:
- акция: `/stocks/:ticker`;
- облигация: `/bonds/:ticker`.
Если `ticker` отсутствует или тип инструмента не поддержан, значение показывается текстом без ссылки.
Для неизвестного `instrumentType` допускается fallback по `classCode`, если он однозначно указывает
на акцию или облигацию.
## Операции
Операции показываются по 10 штук на странице. Страница использует существующий cursor API:
- первый запрос: `{ limit: 10 }`;
- переход вперед: `{ limit: 10, cursor: nextCursor }`;
- переход назад: восстановление предыдущего cursor из локального stack в UI.
UI показывает номер текущей страницы, кнопку `Назад` и кнопку `Вперед`. `Назад` выключена на первой
странице. `Вперед` выключена, когда `hasNext === false` или нет `nextCursor`.
Таблица операций сохраняет основные колонки:
| Колонка | Источник | Поведение |
| ---------- | --------------------------------- | ------------------------------------------------------------- |
| Дата | `date` | `toLocaleString('ru-RU')`, при отсутствии `-`. |
| Тип | `type`, `description`, `category` | Русскоязычное название и бейдж эффекта. |
| Инструмент | `ticker`, `description` | Если известен маршрут инструмента, значение является ссылкой. |
| Сумма | `payment` | Форматируется как деньги и окрашивается по эффекту операции. |
### Русские названия типов
Для известных T-Bank enum-значений frontend показывает русские названия. Минимальный набор:
| T-Bank type | Название |
| ------------------------------------ | -------------------------- |
| `OPERATION_TYPE_BUY` | Покупка |
| `OPERATION_TYPE_BUY_CARD` | Покупка |
| `OPERATION_TYPE_SELL` | Продажа |
| `OPERATION_TYPE_SELL_CARD` | Продажа |
| `OPERATION_TYPE_COUPON` | Выплата купона |
| `OPERATION_TYPE_DIVIDEND` | Дивиденды |
| `OPERATION_TYPE_BOND_REPAYMENT` | Погашение облигации |
| `OPERATION_TYPE_BOND_REPAYMENT_FULL` | Полное погашение облигации |
| `OPERATION_TYPE_TAX` | Налог |
| `OPERATION_TYPE_BOND_TAX` | Налог по облигациям |
| `OPERATION_TYPE_DIVIDEND_TAX` | Налог на дивиденды |
| `OPERATION_TYPE_BROKER_FEE` | Комиссия брокера |
| `OPERATION_TYPE_SERVICE_FEE` | Комиссия за обслуживание |
| `OPERATION_TYPE_INPUT` | Пополнение |
| `OPERATION_TYPE_OUTPUT` | Вывод средств |
| `OPERATION_TYPE_INPUT_SECURITIES` | Зачисление бумаг |
| `OPERATION_TYPE_OUTPUT_SECURITIES` | Списание бумаг |
Если тип неизвестен, но есть `description`, показывается `description`. Если нет и описания,
показывается очищенный enum без префикса `OPERATION_TYPE_`.
### Эффект операции
Выбранный дизайн: бейдж эффекта в колонке `Тип` плюс цвет суммы.
Эффект определяется не только знаком суммы, потому что сделки не являются доходом сами по себе:
- `Пополнение` и доходы вроде купонов или дивидендов получают эффект `Пополняет`;
- налоги, комиссии и вывод средств получают эффект `Списывает`;
- покупки, продажи, ввод/вывод бумаг и погашение тела облигации получают эффект `Перекладка`;
- неизвестные операции получают эффект `Неясно`.
Цвета используются как вспомогательный признак, а текст бейджа остается основным признаком для
доступности:
- `Пополняет`: зеленый акцент;
- `Списывает`: красный акцент;
- `Перекладка`: нейтральный или синий акцент;
- `Неясно`: приглушенный серый акцент.
## Ошибки и пустые состояния
- Если портфель не загрузился, остается текущее сообщение об ошибке портфеля.
- Если операции загружаются, показывается `Загрузка операций...`.
- Если операций нет, показывается пустое состояние `Операций за выбранный период нет`.
- Если у позиции или операции нет тикера, вместо ссылки показывается текстовое значение или `-`.
## TDD-стратегия
Сначала пишутся failing tests:
1. Helper-тесты для группировки позиций, маршрутов инструментов, русских названий операций и эффекта операций.
2. Component/page-тесты для раздельных таблиц акций и облигаций, отсутствия колонки `Доходность` и наличия `Цена`.
3. Component/page-тесты для кликабельных инструментов в позициях и операциях.
4. Component/page-тесты для cursor-пагинации операций по 10 элементов.
Затем реализуются helper-функции, компоненты таблиц и интеграция в `BrokerAccountDetailPage`.
## Acceptance Criteria
- Позиции акций и облигаций отображаются в отдельных таблицах.
- Позиции с другим типом отображаются в `Другие инструменты`, если такие позиции есть.
- В таблицах позиций нет колонки `Доходность`.
- В таблицах позиций есть колонка `Цена`.
- Тикеры позиций ведут на `/stocks/:ticker` или `/bonds/:ticker`.
- Операции запрашиваются с `limit: 10`.
- Пользователь может переходить вперед и назад по cursor-страницам операций.
- В операциях колонка `Инструмент` ведет на страницу акции или облигации, если известен тип инструмента.
- В операциях колонка `Тип` показывает русскоязычные названия.
- В операциях визуально понятно, пополняет операция портфель, списывает средства, является перекладкой или не классифицирована.