12 KiB
Пассивный доход брокерского счёта
Авторизованный User открывает /accounts/:accountId/income из обзора выбранного Brokerage Account. Экран показывает исполненные купоны и дивиденды за применённый календарный период: текстовые итоги по валютам, сравнения и раздел «Выплаты» с отдельной карточкой каждой включённой выплаты.
API
GET /api/v1/brokerage-accounts/:accountId/income-summary возвращает текущий месяц по сегодняшнюю дату для компактного блока обзора. Он не зависит от периода, который User ранее выбрал на странице аналитики.
GET /api/v1/brokerage-accounts/:accountId/income-analytics?from=YYYY-MM-DD&to=YYYY-MM-DD возвращает полный результат. Даты задают включительный период в Europe/Moscow; без них применяется текущий месяц. Период не может быть будущим или обратным. Максимальная длительность периода не ограничена.
Оба endpoint требуют Bearer token и используют обычные ответы о неподключённом T-Bank, неверном token, недоступном счёте и временной недоступности upstream.
Выбор периода и состояния страницы
В блоке «Период» два календарных поля Mantine DateInput показывают даты в формате
ДД.ММ.ГГГГ. Календарный выбор и ручной ввод изменяют только черновик. Кнопка
«Применить» или submit с клавиатуры проверяют видимый текст и передают допустимые
даты в URL как from=YYYY-MM-DD&to=YYYY-MM-DD. До применения результат сохраняется; отдельной
подписи периода под фильтрами нет. Открытие URL и навигация назад восстанавливают
применённый период.
Пустая, несуществующая или будущая дата получает ошибку у своего поля; обратный диапазон — у поля «По». Некорректный текст не заменяется последней допустимой датой при потере фокуса, не меняет URL и не отправляет запрос. Календарь учитывает допустимую соседнюю дату и сегодняшний день в Europe/Moscow. «Сбросить» сразу применяет текущий московский месяц по сегодняшний день, восстанавливает оба поля и очищает ошибки, даже если изменён только черновик.
При первой загрузке или новом периоде без cached data показан structural skeleton. Пустой результат объясняет отсутствие исполненных купонов и дивидендов, сохраняя фильтры, налоговое пояснение и сравнения. Ошибка запроса или повреждённый membership даёт локальное состояние с действием «Повторить». При фоновом обновлении того же периода Client Cached Snapshot и черновик остаются видимыми: загрузку обозначает progress, неуспешное обновление — предупреждение с retry.
На узком экране поля и действия идут вертикально; все карточки выплат остаются в общем потоке страницы без раскрытия или внутренних scroll-панелей.
Реализованный поток
- Backend проверяет доступ к счёту, проходит все cursor pages исполненных операций и не возвращает частичный результат при ошибке страницы или повреждённой cursor chain.
- Только точные upstream types coupon и dividend формируют результат. Суммы остаются exact decimal strings в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и dividend transfer не входят в доход.
- Итоги, сравнения, buckets и Instrument breakdown не смешивают валюты и не создают общий FX total. На странице показано только сравнение с годом ранее, когда календарная дата и история счёта позволяют. Предыдущий период остаётся в API response, но не отображается.
- Buckets по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые buckets. Страница дохода не строит из них график или текстовую динамику; API response не меняется.
- Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке
entries: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступная identity и signed Money видны в каждой карточке; валюты по-прежнему разделены в summary/comparisons. Дополнительный запрос за составом не нужен.
Раздел «Годом ранее» показывает цветные панели без общей белой подложки и
внутренних отступов у внешнего блока. Точные периоды показаны в заголовках двух панелей через
Intl.DateTimeFormat.formatRange с русской локалью и UTC для календарных дат.
Отдельного заголовка с датами и валютного бейджа нет; валюта остаётся у каждой суммы.
Панель «Разница» сохраняет серверную абсолютную разницу и дополняет итог, купоны
и дивиденды процентом: абсолютная разница / сумма годом ранее × 100.
Это округлённый показатель для отображения, а не пересчёт Money или инвестиционная
доходность. Intl.NumberFormat показывает знак и не более одного десятичного разряда.
При нулевой или отрицательной базе либо непредставимом числовом отношении
процентный бейдж не отображается; абсолютная разница остаётся видимой.
Связь breakdown с выплатами
Каждая запись income-analytics.entries содержит обязательный instrumentKey,
равный key соответствующей строки instruments. Backend вычисляет этот ключ
один раз при подготовке income entries и использует его же для группировки.
Приоритет UID, FIGI, ticker/type и operation ID с разделением по валюте остаётся
серверным правилом; клиент воспринимает ключ как непрозрачную строку.
Общий Ledger endpoint не приобретает income-specific поле.
Frontend проверяет принадлежность выплат по полученному ключу до подготовки плоского списка выплат. Отсутствующий, пустой или нестроковый ключ, неоднозначные instrument keys, неизвестная группа, другая валюта или несовпадение receipt count приводят к существующему локальному error/retry состоянию аналитики. Частичный список выплат не показывается как полный; формула идентификации в клиенте не восстанавливается. Полностью пустой ответ допустим. Состав и исходный серверный порядок entries и signed Money сохраняются, frontend не пересчитывает денежные итоги. Повторяющиеся operation IDs остаются отдельными строками; ключи учитывают instrumentKey, occurrence внутри группы и logo source для корректного обновления image fallback.
Совместимость membership и локальное обновление
Сначала обновляется backend: дополнительное поле совместимо с прежним frontend. Затем синхронизируются OpenAPI snapshot/generated types и обновляется frontend consumer. При переключении клиент перезагружается, чтобы не переносить старые in-memory results и незавершённые запросы. Новый frontend требует новый backend; отсутствие membership не скрывается fallback на прежнюю формулу.
При регрессии сначала возвращается прежний frontend вместе с соответствующим snapshot/types и перезагружается клиент; затем при необходимости возвращается backend. Старый frontend может временно работать с новым additive response. Календарные фильтры и отдельные карточки выплат используют тот же контракт и не требуют дополнительного обновления backend. SQLite schema, credentials и broker-reported операции не меняются: rollback не удаляет данные, производные результаты запрашиваются заново.
Будущие события и ограничения
На странице дохода нет блоков «Динамика» и «Ближайший прогноз». Она не запрашивает
/events и не зависит от его доступности. Самостоятельная страница
«Будущие события» по-прежнему доступна из обзора
выбранного Brokerage Account и сохраняет прежнее поведение. Будущие выплаты
не включаются в realized totals или сравнения.
Аналитика read-only, не хранит историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог.