From e9075425df4f5871b7a818aeba809aa440284214 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 13 Aug 2026 20:59:38 +0300 Subject: [PATCH] feat: add brocker account list --- .../.openspec.yaml | 0 .../design.md | 65 +++++++++++++++ .../proposal.md | 4 +- .../specs/brokerage-accounts/spec.md | 81 ++++++++++++++++++ .../tasks.md | 26 ++++++ openspec/specs/brokerage-accounts/spec.md | 83 +++++++++++++++++++ 6 files changed, 257 insertions(+), 2 deletions(-) rename openspec/changes/{add-brokerage-accounts => archive/2026-08-13-add-brokerage-accounts}/.openspec.yaml (100%) create mode 100644 openspec/changes/archive/2026-08-13-add-brokerage-accounts/design.md rename openspec/changes/{add-brokerage-accounts => archive/2026-08-13-add-brokerage-accounts}/proposal.md (78%) create mode 100644 openspec/changes/archive/2026-08-13-add-brokerage-accounts/specs/brokerage-accounts/spec.md create mode 100644 openspec/changes/archive/2026-08-13-add-brokerage-accounts/tasks.md create mode 100644 openspec/specs/brokerage-accounts/spec.md diff --git a/openspec/changes/add-brokerage-accounts/.openspec.yaml b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/.openspec.yaml similarity index 100% rename from openspec/changes/add-brokerage-accounts/.openspec.yaml rename to openspec/changes/archive/2026-08-13-add-brokerage-accounts/.openspec.yaml diff --git a/openspec/changes/archive/2026-08-13-add-brokerage-accounts/design.md b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/design.md new file mode 100644 index 0000000..4ce6a79 --- /dev/null +++ b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/design.md @@ -0,0 +1,65 @@ +## Context + +См. мотивацию в `proposal.md` и поведение в `specs/brokerage-accounts/spec.md`. Backend уже хранит один зашифрованный T-Bank token на User, проверяет его через `UsersService/GetAccounts` и применяет общую policy TLS, rate limit, deadline и retry. Этот RPC пока отбрасывает response body, а `/accounts` во frontend является placeholder-страницей. OpenAPI backend остаётся источником transport contract для frontend. + +## Goals / Non-Goals + +**Goals:** + +- Использовать существующую T-Bank boundary и resilience policy для user-scoped read-path списка счетов. +- Отделить публичную модель инвестиционного счёта от protobuf enum и банковских типов T-Bank. +- Дать `/accounts` понятные loading, empty, error и loaded состояния в FSD-структуре, сохранив стиль codebase и визуальный язык `references/fin-tracker` без переноса его кода или неподдерживаемых данных. +- Зафиксировать feature-документацию с пользовательским потоком, API и MVP-ограничениями после реализации. + +**Non-Goals:** + +- Суммы, доходность, агрегация, графики, структура портфеля и любые вычисления по счетам. +- Кэш backend, timestamp последнего обновления, ручная синхронизация или частичные результаты. +- Закрытые, банковские и неизвестные типы счетов, операции и изменение T-Bank connection из списка. + +## Decisions + +### Read-path списка и public contract + +Новый защищённый `GET /api/v1/brokerage-accounts` сначала читает `TbankConnection` User. При её отсутствии он немедленно возвращает `409 TBANK_NOT_CONNECTED`; gRPC не вызывается. При connection сервис расшифровывает token только для вызова T-Bank и получает открытые счета через существующий общий client, поэтому к списку применяются те же TLS, limiter, deadline и retry, что и к проверке token. + +Успех возвращает прямой JSON-массив, а не envelope: сейчас ответ не содержит метаданных, и обёртка не несёт семантики. Ошибка не возвращает данные. При gRPC `UNAUTHENTICATED` и `PERMISSION_DENIED` connection не удаляется, а клиент получает `400 INVALID_TBANK_TOKEN`; другие ошибки остаются `503 TBANK_UNAVAILABLE`. + +Альтернативы: использовать `/accounts` для API смешало бы инвестиционные и банковские счета; статус `200` без connection сделал бы обязательную интеграцию неявным UI-флагом; сохранение или возврат старого списка потребовало бы cache invalidation и вводило бы пользователя в заблуждение об актуальности. + +### Нормализация Brokerage Account + +Backend добавляет нормализованный DTO `BrokerageAccount` с `id`, `name`, `type` и `openedOn`. Трансляция включает только upstream-типы `ACCOUNT_TYPE_TINKOFF`, `ACCOUNT_TYPE_TINKOFF_IIS`, `ACCOUNT_TYPE_INVEST_BOX` и `ACCOUNT_TYPE_INVEST_FUND`, преобразуя их соответственно в `BROKERAGE`, `INDIVIDUAL_INVESTMENT`, `INVEST_BOX` и `MONEY_MARKET_FUND`. Остальные и неизвестные типы отфильтровываются, порядок upstream сохраняется. + +`name` нормализуется через trim и при пустом результате заменяется fallback названием типа. Timestamp `openedDate` переводится в nullable `YYYY-MM-DD`; время намеренно не публикуется, потому что экран показывает календарную дату открытия, а не момент события. + +Альтернативы: передача protobuf enum привязала бы frontend к T-Bank contract; показ неизвестных типов как инвестиционных расширил бы доменную границу без решения пользователя; отображение технического id вместо fallback-названия ухудшило бы интерфейс без пользы для MVP. + +### UI и React Query + +Новый entity slice `brokerage-account` инкапсулирует typed API query, локальные transport aliases из generated `paths` и display helpers. `AccountsPage` использует entity через public barrel и выбирает одно из состояний: loading, known-error, generic error, empty либо loaded. Query имеет `staleTime: 60_000` и отключённый automatic retry; отключённый focus refetch сохраняется глобальной настройкой. Свежий React Query cache исключает повторный GET в течение минуты; после его устаревания `isFetching` заменяет прежний список loading-state. Retry для временной и неизвестной ошибки вызывает `refetch`; кнопка настроек ведёт на `/settings`. + +Успешная замена или удаление connection инвалидирует query списка. Карточки реализуются Mantine-компонентами и локальным CSS module: белая поверхность, тонкая slate-граница, умеренная мягкая тень, синий accent/kicker и заметный hover/focus. Сетка адаптивна: две колонки при доступной ширине и одна на узком экране. Карточка является доступной ссылкой на `/accounts/:accountId`, показывает имя, локализованный тип и при наличии дату открытия. Не добавляются hero, sparkline и финансовые метрики, поскольку текущий контракт их не предоставляет. + +Альтернативы: размещение query прямо в page смешало бы transport и presentation; копирование разметки или mock-метрик FinTracker нарушило бы границы references и исказило бы реальные данные; фиксированная двухколоночная mobile-сетка ухудшила бы читаемость. + +### Документация и проверка + +После реализации `docs/features/brokerage-accounts.md` описывает пользовательский поток, endpoint, коды ошибок, нормализованные типы и отсутствие кэша/времени актуальности; последовательность запроса фиксируется Mermaid-диаграммой. Постоянный ADR не требуется: backend boundary, OpenAPI и React Query policy уже закреплены ADR 003, 005 и 007, а новая фильтрация типов является ограниченной feature-деталью. + +## Risks / Trade-offs + +- [Токен может быть отозван после подключения] → Список возвращает `INVALID_TBANK_TOKEN` и ведёт в настройки, но не удаляет connection автоматически. +- [Данные меняются у T-Bank между загрузками] → Frontend использует результат до 60 секунд, но backend не хранит кэш и не передаёт метку свежести. +- [T-Bank добавит новый инвестиционный тип] → Неизвестный тип не показывается до явного расширения доменной модели. +- [Визуальный референс содержит финансовые показатели, которых нет в API] → Сохраняются лишь стилистические признаки, не генерируются placeholder-значения. + +## Migration Plan + +1. Расширить T-Bank client получением response body и добавить user-scoped сервис, controller и OpenAPI DTO списка. +2. Добавить backend unit/e2e проверки нормализации, отсутствующего connection, error mapping и auth. +3. Перегенерировать frontend API client из работающего backend OpenAPI. +4. Добавить entity, query, account cards и все состояния `/accounts`; инвалидировать query из T-Bank connection feature. +5. Проверить backend и frontend quality gates, визуально проверить desktop/mobile и создать `docs/features/brokerage-accounts.md`. + +Rollback до release удаляет endpoint и frontend-срез; схема базы данных не изменяется. Удалять `TbankConnection` или шифротекст при rollback не требуется. diff --git a/openspec/changes/add-brokerage-accounts/proposal.md b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/proposal.md similarity index 78% rename from openspec/changes/add-brokerage-accounts/proposal.md rename to openspec/changes/archive/2026-08-13-add-brokerage-accounts/proposal.md index a894ad1..6df551b 100644 --- a/openspec/changes/add-brokerage-accounts/proposal.md +++ b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/proposal.md @@ -5,8 +5,8 @@ ## What Changes - Добавить защищенный API списка активных T-Bank инвестиционных счетов пользователя через нормализованные backend DTO. -- Добавить страницу `/accounts` со списком счетов всех поддерживаемых T-Bank типов, пустым состоянием подключения и безопасным состоянием upstream-ошибок. -- Показывать фактическое время последнего успешного получения данных T-Bank и не скрывать полученные счета при частичной ошибке. +- Добавить страницу `/accounts` со списком поддерживаемых инвестиционных типов T-Bank, пустым состоянием подключения и безопасными состояниями upstream-ошибок. +- Не показывать частичные либо сохранённые ранее счета: результат одного upstream-запроса отображается целиком или заменяется error-state. - Получать данные при открытии экрана через React Query без focus refetch и без ручной кнопки обновления. - Документировать пользовательский поток списка счетов и ограничения актуальности данных. diff --git a/openspec/changes/archive/2026-08-13-add-brokerage-accounts/specs/brokerage-accounts/spec.md b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/specs/brokerage-accounts/spec.md new file mode 100644 index 0000000..244755c --- /dev/null +++ b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/specs/brokerage-accounts/spec.md @@ -0,0 +1,81 @@ +## Purpose + +Позволяет авторизованному пользователю просматривать поддерживаемые активные инвестиционные счета подключенного T-Bank и переходить к их деталям. + +## ADDED Requirements + +### Requirement: Список активных брокерских счетов +Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив его открытых поддерживаемых инвестиционных счетов T-Bank в порядке, полученном от 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 удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа. + +#### 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` + +### 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`. + +#### Scenario: Подключение отсутствует +- **WHEN** User без сохранённого T-Bank connection запрашивает список +- **THEN** система не обращается к T-Bank и отвечает `409` Problem Details с code `TBANK_NOT_CONNECTED` + +#### Scenario: Сохранённый token больше не действителен +- **WHEN** T-Bank отклоняет сохранённый token при запросе списка как неаутентифицированный или без прав +- **THEN** система отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN` и не удаляет connection + +#### Scenario: T-Bank временно недоступен +- **WHEN** запрос списка не завершается из-за временной либо иной upstream-ошибки +- **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 инвалидировать список счетов. + +#### 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** клиент не показывает ранее загруженные счета до получения нового результата + +### Requirement: Клиентские состояния ошибки списка счетов +Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. + +#### 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** клиент не показывает список и предлагает только повторить загрузку diff --git a/openspec/changes/archive/2026-08-13-add-brokerage-accounts/tasks.md b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/tasks.md new file mode 100644 index 0000000..d6f2ab1 --- /dev/null +++ b/openspec/changes/archive/2026-08-13-add-brokerage-accounts/tasks.md @@ -0,0 +1,26 @@ +## 1. Backend read-path счетов + +- [x] 1.1 Расширить T-Bank client для возврата данных открытых счетов, сохранив существующую policy TLS, rate limit, deadline, retry и безопасного mapping ошибок. +- [x] 1.2 Добавить аутентифицированные controller, service и OpenAPI DTO для `GET /api/v1/brokerage-accounts`; при отсутствии connection возвращать `TBANK_NOT_CONNECTED` без upstream-вызова. +- [x] 1.3 Нормализовать поддерживаемые типы счетов T-Bank, trimmed/fallback имена и nullable дату открытия в формате `YYYY-MM-DD` в публичный ответ brokerage account, сохраняя upstream-порядок. +- [x] 1.4 Добавить backend-тесты для аутентификации, отсутствующего connection, нормализованных поддерживаемых счетов, отфильтрованных типов, пустого результата, отсутствующей даты, сохранения невалидного token и upstream-недоступности. + +## 2. Сгенерированный контракт и frontend data layer + +- [x] 2.1 Запустить backend API и перегенерировать frontend OpenAPI client; проверить наличие `GET /api/v1/brokerage-accounts` в сгенерированном контракте. +- [x] 2.2 Добавить FSD entity slice `brokerage-account` с типизированным React Query запросом списка, локальной обработкой кодов ошибок, display helpers и публичными barrel exports. +- [x] 2.3 Инвалидировать query brokerage accounts после успешной замены либо удаления T-Bank connection. + +## 3. Страница счетов + +- [x] 3.1 Заменить placeholder `/accounts` на состояния loading, empty, known-error, generic-error и loaded, скрывая устаревшие карточки при каждом запросе. +- [x] 3.2 Добавить локализованные доступные карточки счетов со ссылкой на `/accounts/:accountId`, названием, нормализованным типом и опциональной локализованной датой открытия. +- [x] 3.3 Оформить loaded-список адаптивными карточками в одну или две колонки: светлые поверхности, синие акценты, мягкие границы и тени в стиле `references/fin-tracker`, через локальные CSS modules и без вымышленных финансовых данных. +- [x] 3.4 Добавить frontend-тесты для всех состояний страницы, действий известных ошибок, retry, навигации и invalidation после mutation подключения. + +## 4. Документация и проверка + +- [x] 4.1 Создать `docs/features/brokerage-accounts.md` с пользовательским потоком, Mermaid sequence diagram, API contract, поведением ошибок, поддерживаемыми типами и ограничением отсутствия кэша/данных об актуальности. +- [x] 4.2 Запустить backend lint, tests и build; запустить frontend lint, typecheck, tests, build и no-TODO check. +- [x] 4.3 Визуально проверить страницу счетов на desktop и mobile ширинах, включая состояния loading, empty, error и loaded. +- [x] 4.4 Перед архивированием запустить `openspec validate add-brokerage-accounts --type change --strict --store wealth-ledger-specs` и `opsx-verify`. diff --git a/openspec/specs/brokerage-accounts/spec.md b/openspec/specs/brokerage-accounts/spec.md new file mode 100644 index 0000000..ff0c8da --- /dev/null +++ b/openspec/specs/brokerage-accounts/spec.md @@ -0,0 +1,83 @@ +# brokerage-accounts Specification + +## Purpose + +Позволяет авторизованному пользователю просматривать поддерживаемые активные инвестиционные счета подключенного T-Bank и переходить к их деталям. + +## Requirements + +### Requirement: Список активных брокерских счетов +Система SHALL предоставлять авторизованному User `GET /api/v1/brokerage-accounts`, возвращающий массив его открытых поддерживаемых инвестиционных счетов T-Bank в порядке, полученном от 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 удалить внешние пробелы из имени и при пустом результате использовать непустое название соответствующего типа. + +#### 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` + +### 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`. + +#### Scenario: Подключение отсутствует +- **WHEN** User без сохранённого T-Bank connection запрашивает список +- **THEN** система не обращается к T-Bank и отвечает `409` Problem Details с code `TBANK_NOT_CONNECTED` + +#### Scenario: Сохранённый token больше не действителен +- **WHEN** T-Bank отклоняет сохранённый token при запросе списка как неаутентифицированный или без прав +- **THEN** система отвечает `400` Problem Details с code `INVALID_TBANK_TOKEN` и не удаляет connection + +#### Scenario: T-Bank временно недоступен +- **WHEN** запрос списка не завершается из-за временной либо иной upstream-ошибки +- **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 инвалидировать список счетов. + +#### 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** клиент не показывает ранее загруженные счета до получения нового результата + +### Requirement: Клиентские состояния ошибки списка счетов +Клиент MUST NOT автоматически повторять неуспешный запрос списка. При `TBANK_NOT_CONNECTED` клиент SHALL показать объяснение отсутствия подключения и действие «Открыть настройки T-Bank». При `INVALID_TBANK_TOKEN` клиент SHALL показать объяснение недействительного token и то же действие. При `TBANK_UNAVAILABLE` клиент SHALL показать безопасное объяснение временной недоступности, действие повторной загрузки и действие открытия настроек T-Bank. При иной ошибке клиент MUST показать общий безопасный error-state только с действием повторной загрузки. + +#### 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** клиент не показывает список и предлагает только повторить загрузку