## MODIFIED Requirements ### Requirement: Список активных брокерских счетов Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий envelope `{ data, meta }`, где `data` содержит сохранённый массив открытых поддерживаемых инвестиционных счетов 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. Обычный запрос MUST читать сохранённый список и доступные сводки без ожидания T-Bank. Срок свежести списка MUST составлять пять минут; сводки MUST следовать минутной свежести Portfolio. Отсутствующий список MUST возвращаться как `200` с `data: null`, `meta.availability: missing` и состоянием запрошенной загрузки. Только успешно полученный полный пустой список MUST возвращаться как `data: []` с `meta.availability: complete`. Время, свежесть и состояние загрузки MUST передаваться по контракту `tbank-data-sync`. Отсутствие либо ошибка загрузки сводки MUST отличаться от отсутствия самого счёта; сохранённая успешная сводка MUST оставаться доступной после временной ошибки её обновления. #### Scenario: Поддерживаемые счета возвращаются в upstream-порядке - **WHEN** User с действующим T-Bank connection запрашивает список, а последняя успешная синхронизация сохранила открытые брокерский счёт, ИИС и неподдержанный дебетовый счёт - **THEN** система отвечает `200` с массивом брокерского счёта и ИИС в `data` в исходном порядке T-Bank без дебетового счёта #### Scenario: Пустой список поддерживаемых счетов - **WHEN** User с действующим T-Bank connection имеет сохранённый успешно подтверждённый пустой список открытых поддерживаемых инвестиционных счетов - **THEN** система отвечает `200` с `data: []` и `meta.availability: complete` #### 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` #### Scenario: Список ещё не загружен - **WHEN** User впервые запрашивает список текущего подключения и успешного сохранённого результата ещё нет - **THEN** система возвращает `200` с `data: null`, состоянием загрузки в `meta`, запрашивает фоновую синхронизацию и не выдаёт отсутствие данных за отсутствие счетов #### Scenario: Ошибка обновления сохранённой сводки - **WHEN** обновление Portfolio одного счёта временно неуспешно и его успешная сводка уже сохранена - **THEN** список сохраняет эту сводку с прежним временем и видимым состоянием ошибки обновления вместо `null` ### Requirement: Состояния недоступности списка счетов Система MUST возвращать последний полностью сохранённый список текущего подключения без синхронной проверки T-Bank. Неполный ответ обновления MUST NOT заменять полный список. При отсутствии T-Bank connection система MUST отвечать `409` Problem Details с code `TBANK_NOT_CONNECTED`. Если для текущего подключения уже подтверждено отклонение token как неаутентифицированного или без прав, система MUST отвечать `400` Problem Details с code `INVALID_TBANK_TOKEN` и сохранять connection. При временной либо иной upstream-ошибке фонового обновления система MUST сохранять прежний список и время его успешной загрузки, при наблюдении завершённой попытки возвращая `200` с `meta.sync.state: failed` и безопасным `meta.sync.errorCode`. Обычный запрос экрана либо ручное обновление MAY запросить новую попытку согласно `tbank-data-sync`; наблюдение статуса MUST NOT запускать её. При отсутствии сохранённого списка `data` MUST оставаться `null`. Ошибка обновления MUST NOT менять время успешной загрузки и сама по себе MUST NOT делать ещё свежие данные устаревшими; свежесть MUST продолжать определяться сроком соответствующего источника. Подтверждённая недействительность текущего token MUST запрещать выдачу личных данных, включая сохранённый список; локальная сессия и текущая граница подключения MUST проверяться до чтения. #### Scenario: Подключение отсутствует - **WHEN** User без сохранённого T-Bank connection запрашивает список - **THEN** система не обращается к T-Bank и отвечает `409` Problem Details с code `TBANK_NOT_CONNECTED` #### Scenario: Сохранённый token больше не действителен - **WHEN** фоновая проверка подтвердила, что T-Bank отклоняет token текущего подключения как неаутентифицированный или без прав, и User снова запрашивает список - **THEN** система отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN` и не удаляет connection #### Scenario: T-Bank временно недоступен - **WHEN** фоновое обновление списка завершается временной либо иной upstream-ошибкой - **THEN** последующее наблюдение результата без нового запуска отвечает `200` с последним сохранённым списком либо `data: null`, `meta.sync.state: failed` и `meta.sync.errorCode: TBANK_UNAVAILABLE`, не подтверждая новую успешную загрузку ### Requirement: Экран списка брокерских счетов Клиент SHALL загружать список при открытии защищённого маршрута `/accounts` и предоставлять действие «Обновить», без автоматического refetch при возврате фокуса. Клиент MUST использовать успешный frontend-кэш списка в течение 60 секунд. После истечения этого срока клиент MUST запрашивать локальный результат и состояние синхронизации; при уже имеющемся успешном списке он MUST сохранять последний успешный Client Cached Snapshot видимым во время фонового обновления и после его ошибки, ясно отделяя это состояние от свежего результата. Клиент MUST читать список из `data`, показывать свежесть и состояние синхронизации из `meta` и MUST NOT считать успешный HTTP-ответ с `data: null` пустым списком. Ручное обновление MUST обходить проверку давности списка и показанных сводок, не обновляя всю историю счетов. При успешном непустом ответе клиент 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** клиент получает `data: []` с `meta.availability: complete` - **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** он показывает нейтральное состояние отсутствия финансовых итогов, а не состояние временной недоступности #### Scenario: Начальная загрузка не становится пустым списком - **WHEN** клиент получает `data: null` со статусом запрошенной синхронизации - **THEN** он показывает состояние первичной загрузки или её ошибки, а не сообщение об отсутствии инвестиционных счетов #### Scenario: Ручное обновление списка - **WHEN** User нажимает «Обновить» на экране списка счетов - **THEN** клиент запрашивает обновление списка и показанных сводок независимо от давности, сохраняя доступные данные и отображая прогресс ### Requirement: Клиентские состояния ошибки списка счетов Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. При `TBANK_NOT_CONNECTED` или `INVALID_TBANK_TOKEN` клиент MUST скрыть личные данные даже при наличии Client Cached Snapshot. Для временной и неизвестной ошибки без snapshot клиент MUST показать ошибку без списка; при наличии snapshot он MUST сохранить его видимым с безопасным предупреждением. `meta.sync.state: failed` MUST отображаться как ошибка фоновой синхронизации, отдельно от ошибки HTTP-запроса; успешный HTTP-ответ сам по себе MUST NOT скрывать такую ошибку. #### 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` через HTTP-ошибку либо `meta.sync.errorCode` - **THEN** клиент сохраняет snapshot видимым, сообщает о временной недоступности и предлагает повторить загрузку либо открыть настройки T-Bank