1
0
forked from BRT/arc
arc/docs/bank-setup-wizard.md

120 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.

# Мастер установки банка (`BankSetupWizard`)
Файлы: `views/bank/BankSetupWizard.{h,cpp}`
## Назначение
Диалоговое окно, проводящее пользователя через настройку нового банковского профиля на конкретном устройстве: ввод пин-кода → автоматический вход в приложение → парсинг профиля → парсинг карт → выбор активной карты. Все данные одновременно сохраняются в локальную БД и регистрируются на бэкенде (`localhost:9999/api/v1/`).
Поддерживаемые банки: **Ozon**, **Black** (Birbank), **Dushanbe City Bank** (`appCode == "ozon" | "black" | "dushanbe_city_bank"`). Остальные банки в мастере не поддерживаются и сразу падают с ошибкой (`BankSetupWizard.cpp:443, 494, 549`).
## Где открывается
Два входа, оба создают `BankSetupWizard` как обычный `QDialog` (немодальный):
- `views/device/DeviceSettingsWindow.cpp:176` — из карточки настроек устройства.
- `views/device/DeviceWidget.cpp:278` — из плитки устройства в главном окне.
В обоих местах используется свежая запись из БД, если профиль уже есть; иначе — переданный `BankProfileInfo` шаблон.
## Структура UI: `QStackedWidget` из 5 страниц
Индексы страниц: `0` Intro, `1` PIN, `2` Progress, `3` Success, `4` Error (`BankSetupWizard.cpp:32-38`).
| Шаг | Что показывает | Кнопки |
|-----|----------------|--------|
| 1 Intro (`createStep1`) | Иконка + название банка + список из 3 шагов | **Далее** → шаг 2 |
| 2 PIN (`createStep2`) | Поле ввода пин-кода (max 10 символов) | **Назад**, **Далее** → сохраняет `m_app.pinCode`, переходит на шаг 3 и запускает `startVerification()` |
| 3 Progress (`createStep3`) | `QProgressBar` + текстовая метка статуса | **Назад**`stopWorkerThread()`, возврат на шаг 2 |
| 4a Success (`createStep4Success`) | ФИО / Телефон / Email + таблица карт с radio-выбором | **Завершить**`applyActiveCardSelection()`, `accept()` |
| 4b Error (`createStep4Error`) | Человекочитаемый текст ошибки | **Назад** → шаг 2, **Начать заново** → очищает БД-запись (если профиль ещё не отправлен) и возвращает на шаг 1 |
## Жизненный цикл и пауза других профилей
В **конструкторе** (`BankSetupWizard.cpp:41`) сразу вызывается `pauseActiveProfiles()`:
- Берёт все профили устройства через `BankProfileDAO::getApplicationsByDeviceId`.
- Все `status == "active"` переводятся в `"off"` локально и **выключаются на сервере** через `NetworkService::toggleBankProfile(enable=false)` (`BankSetupWizard.cpp:297-321`).
- Коды паузованных профилей запоминаются в `m_pausedProfiles`.
Это сделано, чтобы фоновый `EventHandler` / `DeviceScreener` не дёргали телефон параллельно во время работы мастера.
В **деструкторе** (`BankSetupWizard.cpp:44-47`): `stopWorkerThread()` + `resumePausedProfiles()` — возвращает паузованные профили в `"active"` и шлёт `toggleBankProfile(enable=true)` на сервер.
## Цепочка верификации (шаги 3 → 4)
`startVerification()``runPinCheck()``runProfileCheck()``runCardCheck()``onVerificationSuccess()`.
Каждый шаг запускает соответствующий банк-скрипт в **отдельном `QThread`** (`m_workerThread`), результат принимает лямбдой через `Qt::QueuedConnection`. Если шаг вернул непустую строку — `onVerificationError()`, иначе — следующий шаг.
### 1. `runPinCheck()` — проверка пин-кода (`BankSetupWizard.cpp:384`)
Сначала `startVerification()` делает базовую проверку, что устройство онлайн (`device.status == "device"`), затем upsert'ит профиль в БД с `status="off"`, `install=true`, чтобы получить `m_app.id` (`BankSetupWizard.cpp:362-382`).
Дальше создаётся `LoginAndCheckAccountsScript` с флагом **`pinCheckOnly=true`** (3-й или 4-й аргумент в зависимости от банка). Этот флаг говорит скрипту: только дойти до главного экрана, чтобы убедиться что пин валидный, и выйти. Никаких материалов не создаётся.
При успехе: `BankProfileDAO::updatePinCodeStatus(appId, "yes", "")`, прогресс → 33%, переход к `runProfileCheck()`.
### 2. `runProfileCheck()` — данные профиля (`BankSetupWizard.cpp:450`)
Запускает `GetProfileInfoScript`. Скрипт парсит ФИО/телефон/email с главного экрана, обновляет `bank_profiles` в БД и **отправляет на сервер** `POST/PATCH` через `NetworkService` — на этом этапе бэкенд возвращает `bank_profile_id`.
После завершения обработчик `onProfileResult` дочитывает запись из БД (`BankProfileDAO::getApplication`) и проверяет, что `bankProfileId` непустой (`BankSetupWizard.cpp:463`). Если бэкенд не вернул id — ошибка "Не удалось зарегистрировать банковский профиль на сервере. Возможно, банк ещё не поддерживается сервером." Прогресс → 66%, переход к `runCardCheck()`.
### 3. `runCardCheck()` — карты (`BankSetupWizard.cpp:501`)
Запускает `GetCardInfoScript`. Скрипт обходит все карты в приложении, парсит полный номер карты (для Ozon/Dushanbe — через OCR/XML), складывает в БД и отправляет `POST material` на сервер.
После завершения `onCardResult` читает `materials = MaterialDAO::getAccounts(...)`, и если хотя бы одна карта есть, но **ни у одной нет `materialId`** (т.е. ничего не синхронизировалось с сервером) — ошибка "Не удалось отправить данные карт на сервер" (`BankSetupWizard.cpp:518`). Прогресс → 100%, вызов `onVerificationSuccess()`.
### 4. `onVerificationSuccess()` (`BankSetupWizard.cpp:556`)
- Перечитывает профиль из БД, формирует HTML с ФИО/телефоном/email.
- Загружает карты, и **если хоть у одной `cardNumber.isEmpty()` — уходит в Error** ("Не удалось получить полный номер карты, распознаны только последние 4 цифры") (`BankSetupWizard.cpp:574-580`). Это страховка поверх защиты в самих скриптах.
- Заполняет таблицу: 4 колонки (Номер / Сумма / Валюта / Активна-radio).
- Логика автовыбора:
- **0 карт** → строка "Карты не найдены", кнопка Завершить активна.
- **1 карта** → автовыбор, колонка radio скрыта.
- **≥2 карт** → первая карта выбрана по умолчанию, можно перевыбрать.
### 5. `applyActiveCardSelection()` (`BankSetupWizard.cpp:631`) — при нажатии Завершить
- `MaterialDAO::activateExclusive(deviceId, appCode, selectedMaterialId)` — делает выбранную карту единственной активной в этом профиле; возвращает список деактивированных.
- Для выбранной карты шлёт `NetworkService::toggleMaterial(enable=true)`, для каждой деактивированной — `toggleMaterial(enable=false)`. Каждый запрос — в своём `QThread`, fire-and-forget.
## Обработка ошибок
`onVerificationError()` (`BankSetupWizard.cpp:690`) проганяет текст через `humanReadableError()` (`BankSetupWizard.cpp:659`), который:
1. Определяет **этап** по подстрокам ("пин-кода" / "профиля" / "карт").
2. Распознаёт типовые сообщения скриптов (`"Cant open the home screen"`, `"No cards found"`, `"Cannot find user name"`, и т.д.) и подставляет понятный русский текст с подсказкой что делать.
3. Всё незнакомое → "Произошла непредвиденная ошибка. Попробуйте повторить позже."
Кнопка **«Начать заново»** на странице Error дополнительно чистит БД, но **только если профиль ещё не зарегистрирован на сервере** (`current.bankProfileId.isEmpty()`, `BankSetupWizard.cpp:277-283`): удаляет материалы (`MaterialDAO::deleteAccountsByApp`) и сам профиль (`BankProfileDAO::deleteProfile`), сбрасывает `m_app.id = -1`. Это предотвращает дубликаты при повторной попытке.
## Управление воркер-потоком
`stopWorkerThread()` (`BankSetupWizard.cpp:342`) — критичный кусок:
1. `requestInterruption()` + `quit()`, ждёт до 3 секунд `wait(3000)`.
2. Если поток **залип** (обычно — внутри `QProcess::waitForFinished` в `AdbUtils::startProcess`): **отсоединяет** `QThread` от родителя (`setParent(nullptr)`) и вешает `deleteLater` на сигнал `finished`. Без этого `~QWidget` удалит работающий `QThread``qFatal`.
Каждый `run*Check()` сначала зовёт `stopWorkerThread()`, поэтому переход на следующий шаг безопасен даже при гонках.
## Краткая последовательность данных
```
Пользователь вводит PIN
BankProfileDAO::upsertApplication (status=off, install=true)
LoginAndCheckAccountsScript (pinCheckOnly=true) ─→ только верифицирует вход
BankProfileDAO::updatePinCodeStatus("yes", "")
GetProfileInfoScript ─→ парсит профиль → POST/PATCH bank_profile → bankProfileId
GetCardInfoScript ─→ парсит карты → POST material для каждой → materialId
Таблица карт + выбор активной
MaterialDAO::activateExclusive + toggleMaterial на сервере
accept() → resumePausedProfiles() в деструкторе возвращает остальные профили в active
```