docs(specs): archive brokerage account list change
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
schema: wealth-ledger-sdd
|
||||
created: 2026-08-26
|
||||
goal: Improve the all-accounts overview with comparable current portfolio summaries
|
||||
and an informative, accessible account-list interface.
|
||||
affected_areas:
|
||||
- frontend
|
||||
- backend
|
||||
- specs
|
||||
- docs
|
||||
@@ -0,0 +1,195 @@
|
||||
## Context
|
||||
|
||||
`GET /api/v1/brokerage-accounts` уже разрешает текущее T-Bank connection и
|
||||
поддерживаемые счета через `TbankAccessService`, сохраняя upstream-порядок и
|
||||
существующее отображение публичных ошибок. Получение Portfolio использует ту
|
||||
же границу доступа и рассчитывает точные итоги по каждой валюте из значений
|
||||
позиций `GetPortfolio` и денежных остатков `GetPositions`. Текущая
|
||||
нормализация также обогащает позиции для экрана деталей счёта, что не нужно
|
||||
для сводки списка счетов.
|
||||
|
||||
Frontend уже потребляет сгенерированный OpenAPI-тип через один
|
||||
аутентифицированный React Query hook с 60-секундной политикой актуальности. Его
|
||||
page VM сохраняет успешный query snapshot во время фонового refetch и выбирает
|
||||
известные error-состояния списка. Сейчас entity рендерит двухколоночную
|
||||
коллекцию ссылок только с метаданными счета. Новый контракт должен сохранить
|
||||
существующую семантику connection, query, навигации и клавиатуры, обогащая
|
||||
каждый элемент без представления разных валют как единого значения.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Расширить существующий контракт списка явной nullable-сводкой Portfolio для
|
||||
каждого счёта, сохранив policy доступа к счетам и upstream-порядок.
|
||||
- Переиспользовать точный сохраняющий валюты расчёт итогов Portfolio и для
|
||||
snapshot деталей, и для сводок списка без работы с метаданными позиций.
|
||||
- Локализовать временный upstream-сбой на сводке затронутого счёта, сохранив
|
||||
установленную семантику ошибок connection и token на уровне списка.
|
||||
- Сохранить существующий 60-секундный React Query-кэш и policy Client Cached
|
||||
Snapshot, выражая состояния сводки через текущую границу page VM и
|
||||
presentation.
|
||||
- Обновить as-built документацию только после проверки поставленного поведения.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- FX-conversion, межвалютный или межсчётный агрегат, отдельное отображение Cash
|
||||
Balance, сортировка/фильтрация/настройки списка или постоянное хранение
|
||||
Portfolio.
|
||||
- Переработка Portfolio на странице деталей счёта, управления T-Bank
|
||||
connection, resilience policy T-Bank client или глобальной frontend fetch
|
||||
policy.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1. Обогатить каждый нормализованный счёт nullable-сводкой на существующей границе списка
|
||||
|
||||
`TbankAccessService` продолжит владеть разрешением current connection, policy
|
||||
поддерживаемых счетов, обработкой token и начальным вызовом `GetAccounts`.
|
||||
Поток списка сделает разрешённый token и ID поддерживаемых upstream-счетов
|
||||
доступными `BrokerageAccountsService`, который сохранит ответственность за
|
||||
формирование ответа списка. Для каждого поддерживаемого счёта он параллельно
|
||||
запросит два upstream-входа Portfolio и сохранит нормализованный счёт на его
|
||||
исходной позиции.
|
||||
|
||||
Работа по каждому счёту будет собрана без изменения порядка счетов. Результат
|
||||
`TBANK_UNAVAILABLE` любого из входов сводки создаёт `portfolioSummary: null`
|
||||
только для этого счёта. Начальное разрешение connection, обнаружение счетов,
|
||||
перевод invalid-token и непредвиденные ошибки сохраняют текущее поведение
|
||||
Problem Details на уровне списка; они не превращаются молча в частичный успех.
|
||||
|
||||
*Альтернатива:* вызывать public Portfolio service страницы деталей для каждого
|
||||
счёта. Отклонено, поскольку это свяжет список с cache snapshot деталей,
|
||||
семантикой timestamp и stale-fallback и вызовет нерелевантное обогащение
|
||||
метаданных Instrument.
|
||||
|
||||
*Альтернатива:* делать любую неуспешную сводку ошибкой списка. Отклонено,
|
||||
поскольку это противоречит требуемому поведению частичной доступности и
|
||||
скрывает в ином случае доступную навигацию к счёту.
|
||||
|
||||
### D2. Извлечь общий normalizer точных итогов, а не вторую модель Portfolio
|
||||
|
||||
Небольшой backend-owned normalizer примет upstream-значения позиций и денежные
|
||||
остатки и вернёт итоги `Money[]`, сгруппированные по нормализованной исходной
|
||||
валюте. Он сохранит существующую decimal-арифметику и правила включения:
|
||||
нулевые значения или значения без валюты исключаются; валидные текущие
|
||||
значения позиций и Cash Balance в одной валюте точно складываются;
|
||||
FX-conversion не выполняется. Portfolio service деталей использует helper при
|
||||
формировании существующего snapshot, а поток списка обернёт тот же результат
|
||||
как `{ totalsByCurrency }`.
|
||||
|
||||
Это сохраняет денежный инвариант в одном месте и устраняет зависимость от
|
||||
маппинга позиций Portfolio, запросов метаданных, timestamp или cache-поведения.
|
||||
Пустой успешный upstream Portfolio становится пустым массивом итогов и,
|
||||
следовательно, доступной non-null сводкой списка.
|
||||
|
||||
*Альтернатива:* дублировать текущий aggregation loop в service списка.
|
||||
Отклонено, поскольку две реализации точных денежных расчётов могут разойтись
|
||||
на edge cases денежных остатков или валют.
|
||||
|
||||
*Альтернатива:* возвращать в списке полный `PortfolioDto`. Отклонено, поскольку
|
||||
контракт списка не требует ни позиций, ни метаданных snapshot/cache и раскроет
|
||||
избыточно дорогой payload.
|
||||
|
||||
### D3. Сделать OpenAPI DTO и generated client единственным транспортным изменением
|
||||
|
||||
`BrokerageAccountDto` получает nullable `portfolioSummary`, единственное поле
|
||||
которой — `totalsByCurrency: MoneyDto[]`. Swagger/OpenAPI описывает эту форму,
|
||||
после чего frontend snapshot и generated types обновляются установленным
|
||||
workflow runtime contract. Frontend-код продолжает выводить тип счёта из
|
||||
сгенерированных `paths`, не создавая параллельный transport interface.
|
||||
|
||||
Это совместимое аддитивное изменение ответа, но оно остаётся изменением
|
||||
публичного контракта и требует cross-repository gate runtime/OpenAPI.
|
||||
|
||||
### D4. Сохранить query-поведение списка; подготовить представление карточек в page VM
|
||||
|
||||
Существующие query key brokerage-account, 60-секундный `staleTime`, policy без
|
||||
retry и invalidation после замены/удаления T-Bank connection не изменяются.
|
||||
Accounts page VM сохранит текущее top-level выделение loading/error/empty/loaded
|
||||
и поведение cached snapshot. Он подготовит ограниченную display model каждой
|
||||
карточки из generated account: не более двух итогов исходных валют и явный
|
||||
счётчик/индикатор остальных валют либо раздельные локальные состояния
|
||||
недоступной и пустой сводки.
|
||||
|
||||
Presentation component списка счетов станет одноколоночной последовательностью
|
||||
широких карточек-ссылок. Каждая ссылка сохраняет навигацию по всей карточке и
|
||||
native keyboard focus и показывает локализованные тип, имя, сводку, вторичные
|
||||
метаданные даты открытия и видимое действие открытия. Presentation-код
|
||||
форматирует одно переданное значение Money существующим shared formatter; он
|
||||
не агрегирует и не конвертирует значения.
|
||||
|
||||
*Альтернатива:* рассчитывать видимые два итога в entity view. Отклонено,
|
||||
поскольку выбор и подсчёт значений из коллекции — derived presentation data,
|
||||
принадлежащие page VM согласно frontend VM boundary.
|
||||
|
||||
*Альтернатива:* ввести новый client cache или независимо получать Portfolio
|
||||
каждого счёта. Отклонено, поскольку обогащённый endpoint списка уже передаёт
|
||||
нужное компактное наблюдение, а отдельные query добавят fan-out, loading states
|
||||
и пути invalidation.
|
||||
|
||||
## Delivery Map
|
||||
|
||||
| Area | Status | Architectural outcome or N/A reason |
|
||||
| --- | --- | --- |
|
||||
| frontend | `affected` | Regenerated list response type, существующие query policy/invalidation, page VM card display models и доступные одноколоночные summary cards реализуют обогащённые состояния списка. |
|
||||
| backend | `affected` | Существующая account access boundary поставляет поддерживаемые счета; общий normalizer точных итогов Portfolio и orchestration списка формируют сводки по каждому счёту с локальной обработкой upstream-недоступности и аддитивным DTO. |
|
||||
| specs | `affected` | Этот change хранит утверждённые Proposal, Delta Specs, Design и последующие implementation Tasks в canonical store. |
|
||||
| docs | `affected` | После поставки as-built Brokerage Accounts и релевантная system documentation описывают фактический обогащённый контракт списка, частичную доступность сводки и неизменную границу наблюдения current connection. |
|
||||
|
||||
### Contract Gate Decision
|
||||
|
||||
| Gate | Status | Reason |
|
||||
| --- | --- | --- |
|
||||
| runtime-openapi | `required` | Backend изменяет публичный ответ списка, а frontend потребляет его generated OpenAPI snapshot. |
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Upstream fan-out растёт с количеством поддерживаемых счетов.** Список
|
||||
выполняет два Portfolio RPC на счёт, но использует существующие client-wide
|
||||
rate limiter, deadline и retry policy вместо создания другого механизма
|
||||
concurrency. Временно недоступная отдельная сводка деградирует локально.
|
||||
- **Расхождение агрегатов сводки и деталей.** Общая точная нормализация и
|
||||
фокусированные money tests исключают разную обработку Cash Balance, нулевых
|
||||
сумм и исходных валют.
|
||||
- **Частичный ответ может скрыть невалидные учётные данные.** Локальным для
|
||||
сводки является только известный результат недоступности; token и другие
|
||||
сбои уровня списка проходят через существующий error translation.
|
||||
- **Широкие карточки могут стать хуже для сканирования на малых экранах.**
|
||||
Одноколоночный layout сохраняет компактные вторичные метаданные, ограничивает
|
||||
отображаемые валюты двумя и явно обозначает пропущенные.
|
||||
- **Кэшированные данные могут быть временно устаревшими.** Это сохраняет
|
||||
установленный trade-off 60-секундного Client Cached Snapshot: фоновое
|
||||
обновление и его предупреждение остаются видимыми, а известные данные не
|
||||
очищаются.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Добавить компактный summary DTO и разделить точную нормализацию итогов
|
||||
Portfolio между существующим потоком деталей и обогащённым потоком списка
|
||||
счетов.
|
||||
2. Расширить orchestration списка для получения сводок внутри существующей
|
||||
T-Bank access и resilience boundary, сохранив порядок счетов и отображая в
|
||||
`null` только недоступность отдельного счёта.
|
||||
3. Перегенерировать frontend OpenAPI snapshot/types из работающего backend и
|
||||
пройти runtime contract gate.
|
||||
4. Адаптировать существующие consumer account query, page VM, карточки,
|
||||
переводы и risk-based tests, сохранив loading, error, empty, cache и
|
||||
invalidation behavior.
|
||||
5. После проверки реализации обновить as-built feature/system documentation
|
||||
до поставленных контракта и поведения.
|
||||
|
||||
### Rollback Contract
|
||||
|
||||
| Field | Decision |
|
||||
| --- | --- |
|
||||
| Status | `required` |
|
||||
| Reason | Аддитивное поле ответа — публичный API contract, потребляемый frontend. |
|
||||
| Trigger | Release-blocking defect в доступности списка счетов, точности денежной сводки или совместимости с generated client. |
|
||||
| Actions | Одновременно откатить frontend consumption и backend additive summary/DTO; перед выпуском откатываемой пары перегенерировать и закоммитить соответствующий frontend OpenAPI snapshot. |
|
||||
| Data safety | Schema, persisted Portfolio, Brokerage Account или данные владения не создаются и не мутируют; rollback лишь убирает из ответа текущее upstream-наблюдение. |
|
||||
| Verification | Запустить canonical gates затронутых backend и frontend, а также runtime OpenAPI gate против откатанного контракта; проверить, что список сохраняет прежние метаданные, навигацию и error states. |
|
||||
|
||||
## Open Questions
|
||||
|
||||
<!-- none -->
|
||||
@@ -0,0 +1,52 @@
|
||||
## Why
|
||||
|
||||
Страница всех брокерских счетов сейчас помогает только выбрать счёт по имени,
|
||||
типу и дате открытия. Пользователь не может сравнить текущее состояние счетов
|
||||
до перехода на каждую отдельную страницу; двухколоночные плитки также не дают
|
||||
достаточно места для финансовых итогов и плохо поддерживают быстрое
|
||||
сканирование списка.
|
||||
|
||||
Нужен компактный, но информативный обзор: пользователь должен сравнить счета и
|
||||
перейти к нужному, не принимая мультивалютные суммы за одну конвертированную
|
||||
стоимость и не теряя доступ ко всему списку из-за временной проблемы данных
|
||||
одного счёта.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Расширить наблюдаемый список Brokerage Account текущей сводкой Portfolio по
|
||||
исходным валютам для каждого доступного счёта. Сводка показывает не более
|
||||
двух валютных итогов и наличие остальных валют, не выполняя и не подразумевая
|
||||
FX-конвертацию; Cash Balance не выдаётся за отдельный второй итог.
|
||||
- Сделать недоступность сводки одного счёта локальным состоянием карточки:
|
||||
остальные счета и переход к их деталям остаются доступными.
|
||||
- Переработать экран всех счетов в адаптивный одноколоночный список широких
|
||||
интерактивных карточек с ясной иерархией: тип, имя, финансовая сводка,
|
||||
вторичные метаданные и заметное действие открытия счёта. Карточка сохраняет
|
||||
переход по всей области и доступное клавиатурное состояние.
|
||||
- Сохранить существующие штатные состояния загрузки, пустого списка и ошибок
|
||||
списка, включая безопасное отображение последнего успешного клиентского
|
||||
snapshot при фоновом обновлении.
|
||||
- **Non-goals:** не добавлять сортировку, фильтры, пользовательские настройки
|
||||
списка, FX-конвертацию или агрегированный итог по разным счетам; не вводить
|
||||
долговременное хранение Portfolio или Brokerage Account и не менять страницу
|
||||
деталей счёта сверх совместимости со списком.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `brokerage-accounts`: список счетов и его экран должны предоставлять
|
||||
сопоставимую мультивалютную сводку Portfolio, устойчиво показывать частичную
|
||||
недоступность сводки и представлять счета в информативном адаптивном списке.
|
||||
|
||||
## Impact
|
||||
|
||||
Затрагиваются авторизованные пользователи, открывающие `/accounts`, а также
|
||||
публичный контракт списка Brokerage Account и его frontend-потребитель.
|
||||
Потребуются синхронные изменения требований, backend-наблюдения данных T-Bank,
|
||||
frontend-представления и as-built документации; долговременная модель данных,
|
||||
аутентификация и T-Bank connection остаются вне scope.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Список активных брокерских счетов
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив открытых поддерживаемых инвестиционных счетов T-Bank, доступных через текущий T-Bank connection User, в порядке, полученном от T-Bank. Каждый элемент MUST содержать стабильный `id`, непустое `name`, нормализованный `type` (`BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или `MONEY_MARKET_FUND`) и nullable `openedOn` в формате `YYYY-MM-DD`. Система MUST включать только счета T-Bank типов «брокерский счёт», «ИИС», «Инвесткопилка» и «фонд денежного рынка»; банковские и неизвестные типы MUST NOT возвращаться. Система MUST удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа.
|
||||
|
||||
Каждый элемент MUST также содержать nullable `portfolioSummary`. Доступная сводка MUST содержать `totalsByCurrency`: точные Money-итоги Portfolio по исходным ISO-валютам, включающие стоимость позиций и Cash Balance в той же валюте. Она MUST NOT содержать FX-конвертированный или межвалютный итог и MUST NOT выдавать Cash Balance за самостоятельный второй итог. Если сводка Portfolio конкретного доступного счёта временно недоступна, `portfolioSummary` MUST быть `null`, не исключая этот счёт из списка и не делая список неуспешным. Пустой доступный Portfolio MUST передаваться доступной сводкой с пустым `totalsByCurrency`, а не `null`.
|
||||
|
||||
Список Brokerage Account и его сводки являются результатом наблюдения текущего брокерского подключения и MUST NOT создавать ожидание, что WealthLedger хранит долговременное право владения счётом или Portfolio независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Поддерживаемые счета возвращаются в upstream-порядке
|
||||
- **WHEN** User с действующим T-Bank connection запрашивает список, а T-Bank возвращает открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт
|
||||
- **THEN** система отвечает `200` массивом брокерского счёта и ИИС в исходном порядке T-Bank без дебетового счёта
|
||||
|
||||
#### Scenario: Пустой список поддерживаемых счетов
|
||||
- **WHEN** User с действующим T-Bank connection не имеет открытых поддерживаемых инвестиционных счетов
|
||||
- **THEN** система отвечает `200` пустым массивом
|
||||
|
||||
#### Scenario: Пустое название получает fallback
|
||||
- **WHEN** T-Bank возвращает поддерживаемый открытый счёт с пустым либо состоящим из пробелов именем
|
||||
- **THEN** система отвечает этим счётом с непустым названием его нормализованного типа
|
||||
|
||||
#### Scenario: Дата открытия отсутствует
|
||||
- **WHEN** T-Bank не возвращает дату открытия поддерживаемого открытого счёта
|
||||
- **THEN** система отвечает этим счётом с `openedOn: null`
|
||||
|
||||
#### Scenario: Мультивалютная сводка счёта
|
||||
- **WHEN** доступный Portfolio счёта содержит итоги в RUB и USD, включая Cash Balance в USD
|
||||
- **THEN** ответ содержит одну доступную `portfolioSummary` с отдельными Money-итогами RUB и USD без FX-конвертации, общего итога или отдельного итога Cash Balance
|
||||
|
||||
#### Scenario: Сводка одного счёта временно недоступна
|
||||
- **WHEN** список доступных счетов получен, но Portfolio одного из них временно недоступен
|
||||
- **THEN** система отвечает `200` всеми счетами в исходном порядке, передаёт для этого счёта `portfolioSummary: null` и не заменяет успешные сводки остальных счетов ошибкой списка
|
||||
|
||||
#### Scenario: Доступный Portfolio пуст
|
||||
- **WHEN** доступный счёт не имеет ненулевых позиций и доступных денежных остатков
|
||||
- **THEN** система передаёт доступную `portfolioSummary` с пустым `totalsByCurrency`
|
||||
|
||||
### Requirement: Экран списка брокерских счетов
|
||||
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд. После истечения этого срока клиент MUST запрашивать актуальный список; при уже имеющемся успешном списке он MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового обновления и после его ошибки, ясно отделяя это состояние от свежего результата. При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный список широких интерактивных карточек счетов с локализованными типом и, если доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, вести на `/accounts/:accountId` по всей области и сохранять доступное клавиатурное состояние. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов.
|
||||
|
||||
Для доступной `portfolioSummary` карточка MUST показывать не более двух Money-итогов в исходных валютах и обозначать наличие остальных валют, если они есть; она MUST NOT показывать или подразумевать FX-конвертацию, межвалютный агрегированный итог либо Cash Balance как отдельный второй итог. При `portfolioSummary: null` карточка MUST показывать локальное безопасное состояние временной недоступности сводки, сохраняя метаданные счёта и возможность открыть его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов за временную ошибку.
|
||||
|
||||
#### Scenario: Пользователь открывает счёт из списка
|
||||
- **WHEN** клиент успешно загрузил непустой список и User выбирает карточку счёта
|
||||
- **THEN** клиент переходит на `/accounts/:accountId` с `id` выбранного счёта
|
||||
|
||||
#### Scenario: Список пуст
|
||||
- **WHEN** клиент получает успешный пустой массив
|
||||
- **THEN** клиент показывает штатное сообщение об отсутствии инвестиционных счетов T-Bank
|
||||
|
||||
#### Scenario: Список берётся из свежего frontend-кэша
|
||||
- **WHEN** User повторно открывает список в течение 60 секунд после успешной загрузки
|
||||
- **THEN** клиент показывает сохранённые счета без нового API-запроса
|
||||
|
||||
#### Scenario: Устаревший список загружается повторно
|
||||
- **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список
|
||||
- **THEN** клиент оставляет этот список видимым во время нового запроса и ясно обозначает фоновое обновление
|
||||
|
||||
#### Scenario: Ошибка фонового обновления не скрывает snapshot
|
||||
- **WHEN** обновление устаревшего списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot
|
||||
- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном обновлении и предоставляет применимое действие повторной загрузки
|
||||
|
||||
#### Scenario: Карточка ограничивает валютные итоги
|
||||
- **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах
|
||||
- **THEN** карточка показывает не более двух исходных валютных итогов и обозначает наличие остальных без общего сконвертированного значения
|
||||
|
||||
#### Scenario: Недоступность сводки локальна карточке
|
||||
- **WHEN** клиент получает список, в котором `portfolioSummary` одного счёта равна `null`
|
||||
- **THEN** он показывает временную недоступность только на этой карточке, не скрывает остальные карточки и сохраняет переход на детали недоступной сводки счёта
|
||||
|
||||
#### Scenario: Пустая сводка не является ошибкой
|
||||
- **WHEN** клиент получает доступную `portfolioSummary` с пустым `totalsByCurrency`
|
||||
- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности
|
||||
|
||||
### Requirement: Клиентские состояния ошибки списка счетов
|
||||
Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. Если последний успешный Client Cached Snapshot отсутствует, эти состояния MUST NOT показывать список; если snapshot существует во время фонового обновления, клиент MUST сохранить его видимым и показать соответствующее безопасное предупреждение и только применимые действия.
|
||||
|
||||
#### Scenario: Подключение требуется
|
||||
- **WHEN** клиент получает `TBANK_NOT_CONNECTED`
|
||||
- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank
|
||||
|
||||
#### Scenario: Известная ошибка не повторяется автоматически
|
||||
- **WHEN** запрос списка завершается известной ошибкой
|
||||
- **THEN** клиент не выполняет дополнительные запросы, пока User не выберет действие повторной загрузки
|
||||
|
||||
#### Scenario: Token требует внимания
|
||||
- **WHEN** клиент получает `INVALID_TBANK_TOKEN`
|
||||
- **THEN** клиент не показывает список и предлагает открыть настройки T-Bank
|
||||
|
||||
#### Scenario: Временная ошибка T-Bank
|
||||
- **WHEN** клиент получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент не показывает список и предлагает повторить загрузку либо открыть настройки T-Bank
|
||||
|
||||
#### Scenario: Неизвестная ошибка загрузки
|
||||
- **WHEN** клиент получает ошибку без известного кода списка счетов
|
||||
- **THEN** клиент не показывает список и предлагает только повторить загрузку
|
||||
|
||||
#### Scenario: Известная ошибка при сохранённом snapshot
|
||||
- **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить загрузку либо открыть настройки T-Bank
|
||||
@@ -0,0 +1,50 @@
|
||||
## 1. Точная backend-сводка и контракт списка
|
||||
|
||||
- [x] 1.1 [backend] Извлечь backend-owned normalizer `Money[]` из текущего Portfolio flow и применить его как для snapshot деталей, так и для компактной сводки списка: нормализовать ISO-валюты, точно сложить ненулевые стоимости позиций и Cash Balance одной валюты, не выполнять FX-конвертацию и не загружать Instrument metadata для списка.
|
||||
- **Covers:** `brokerage-accounts` — `Список активных брокерских счетов`
|
||||
- **Verification:** `npm test -- tbank/portfolio.service.spec.ts --runInBand` в `backend` с cases точных итогов, Cash Balance, нулевых/безвалютных значений и пустого доступного Portfolio.
|
||||
- [x] 1.2 [backend] Расширить `BrokerageAccountDto` nullable `portfolioSummary` с `totalsByCurrency` и изменить list orchestration за существующей `TbankAccessService`: для каждого поддерживаемого upstream-счёта параллельно получить два Portfolio-входа, сохранить upstream-порядок, локализовать только `TBANK_UNAVAILABLE` как `portfolioSummary: null`, а ошибки connection/token и прочие ошибки списка оставить существующей boundary.
|
||||
- **Covers:** `brokerage-accounts` — `Список активных брокерских счетов`
|
||||
- **Verification:** `npm test -- tbank/brokerage-accounts.service.spec.ts --runInBand` в `backend` с cases порядка, успешных нескольких сводок, локальной unavailable-сводки, пустой сводки и неподавленных connection/token ошибок.
|
||||
- [x] 1.3 [backend] Пройти canonical backend gate после DTO, normalizer и list orchestration.
|
||||
- **Covers:** `N/A` — canonical gate проверяет реализацию уже покрытого contract и money behavior, не вводя отдельного observable requirement.
|
||||
- **Verification:** `npm run verify` в `backend` под Node 22.
|
||||
|
||||
## 2. Generated frontend contract и доступный список
|
||||
|
||||
- [x] 2.1 [frontend] После доступности backend OpenAPI синхронизировать committed OpenAPI snapshot и generated schema через штатный workflow; использовать расширенный generated `BrokerageAccount` type без ручного transport DTO.
|
||||
- **Covers:** `N/A` — это обязательная реализация решения D3 для already-specified public response, не новое client behavior.
|
||||
- **Verification:** `npm run openapi:sync` в `frontend`, затем `npm run openapi:check` в `frontend`.
|
||||
- [x] 2.2 [frontend] Расширить Accounts page VM и её focused tests: сохранить существующие key, `staleTime: 60_000`, отсутствие retry/refetch-on-focus, invalidation и top-level loading/error/empty/cached-snapshot selection; подготовить display model сводки карточки с максимум двумя исходными Money, индикатором остальных валют и раздельными состояниями unavailable/empty.
|
||||
- **Covers:** `brokerage-accounts` — `Экран списка брокерских счетов`
|
||||
- **Verification:** `npm run test:run -- src/pages/accounts/ui/AccountsPage.test.tsx` в `frontend` с cases fresh cache, background refresh/error snapshot, limit двух валют, remaining-currency indicator, unavailable и empty summary.
|
||||
- [x] 2.3 [frontend] Переработать presentation списка в адаптивную одноколоночную последовательность широких ссылок-карточек: отобразить локализованные тип, имя, summary, вторичные метаданные даты и видимое действие открытия; сохранить native link navigation/focus, безопасный local summary state и форматирование каждого переданного Money существующим formatter без агрегации или FX-смысла.
|
||||
- **Covers:** `brokerage-accounts` — `Экран списка брокерских счетов`
|
||||
- **Verification:** `npm run test:run -- src/entities/brokerage-account/ui/BrokerageAccountList.test.tsx` в `frontend` с accessible link/action, доступными summary states и responsive single-column class behavior.
|
||||
- [x] 2.4 [frontend] Сохранить известные list error states и recovery actions в page VM/presentation: connection и invalid-token ведут только к настройкам, temporary unavailable показывает retry и settings, unknown error — только retry; при Client Cached Snapshot warning не скрывает карточки и не запускает retry автоматически.
|
||||
- **Covers:** `brokerage-accounts` — `Клиентские состояния ошибки списка счетов`
|
||||
- **Verification:** `npm run test:run -- src/pages/accounts/ui/AccountsPage.test.tsx` в `frontend` с cases всех известных/unknown errors, recovery actions и cached-snapshot warning.
|
||||
- [x] 2.5 [frontend] Пройти canonical frontend gate после generated contract, VM, presentation и tests.
|
||||
- **Covers:** `N/A` — canonical gate подтверждает delivered frontend behavior и boundary rules, но не задаёт новый requirement.
|
||||
- **Verification:** `npm run verify` в `frontend` под Node 22.
|
||||
|
||||
## 3. As-built documentation и canonical SDD checks
|
||||
|
||||
- [x] 3.1 [docs] Обновить delivered documentation брокерских счетов и system overview после verified implementation: описать additive summary contract, исходные валюты без FX-агрегации, локальную unavailable-сводку, неизменную current-connection boundary и Client Cached Snapshot; не копировать OpenSpec planning artifacts.
|
||||
- **Covers:** `N/A` — docs фиксирует только поставленное as-built состояние из Design Delivery Map и не создаёт самостоятельного observable behavior.
|
||||
- **Verification:** Review `docs/features/brokerage-accounts.md` и `docs/architecture/system-overview.md` against delivered backend OpenAPI and frontend behavior.
|
||||
- [x] 3.2 [docs] Пройти canonical docs gate для as-built documentation.
|
||||
- **Covers:** `N/A` — Markdown gate подтверждает documentation delivery и не реализует отдельный Delta Requirement.
|
||||
- **Verification:** `npm run verify` в `docs` под Node 22.
|
||||
- [x] 3.3 [specs] Проверить canonical change artifacts и strict Delta Specs после создания Tasks, не создавая competing planning source.
|
||||
- **Covers:** `N/A` — specs area владеет SDD artifact integrity, а не отдельным runtime behavior.
|
||||
- **Verification:** `bash specs/scripts/verify-openspec-planning.sh enhance-brokerage-account-list`, `openspec validate enhance-brokerage-account-list --type change --strict --store wealth-ledger-specs` и `openspec validate --specs --strict --store wealth-ledger-specs` из `specs`.
|
||||
|
||||
## 4. Cross-repository verification before archive
|
||||
|
||||
- [x] 4.1 [workspace] Выполнить canonical workspace dispatcher после всех repository-owned tasks, включая required runtime OpenAPI contract gate для backend response и committed frontend snapshot.
|
||||
- **Covers:** `N/A` — workspace только оркестрирует approved Delivery Map и repository gates; он не владеет implementation behavior.
|
||||
- **Verification:** `bash specs/scripts/verify-workspace-change.sh enhance-brokerage-account-list` из workspace root под Node 22.
|
||||
- [x] 4.2 [workspace] Провести semantic verification и independent read-only review implementation против Proposal, всех Delta Requirements/Scenarios, Decisions D1–D4, rollback contract и completed Tasks; устранить Critical и Important findings до archive eligibility.
|
||||
- **Covers:** `N/A` — terminal review доказывает соответствие уже specified behavior, не вводя нового requirement.
|
||||
- **Verification:** Сохранить review scope, findings и resolution только после фактической проверки; подтвердить отсутствие Critical и Important findings.
|
||||
@@ -9,7 +9,9 @@
|
||||
### Requirement: Список активных брокерских счетов
|
||||
Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив открытых поддерживаемых инвестиционных счетов T-Bank, доступных через текущий T-Bank connection User, в порядке, полученном от T-Bank. Каждый элемент MUST содержать стабильный `id`, непустое `name`, нормализованный `type` (`BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` или `MONEY_MARKET_FUND`) и nullable `openedOn` в формате `YYYY-MM-DD`. Система MUST включать только счета T-Bank типов «брокерский счёт», «ИИС», «Инвесткопилка» и «фонд денежного рынка»; банковские и неизвестные типы MUST NOT возвращаться. Система MUST удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа.
|
||||
|
||||
Список Brokerage Account является результатом наблюдения текущего брокерского подключения и MUST NOT создавать ожидание, что WealthLedger хранит долговременное право владения счётом независимо от T-Bank connection.
|
||||
Каждый элемент MUST также содержать nullable `portfolioSummary`. Доступная сводка MUST содержать `totalsByCurrency`: точные Money-итоги Portfolio по исходным ISO-валютам, включающие стоимость позиций и Cash Balance в той же валюте. Она MUST NOT содержать FX-конвертированный или межвалютный итог и MUST NOT выдавать Cash Balance за самостоятельный второй итог. Если сводка Portfolio конкретного доступного счёта временно недоступна, `portfolioSummary` MUST быть `null`, не исключая этот счёт из списка и не делая список неуспешным. Пустой доступный Portfolio MUST передаваться доступной сводкой с пустым `totalsByCurrency`, а не `null`.
|
||||
|
||||
Список Brokerage Account и его сводки являются результатом наблюдения текущего брокерского подключения и MUST NOT создавать ожидание, что WealthLedger хранит долговременное право владения счётом или Portfolio независимо от T-Bank connection.
|
||||
|
||||
#### Scenario: Поддерживаемые счета возвращаются в upstream-порядке
|
||||
- **WHEN** User с действующим T-Bank connection запрашивает список, а T-Bank возвращает открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт
|
||||
@@ -27,6 +29,18 @@
|
||||
- **WHEN** T-Bank не возвращает дату открытия поддерживаемого открытого счёта
|
||||
- **THEN** система отвечает этим счётом с `openedOn: null`
|
||||
|
||||
#### Scenario: Мультивалютная сводка счёта
|
||||
- **WHEN** доступный Portfolio счёта содержит итоги в RUB и USD, включая Cash Balance в USD
|
||||
- **THEN** ответ содержит одну доступную `portfolioSummary` с отдельными Money-итогами RUB и USD без FX-конвертации, общего итога или отдельного итога Cash Balance
|
||||
|
||||
#### Scenario: Сводка одного счёта временно недоступна
|
||||
- **WHEN** список доступных счетов получен, но Portfolio одного из них временно недоступен
|
||||
- **THEN** система отвечает `200` всеми счетами в исходном порядке, передаёт для этого счёта `portfolioSummary: null` и не заменяет успешные сводки остальных счетов ошибкой списка
|
||||
|
||||
#### Scenario: Доступный Portfolio пуст
|
||||
- **WHEN** доступный счёт не имеет ненулевых позиций и доступных денежных остатков
|
||||
- **THEN** система передаёт доступную `portfolioSummary` с пустым `totalsByCurrency`
|
||||
|
||||
### Requirement: Состояния недоступности списка счетов
|
||||
Система MUST NOT возвращать частичный или сохранённый ранее список счетов. При отсутствии T-Bank connection система MUST отвечать `409` Problem Details с code `TBANK_NOT_CONNECTED`. Если T-Bank отклоняет сохранённый token как неаутентифицированный или без прав, система MUST отвечать `400` Problem Details с code `INVALID_TBANK_TOKEN` и сохранять connection. При иной неуспешной попытке получить список из T-Bank система MUST отвечать `503` Problem Details с code `TBANK_UNAVAILABLE`.
|
||||
|
||||
@@ -43,7 +57,9 @@
|
||||
- **THEN** система отвечает `503` Problem Details с code `TBANK_UNAVAILABLE` без счетов
|
||||
|
||||
### Requirement: Экран списка брокерских счетов
|
||||
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд; после истечения этого срока при новом запросе клиент MUST скрывать ранее показанный список и показывать loading-состояние. При успешном непустом ответе клиент MUST отобразить светлые интерактивные карточки счетов с названием, локализованным типом и, если доступна, локализованной датой открытия без времени; карточки MUST вести на `/accounts/:accountId`. При успешном пустом ответе клиент MUST показать штатное состояние отсутствия инвестиционных счетов. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов.
|
||||
Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` без ручного обновления и без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд. После истечения этого срока клиент MUST запрашивать актуальный список; при уже имеющемся успешном списке он MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового обновления и после его ошибки, ясно отделяя это состояние от свежего результата. При успешном непустом ответе клиент MUST отобразить адаптивный одноколоночный список широких интерактивных карточек счетов с локализованными типом и, если доступна, датой открытия без времени; каждая карточка MUST ясно показывать тип, имя, финансовую сводку, вторичные метаданные и заметное действие открытия счёта, вести на `/accounts/:accountId` по всей области и сохранять доступное клавиатурное состояние. После успешной замены или удаления T-Bank connection клиент MUST инвалидировать список счетов.
|
||||
|
||||
Для доступной `portfolioSummary` карточка MUST показывать не более двух Money-итогов в исходных валютах и обозначать наличие остальных валют, если они есть; она MUST NOT показывать или подразумевать FX-конвертацию, межвалютный агрегированный итог либо Cash Balance как отдельный второй итог. При `portfolioSummary: null` карточка MUST показывать локальное безопасное состояние временной недоступности сводки, сохраняя метаданные счёта и возможность открыть его. При доступной пустой сводке карточка MUST не выдавать отсутствие итогов за временную ошибку.
|
||||
|
||||
#### Scenario: Пользователь открывает счёт из списка
|
||||
- **WHEN** клиент успешно загрузил непустой список и User выбирает карточку счёта
|
||||
@@ -58,11 +74,27 @@
|
||||
- **THEN** клиент показывает сохранённые счета без нового API-запроса
|
||||
|
||||
#### Scenario: Устаревший список загружается повторно
|
||||
- **WHEN** User открывает список после истечения 60 секунд frontend-кэша
|
||||
- **THEN** клиент не показывает ранее загруженные счета до получения нового результата
|
||||
- **WHEN** User открывает список после истечения 60 секунд frontend-кэша и уже существует последний успешный список
|
||||
- **THEN** клиент оставляет этот список видимым во время нового запроса и ясно обозначает фоновое обновление
|
||||
|
||||
#### Scenario: Ошибка фонового обновления не скрывает snapshot
|
||||
- **WHEN** обновление устаревшего списка завершается ошибкой при наличии последнего успешного Client Cached Snapshot
|
||||
- **THEN** клиент сохраняет snapshot видимым, безопасно сообщает о неуспешном обновлении и предоставляет применимое действие повторной загрузки
|
||||
|
||||
#### Scenario: Карточка ограничивает валютные итоги
|
||||
- **WHEN** доступная сводка счёта содержит итоги в трёх или более валютах
|
||||
- **THEN** карточка показывает не более двух исходных валютных итогов и обозначает наличие остальных без общего сконвертированного значения
|
||||
|
||||
#### Scenario: Недоступность сводки локальна карточке
|
||||
- **WHEN** клиент получает список, в котором `portfolioSummary` одного счёта равна `null`
|
||||
- **THEN** он показывает временную недоступность только на этой карточке, не скрывает остальные карточки и сохраняет переход на детали недоступной сводки счёта
|
||||
|
||||
#### Scenario: Пустая сводка не является ошибкой
|
||||
- **WHEN** клиент получает доступную `portfolioSummary` с пустым `totalsByCurrency`
|
||||
- **THEN** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности
|
||||
|
||||
### Requirement: Клиентские состояния ошибки списка счетов
|
||||
Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки.
|
||||
Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. Если последний успешный Client Cached Snapshot отсутствует, эти состояния MUST NOT показывать список; если snapshot существует во время фонового обновления, клиент MUST сохранить его видимым и показать соответствующее безопасное предупреждение и только применимые действия.
|
||||
|
||||
#### Scenario: Подключение требуется
|
||||
- **WHEN** клиент получает `TBANK_NOT_CONNECTED`
|
||||
@@ -83,3 +115,7 @@
|
||||
#### Scenario: Неизвестная ошибка загрузки
|
||||
- **WHEN** клиент получает ошибку без известного кода списка счетов
|
||||
- **THEN** клиент не показывает список и предлагает только повторить загрузку
|
||||
|
||||
#### Scenario: Известная ошибка при сохранённом snapshot
|
||||
- **WHEN** фоновое обновление списка с последним успешным Client Cached Snapshot получает `TBANK_UNAVAILABLE`
|
||||
- **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить загрузку либо открыть настройки T-Bank
|
||||
|
||||
Reference in New Issue
Block a user