- Add toggleMaterial logic to NetworkService for enabling/disabling materials via PATCH request.
- Implement `toggleMaterialStatus` in `AccountSettingsWindow` for UI control and threading integration. - Enhance `MaterialDAO` with numeric validation for `lastNumbers` field to ensure data integrity. - Update `PayByCardScript` and `PayByPhoneScript` to handle PIN entry if required after payment confirmation. - Introduce dynamic UI adjustments for material toggling, including a new toggle button and layout refinements. - Add documentation for task logic in `ozon_tasks.md` and `black_tasks.md`. - Switch default currency in `GetCardInfoScript` (Black app) from USD to ARS.
This commit is contained in:
parent
7e826a22f1
commit
38f33797c5
@ -110,7 +110,7 @@ void GetCardInfoScript::doStart() {
|
||||
account.lastNumbers = lastNumbers;
|
||||
account.cardNumber = cvuNumber;
|
||||
account.amount = balance;
|
||||
account.currency = "USD";
|
||||
account.currency = "ARS";
|
||||
account.description = "CVU";
|
||||
account.status = MaterialStatus::Active;
|
||||
account.updateTime = QDateTime::currentDateTimeUtc();
|
||||
|
||||
@ -81,6 +81,8 @@ void PayByCardScript::makePayment(
|
||||
if (enviarBtn.isEmpty()) {
|
||||
m_error = "Button 'Enviar a CBU o alias' not found: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -103,6 +105,8 @@ void PayByCardScript::makePayment(
|
||||
if (!onInputScreen) {
|
||||
m_error = "Input card number screen not loaded: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -153,6 +157,8 @@ void PayByCardScript::makePayment(
|
||||
if (!onTransferConfirm) {
|
||||
m_error = "Transfer confirm screen not loaded: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -186,6 +192,8 @@ void PayByCardScript::makePayment(
|
||||
if (!onInputSum) {
|
||||
m_error = "Input sum screen not loaded: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -278,6 +286,8 @@ void PayByCardScript::makePayment(
|
||||
if (!onPayConfirm) {
|
||||
m_error = "Pay confirm screen not loaded: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -330,6 +340,8 @@ void PayByCardScript::makePayment(
|
||||
if (!onSuccess) {
|
||||
m_error = "Pay success screen not loaded: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -281,10 +281,28 @@ void PayByCardScript::makePayment(
|
||||
AdbUtils::makeTap(deviceId, transferBtn2.x(), transferBtn2.y());
|
||||
QThread::msleep(3000);
|
||||
|
||||
// 8.5. Проверяем экран ввода PIN-кода
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
if (const QList<Node> pinNodes = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
!pinNodes.isEmpty()) {
|
||||
qDebug() << "[PayByCard] PIN code screen detected after confirm, entering PIN...";
|
||||
inputPinCode(deviceId, pinNodes);
|
||||
QThread::msleep(3000);
|
||||
}
|
||||
|
||||
// 9. Ждём результат: "Успешно" или "Не удалось"
|
||||
for (int attempt = 0; attempt < 15; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
// Если PIN-код появился позже — вводим
|
||||
if (const QList<Node> pinNodes = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
!pinNodes.isEmpty()) {
|
||||
qDebug() << "[PayByCard] PIN code screen detected (retry), entering PIN...";
|
||||
inputPinCode(deviceId, pinNodes);
|
||||
QThread::msleep(3000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xmlScreenParser.findTextNode("Успешно").isEmpty()) {
|
||||
qDebug() << "[PayByCard] Payment successful!";
|
||||
m_error.clear();
|
||||
|
||||
@ -386,10 +386,28 @@ void PayByPhoneScript::makePayment(
|
||||
AdbUtils::makeTap(deviceId, transferConfirmBtn.x(), transferConfirmBtn.y());
|
||||
QThread::msleep(3000);
|
||||
|
||||
// 8.5. Проверяем экран ввода PIN-кода
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
if (const QList<Node> pinNodes = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
!pinNodes.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] PIN code screen detected after confirm, entering PIN...";
|
||||
inputPinCode(deviceId, pinNodes);
|
||||
QThread::msleep(3000);
|
||||
}
|
||||
|
||||
// 9. Ждём результат: "Успешно" или "Не удалось"
|
||||
for (int attempt = 0; attempt < 15; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
// Если PIN-код появился позже — вводим
|
||||
if (const QList<Node> pinNodes = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
!pinNodes.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] PIN code screen detected (retry), entering PIN...";
|
||||
inputPinCode(deviceId, pinNodes);
|
||||
QThread::msleep(3000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xmlScreenParser.findTextNode("Успешно").isEmpty()) {
|
||||
qDebug() << "[PayByPhone] Payment successful!";
|
||||
m_error.clear();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QRegularExpression>
|
||||
#include "MaterialDAO.h"
|
||||
|
||||
#include "db/MaterialInfo.h"
|
||||
@ -7,6 +8,14 @@
|
||||
#include "time/DateUtils.h"
|
||||
|
||||
bool MaterialDAO::upsertAccount(const MaterialInfo &info) {
|
||||
// Валидация: lastNumbers должен содержать только цифры
|
||||
static const QRegularExpression digitsOnly(R"(^\d+$)");
|
||||
if (!info.lastNumbers.isEmpty() && !digitsOnly.match(info.lastNumbers).hasMatch()) {
|
||||
qWarning() << "[MaterialDAO] upsertAccount rejected: lastNumbers is not numeric:"
|
||||
<< info.lastNumbers << "appCode:" << info.appCode;
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
|
||||
368
docs/black_tasks.md
Normal file
368
docs/black_tasks.md
Normal file
@ -0,0 +1,368 @@
|
||||
# Black - Логика задач
|
||||
|
||||
[← Главная](index.md) | [События](events.md)
|
||||
|
||||
Описание логики всех типов задач для банка **Black** (appCode: `black`).
|
||||
|
||||
> Black — аргентинское банковское приложение. Интерфейс на испанском. Валюта: ARS (код 32). Таймзона: America/Argentina/Buenos_Aires (UTC-3).
|
||||
|
||||
---
|
||||
|
||||
## Общие механизмы (CommonScript)
|
||||
|
||||
### Запуск приложения (`runAppAndGoToHomeScreen`)
|
||||
|
||||
1. Убивает приложение если запущено
|
||||
2. Запускает приложение
|
||||
3. Ждёт 4.5 секунды
|
||||
4. Цикл до 10 попыток:
|
||||
- Получает XML-дамп экрана
|
||||
- Проверяет `isHomeScreen()` (наличие "Saldo disponible" + content-desc "Inicio")
|
||||
- Если экран с PIN-кодом (`isPinCodeScreen()` — "¿Olvidaste tu PIN?" или "Ingresá tu PIN"):
|
||||
- Находит EditText с password=true
|
||||
- Тапает для фокуса → ожидание 500мс
|
||||
- Вводит PIN через `AdbUtils::inputText()`
|
||||
- Ожидание 3 сек
|
||||
- Ожидание 1 сек между попытками
|
||||
5. При неудаче — скриншот
|
||||
|
||||
### Прокрутка к кнопке (`swipeToButton`)
|
||||
|
||||
- До 10 свайпов вверх
|
||||
- После каждого свайпа парсит XML и ищет кнопку
|
||||
- Аналогично Ozon
|
||||
|
||||
### Создание банковского профиля (`createBankProfileIfNeeded`)
|
||||
|
||||
- Аналогично Ozon (общий код в CommonScript)
|
||||
- Валюта по умолчанию: ARS→32
|
||||
|
||||
---
|
||||
|
||||
## FETCH_PROFILE
|
||||
|
||||
**Скрипты:** `Black::LoginAndCheckAccountsScript` → `Black::GetProfileInfoScript` → `Black::GetCardInfoScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Авторизация, сбор профиля (ФИО, телефон, email), получение CVU и баланса.
|
||||
|
||||
### Шаги LoginAndCheckAccountsScript
|
||||
|
||||
1. **Валидация** — получение DeviceInfo и BankProfileInfo из БД
|
||||
2. **Запуск приложения** — `runAppAndGoToHomeScreen()` с PIN-кодом
|
||||
3. **Запуск GetProfileInfoScript** — синхронно, для сбора профиля
|
||||
4. При ошибке: скриншот + лог + обновление комментария в `BankProfileDAO`
|
||||
|
||||
### Шаги GetProfileInfoScript
|
||||
|
||||
1. **Домашний экран** — проверка/перезапуск
|
||||
|
||||
2. **Парсинг с домашнего экрана**:
|
||||
- fullName из `parseUserNameFromHomeScreen()` — формат "¡Hola!\nИМЯ\ninitials"
|
||||
- Баланс из `parseBalanceFromHomeScreen()` — формат "$1.857,00" → 1857.00
|
||||
- Обновление баланса в `BankProfileDAO`
|
||||
|
||||
3. **Навигация в боковое меню**:
|
||||
- Поиск кнопки "¡Hola!" (аватар)
|
||||
- Тап → ожидание 2 сек
|
||||
- Ожидание `isSideMenu()` (5 × 1 сек) — маркеры: "Tu Información" + "Cerrar sesión"
|
||||
|
||||
4. **Навигация к профилю**:
|
||||
- Тап "Tu Información" (content-desc)
|
||||
- Ожидание 2.5 сек
|
||||
- Ожидание `isProfilePage()` (5 × 1 сек) — маркер: content-desc "Tu información"
|
||||
|
||||
5. **Парсинг профиля**:
|
||||
- Nombre, Apellido, Email — по hint-атрибутам EditText
|
||||
- Свайп вниз → ожидание 1.5 сек
|
||||
- Телефон — из hint-атрибута
|
||||
- fullName = "nombre apellido" (или fallback на имя с домашнего экрана)
|
||||
|
||||
6. **Сохранение**: `BankProfileDAO::updateProfileInfo(id, fullName, phone, email)`
|
||||
|
||||
7. **Синхронизация с сервером**: `createBankProfileIfNeeded()`
|
||||
|
||||
8. **Отправка аккаунтов**: `postAccountsData()` для аккаунтов без materialId
|
||||
|
||||
9. **Завершение** — убийство приложения
|
||||
|
||||
### Шаги GetCardInfoScript (CVU)
|
||||
|
||||
1. **Домашний экран** — проверка/перезапуск
|
||||
|
||||
2. **Парсинг баланса** → обновление в БД
|
||||
|
||||
3. **Навигация к CVU**:
|
||||
- Поиск кнопки "Tu CVU" (exact match)
|
||||
- Тап → ожидание 2 сек
|
||||
- Ожидание `isCVUScreen()` (5 × 1 сек)
|
||||
|
||||
4. **Парсинг CVU**:
|
||||
- `parseCVUNumber()` — 22-значный номер после маркера "CVU"
|
||||
- lastNumbers = последние 4 цифры CVU
|
||||
|
||||
5. **Сохранение в БД**:
|
||||
- `MaterialDAO::upsertAccount()` с:
|
||||
- cardNumber = полный CVU
|
||||
- lastNumbers = CVU[-4:]
|
||||
- currency = "USD" (хардкод)
|
||||
- description = "CVU"
|
||||
|
||||
6. **Отправка материала на сервер**: `ns.postMaterial()` (если есть bankProfileId и нет materialId)
|
||||
|
||||
7. **Завершение** — убийство приложения
|
||||
|
||||
### Ответ серверу (при успехе)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": "...",
|
||||
"bank_name": "black",
|
||||
"data": {
|
||||
"bank_account_id": "",
|
||||
"amount": 185700,
|
||||
"currency": 32
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FETCH_TRANSACTIONS
|
||||
|
||||
**Скрипт:** `Black::GetLastTransactionsScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Получение списка последних транзакций из истории операций.
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Домашний экран** — проверка/перезапуск
|
||||
|
||||
2. **Навигация к истории транзакций**:
|
||||
- Поиск кнопки "Ver más" после секции "Tu actividad" через `findVerMasAfterTuActividad()`
|
||||
- Если не найдена — до 5 свайпов вниз (1 сек между свайпами)
|
||||
- Тап "Ver más" → ожидание 2 сек
|
||||
|
||||
3. **Ожидание загрузки** (15 × 1.5 сек):
|
||||
- `isTransactionLoadingScreen()` = "Tu Actividad" есть, но транзакций нет (загрузка)
|
||||
- `isTransactionListScreen()` = "Tu Actividad" + видимые транзакции
|
||||
- Если после 15 попыток всё ещё loading → пустой аккаунт (нормально), возврат без ошибки
|
||||
|
||||
4. **Парсинг транзакций** (`parseTransactionItems()`):
|
||||
- Источник: ImageView ноды с текстом формата:
|
||||
```
|
||||
Transferencia
|
||||
DD de MES de YYYY - HH:MM
|
||||
$amount,cents
|
||||
```
|
||||
или (входящие):
|
||||
```
|
||||
Transferencia externa de ИМЯ
|
||||
DD de MES de YYYY - HH:MM
|
||||
$amount,cents
|
||||
```
|
||||
- Отрицательные суммы: `$-amount` (исходящие переводы)
|
||||
- Парсинг испанских дат (`parseSpanishDate()`): enero, febrero, ..., diciembre
|
||||
|
||||
5. **Сохранение в БД** (для каждой транзакции):
|
||||
- bankName = "black"
|
||||
- status = Complete
|
||||
- timestamp = currentDateTimeUtc()
|
||||
- Конвертация времени: парсинг испанской даты → таймзона Buenos Aires
|
||||
- `TransactionDAO::insertTransactionIfNotExists()`
|
||||
|
||||
6. **Возврат** — `goBack()` → ожидание 1 сек
|
||||
|
||||
### Retry-логика (EventHandler)
|
||||
|
||||
Аналогична Ozon:
|
||||
```
|
||||
"Transaction not found" + < 10 мин → повтор через 15 сек
|
||||
"Transaction not found" + ≥ 10 мин → ошибка
|
||||
```
|
||||
|
||||
### Ответ серверу
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_TRANSACTIONS",
|
||||
"material_id": "...",
|
||||
"bank_name": "black",
|
||||
"data": [
|
||||
{
|
||||
"bank_transaction_id": null,
|
||||
"amount": 185700,
|
||||
"currency": 32,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "+5491112345678",
|
||||
"status": "completed",
|
||||
"extra": { "fee": 0 },
|
||||
"created_at": "2025-01-15T14:30:00.000-03:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CREATE_TRANSACTION (bank — по CBU/CVU/Alias)
|
||||
|
||||
**Скрипт:** `Black::PayByCardScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Перевод на CBU, CVU или Alias получателя.
|
||||
|
||||
### Входные параметры
|
||||
|
||||
- `event.phone` — CBU/CVU/Alias получателя (поле `m_cardNumber`)
|
||||
- `event.amount` — сумма перевода
|
||||
- `m_account` — аккаунт-отправитель
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Домашний экран** — проверка/перезапуск
|
||||
|
||||
2. **Поиск кнопки оплаты**:
|
||||
- `findNodeByContentDesc("Enviar a\nCBU o alias", contains=true)`
|
||||
- Если не найдена — до 5 свайпов вниз
|
||||
- Тап → ожидание 2 сек
|
||||
|
||||
3. **Ввод реквизитов** (10 × 1.5 сек ожидание экрана):
|
||||
- Экран `isPayInputCardNumberScreen()` — hint "Ingresar CBU, CVU o Alias"
|
||||
- Тап на EditText → ожидание 500мс
|
||||
- Ввод CBU/CVU/Alias
|
||||
- Тап "Continuar" → ожидание 2 сек
|
||||
|
||||
4. **Подтверждение получателя** (10 × 1.5 сек):
|
||||
- Экран `isPayTransferConfirmScreen()` — кнопка "Transferir" + поле "Alias:"
|
||||
- Тап "Transferir" → ожидание 2 сек
|
||||
|
||||
5. **Ввод суммы** (10 × 1.5 сек):
|
||||
- Экран `isPayInputSumScreen()` — "Cuánto vas a transferír"
|
||||
- Тап на EditText → ожидание 500мс
|
||||
- **Очистка поля**: 20 × keyevent 67 (backspace)
|
||||
- **Ввод суммы в сентаво**: `int(amount * 100)` → строка
|
||||
- Пример: 500.00 → "50000"
|
||||
- Проверка: перечитывает поле, если не совпадает — повторяет ввод
|
||||
- Тап "Continuar" → ожидание 2 сек
|
||||
|
||||
6. **Финальное подтверждение** (10 × 1.5 сек):
|
||||
- Экран `isPayConfirmScreen()` — "Vas a transferir \na ИМЯ_ПОЛУЧАТЕЛЯ"
|
||||
- Парсинг имени получателя (`parseRecipientNameFromConfirm()`)
|
||||
- Тап "Transferir" → ожидание 3 сек
|
||||
|
||||
7. **Ввод PIN** (если запрошен):
|
||||
- `isPinCodeScreen()` → `inputPinCode()`
|
||||
- Ожидание 3 сек
|
||||
|
||||
8. **Проверка результата** (15 × 2 сек):
|
||||
- **"Dinero enviado!"** → успех:
|
||||
- Парсинг bankTime: `parsePaySuccessDateTime()` → "DD/MM/YY HH:MM"
|
||||
- Парсинг ID: `parsePaySuccessCoelsaId()` → Coelsa transaction ID
|
||||
- Скриншот как чек
|
||||
- Если снова PIN-экран → повторный ввод PIN
|
||||
- Таймаут → ошибка
|
||||
|
||||
9. **Сохранение транзакции**:
|
||||
- amount = -m_amount (отрицательная)
|
||||
- fee = 0
|
||||
- type = Card, status = Complete
|
||||
- name = имя получателя
|
||||
- description = "Transferencia a ИМЯ"
|
||||
- phone = CBU/CVU/Alias
|
||||
- bankTrExternalId = Coelsa ID
|
||||
- receiptImage = скриншот
|
||||
- `TransactionDAO::insertTransaction()`
|
||||
|
||||
### Ответ серверу
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "CREATE_TRANSACTION",
|
||||
"material_id": "...",
|
||||
"bank_name": "black",
|
||||
"data": {
|
||||
"amount": -50000,
|
||||
"currency": 32,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "0000003100012345678901",
|
||||
"status": "completed",
|
||||
"bank_transaction_id": "COELSA-12345",
|
||||
"extra": { "fee": 0 },
|
||||
"created_at": "2025-01-15T14:30:00.000-03:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ScreenXmlParser (Black)
|
||||
|
||||
### Определение экранов
|
||||
|
||||
| Метод | Маркеры |
|
||||
|-------|---------|
|
||||
| `isHomeScreen()` | "Saldo disponible" + content-desc "Inicio" |
|
||||
| `isPinCodeScreen()` | "¿Olvidaste tu PIN?" или "Ingresá tu PIN" |
|
||||
| `isSideMenu()` | "Tu Información" + "Cerrar sesión" |
|
||||
| `isProfilePage()` | content-desc "Tu información" |
|
||||
| `isCVUScreen()` | маркер "CVU" |
|
||||
| `isTransactionLoadingScreen()` | "Tu Actividad" без "Transferencia" |
|
||||
| `isTransactionListScreen()` | "Tu Actividad" + транзакции |
|
||||
| `isPayInputCardNumberScreen()` | EditText с hint "Ingresar CBU, CVU o Alias" |
|
||||
| `isPayTransferConfirmScreen()` | "Transferir" + "Alias:" без "Vas a transferir" |
|
||||
| `isPayInputSumScreen()` | "Cuánto vas a transferír" |
|
||||
| `isPayConfirmScreen()` | "Vas a transferir" |
|
||||
| `isPaySuccessScreen()` | "Dinero enviado!" |
|
||||
|
||||
### Парсинг данных
|
||||
|
||||
| Метод | Формат / Логика |
|
||||
|-------|-----------------|
|
||||
| `parseUserNameFromHomeScreen()` | "¡Hola!\nИМЯ\ninitials" → ИМЯ |
|
||||
| `parseBalanceFromHomeScreen()` | "$1.857,00" → 1857.00 (аргентинский формат) |
|
||||
| `parseProfileNombre/Apellido/Email/Phone()` | По hint-атрибутам EditText |
|
||||
| `parseCVUNumber()` | 22-значный номер после маркера "CVU" |
|
||||
| `parseTransactionItems()` | ImageView ноды с "Transferencia" |
|
||||
| `parseSpanishDate()` | "DD de MES de YYYY - HH:MM" → QDateTime |
|
||||
| `parseRecipientNameFromConfirm()` | "Vas a transferir \na ИМЯ" → ИМЯ |
|
||||
| `parsePaySuccessDateTime()` | "DD/MM/YY HH:MM" |
|
||||
| `parsePaySuccessCoelsaId()` | Текст после "Id Coelsa" |
|
||||
|
||||
### Парсинг транзакций
|
||||
|
||||
Формат элемента списка (ImageView text):
|
||||
```
|
||||
Transferencia // тип
|
||||
15 de enero de 2025 - 14:30 // дата (испанский)
|
||||
$1.500,00 // сумма (положительная = входящая)
|
||||
```
|
||||
|
||||
или:
|
||||
```
|
||||
Transferencia externa de Juan Perez // тип + имя отправителя
|
||||
15 de enero de 2025 - 14:30
|
||||
$-1.500,00 // отрицательная = исходящая
|
||||
```
|
||||
|
||||
### Испанские месяцы
|
||||
|
||||
| Месяц | Перевод |
|
||||
|-------|---------|
|
||||
| enero | январь |
|
||||
| febrero | февраль |
|
||||
| marzo | март |
|
||||
| abril | апрель |
|
||||
| mayo | май |
|
||||
| junio | июнь |
|
||||
| julio | июль |
|
||||
| agosto | август |
|
||||
| septiembre | сентябрь |
|
||||
| octubre | октябрь |
|
||||
| noviembre | ноябрь |
|
||||
| diciembre | декабрь |
|
||||
384
docs/ozon_tasks.md
Normal file
384
docs/ozon_tasks.md
Normal file
@ -0,0 +1,384 @@
|
||||
# Ozon - Логика задач
|
||||
|
||||
[← Главная](index.md) | [События](events.md)
|
||||
|
||||
Описание логики всех типов задач для банка **Ozon** (appCode: `ozon`).
|
||||
|
||||
---
|
||||
|
||||
## Общие механизмы (CommonScript)
|
||||
|
||||
### Запуск приложения (`runAppAndGoToHomeScreen`)
|
||||
|
||||
1. Убивает приложение если запущено (`AdbUtils::tryToKillApplication`)
|
||||
2. Запускает приложение (`AdbUtils::tryToStartApplication`)
|
||||
3. Ждёт 4.5 секунды на старт
|
||||
4. Цикл до 10 попыток:
|
||||
- Получает XML-дамп экрана
|
||||
- Проверяет `isHomeScreen()` (наличие кнопок "Перевести" + "Пополнить")
|
||||
- Закрывает баннеры: диалоги, баннер безопасности, Google Play, рекламный bottom-sheet
|
||||
- Если экран с PIN-кодом — вводит PIN (100–900мс между цифрами)
|
||||
- Ожидание 1 сек между попытками
|
||||
5. При неудаче — скриншот + лог ошибки
|
||||
|
||||
### Навигация к продуктам (`goToAllProducts`)
|
||||
|
||||
1. Закрывает баннеры на домашнем экране
|
||||
2. Ищет вкладку "Финансы"
|
||||
3. Тапает до 3 раз с ожиданием 1 сек
|
||||
|
||||
### Прокрутка к кнопке (`swipeToButton`)
|
||||
|
||||
- До 10 свайпов вверх (от 3/4 высоты к 1/4 высоты экрана)
|
||||
- После каждого свайпа парсит XML и ищет кнопку
|
||||
- Ожидание 1 сек между свайпами
|
||||
|
||||
### Создание банковского профиля (`createBankProfileIfNeeded`)
|
||||
|
||||
- Читает fullName и phone из `BankProfileDAO`
|
||||
- Нормализует телефон: убирает нецифровые символы, добавляет код страны из `config.ini`
|
||||
- Маппинг валют: RUB→643, USD→840, KRW→410, AZN→944, ARS→32
|
||||
- POST (новый) или PATCH (существующий) на сервер
|
||||
|
||||
---
|
||||
|
||||
## FETCH_PROFILE
|
||||
|
||||
**Скрипт:** `Ozon::LoginAndCheckAccountsScript` + `Ozon::GetCardInfoScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Авторизация в приложении, получение баланса, полных номеров карт и синхронизация с сервером.
|
||||
|
||||
### Шаги LoginAndCheckAccountsScript
|
||||
|
||||
1. **Валидация** — получение DeviceInfo и BankProfileInfo из БД
|
||||
2. **Запуск приложения** — `runAppAndGoToHomeScreen()` с PIN-кодом
|
||||
3. **Создание профиля** — `createBankProfileIfNeeded()` (синхронизация fullName/phone)
|
||||
4. **Сбор аккаунтов** (если не режим fetchProfileOnly):
|
||||
- Навигация к "Финансы" через `goToAllProducts()`
|
||||
- Парсинг видимых аккаунтов из XML
|
||||
- Upsert в `MaterialDAO`
|
||||
- Отправка на сервер
|
||||
5. **Запуск GetCardInfoScript** — синхронно, для получения полных номеров карт
|
||||
|
||||
### Шаги GetCardInfoScript
|
||||
|
||||
1. **Навигация на домашний экран**
|
||||
2. **Парсинг профиля** (если нет в БД):
|
||||
- Тап на имя пользователя вверху экрана
|
||||
- Парсинг fullName и phone
|
||||
- Обновление `BankProfileDAO`
|
||||
3. **Загрузка карт** — `parseCardsOnHomeScreen()` (до 5 попыток, 1.5 сек)
|
||||
4. **Upsert всех карт** в `MaterialDAO`
|
||||
5. **Синхронизация с сервером**:
|
||||
- Создание устройства (POST) если нет `apiId`
|
||||
- POST/PATCH банковского профиля с балансом, ФИО, телефоном
|
||||
6. **Получение полных номеров карт** (для каждой карты):
|
||||
- Тап на карту → ожидание экрана карты (10 × 2 сек)
|
||||
- Тап "Показать" → ожидание появления номера (10 × 2 сек)
|
||||
- Regex: `\d{4}[\s]\d{4}[\s]\d{4}[\s]\d{4}` (с unicode-пробелами)
|
||||
- Обработка баннера "Понятно"
|
||||
- Сохранение в `MaterialDAO::updateCardNumber()`
|
||||
- При неудаче: статус карты → Off, лог + скриншот
|
||||
7. **Отправка материалов** на сервер (`ns.postMaterial()`)
|
||||
8. **Завершение** — убийство приложения
|
||||
|
||||
### Ответ серверу (при успехе)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": "...",
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"bank_account_id": "",
|
||||
"amount": 1500000,
|
||||
"currency": 643
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `amount` — в копейках (баланс × 100)
|
||||
|
||||
---
|
||||
|
||||
## FETCH_TRANSACTIONS
|
||||
|
||||
**Скрипт:** `Ozon::GetLastTransactionsScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Поиск конкретной транзакции по сумме и времени в истории операций банка.
|
||||
|
||||
### Входные параметры (из JSON задачи)
|
||||
|
||||
- `body.amount` — сумма в копейках (делится на 100)
|
||||
- `body.created_at` — время транзакции (ISO формат)
|
||||
|
||||
### Два режима работы
|
||||
|
||||
| Режим | Условие | Поведение |
|
||||
|-------|---------|-----------|
|
||||
| **Поиск** | `searchAmount > 0` | Ищет конкретную транзакцию |
|
||||
| **Сканирование** | `searchAmount == 0` | Собирает все транзакции за 24 часа |
|
||||
|
||||
### Шаги (режим поиска)
|
||||
|
||||
1. **Навигация к списку операций** (`navigateToTransactionList`):
|
||||
- Закрытие рекламного баннера
|
||||
- Проверка домашнего экрана (перезапуск если нужно)
|
||||
- Свайп до кнопки "Все" (до 10 свайпов)
|
||||
- Ожидание экрана "Операции" (10 × 1.5 сек)
|
||||
|
||||
2. **Поиск транзакции** (`searchTransaction`):
|
||||
- Цикл до 15 прокруток
|
||||
- Для каждой видимой кнопки-транзакции:
|
||||
- Парсинг суммы из текста кнопки
|
||||
- Проверка совпадения: `|amount - searchAmount| ≤ 0.01`
|
||||
- Парсинг даты из текста, проверка: `|time - searchTime| ≤ 1 час`
|
||||
- При совпадении:
|
||||
- Тап на транзакцию → парсинг деталей (телефон, имя, время)
|
||||
- Вставка в `TransactionDAO`
|
||||
- Возврат
|
||||
|
||||
3. **Если не найдена** — возвращает `"Transaction not found"`
|
||||
|
||||
### Шаги (режим сканирования)
|
||||
|
||||
1. Навигация к списку операций (аналогично)
|
||||
2. **Сканирование** (`scanRecentTransactions`):
|
||||
- Для каждой транзакции:
|
||||
- Тап → ожидание деталей (10 × 1.5 сек)
|
||||
- Парсинг кнопки и деталей
|
||||
- Фильтр по времени: пропускает транзакции старше 24 часов
|
||||
- **Получение чека:**
|
||||
- Тап "Документы"
|
||||
- Ожидание "Поделиться" (20 × 1.5 сек)
|
||||
- Скриншот → OCR (Tesseract, rus+eng)
|
||||
- Извлечение ID операции через regex:
|
||||
- `ID операции = ...` (буквенно-цифровой)
|
||||
- `№ Ф-YYYY-XXXXXXX`
|
||||
- Вставка в `TransactionDAO`
|
||||
- Возврат назад → ожидание списка
|
||||
|
||||
### Retry-логика (EventHandler)
|
||||
|
||||
```
|
||||
Результат "Transaction not found"
|
||||
└─ Прошло < 10 мин? → статус Wait, повтор через 15 сек
|
||||
└─ Прошло ≥ 10 мин? → статус Error
|
||||
```
|
||||
|
||||
> Между повторами другие задачи могут выполниться (см. приоритеты в events.md)
|
||||
|
||||
### Ответ серверу (при успехе)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_TRANSACTIONS",
|
||||
"material_id": "...",
|
||||
"bank_name": "ozon",
|
||||
"data": [
|
||||
{
|
||||
"bank_transaction_id": "Ф-2025-1234567",
|
||||
"amount": 50000,
|
||||
"currency": 643,
|
||||
"transaction_type": "top-up",
|
||||
"bank_participator_id": "+79001234567",
|
||||
"status": "completed",
|
||||
"extra": { "fee": 0 },
|
||||
"created_at": "2025-01-15T14:30:00.000+03:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Возвращаются **все** транзакции аккаунта за последние 24 часа (не только найденная).
|
||||
|
||||
---
|
||||
|
||||
## CREATE_TRANSACTION (bank — по номеру карты)
|
||||
|
||||
**Скрипт:** `Ozon::PayByCardScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Перевод по номеру карты получателя.
|
||||
|
||||
### Входные параметры
|
||||
|
||||
- `event.phone` — номер карты получателя
|
||||
- `event.amount` — сумма перевода
|
||||
- `event.comment` — имя банка-получателя
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Подготовка**:
|
||||
- Закрытие рекламных баннеров
|
||||
- Проверка домашнего экрана (перезапуск если нужно)
|
||||
- Закрытие: баннеры, Google Play, "Заказать карту"
|
||||
|
||||
2. **Тап "Перевести"** → ожидание 2 сек
|
||||
|
||||
3. **Ожидание "Платежи и переводы"** (10 × 1.5 сек)
|
||||
|
||||
4. **Тап "По номеру карты"** (секция OTHER_WAYS) → ожидание 2 сек
|
||||
|
||||
5. **Ожидание "Перевод по номеру карты"** (10 × 1.5 сек)
|
||||
|
||||
6. **Ввод номера карты**:
|
||||
- Поиск EditText с hint "Номер карты получателя"
|
||||
- `AdbUtils::inputText(cardNumber)`
|
||||
- Ожидание 2 сек
|
||||
|
||||
7. **Ввод суммы**:
|
||||
- Поиск EditText с hint "Сумма от"
|
||||
- Ввод суммы (целое число)
|
||||
- Ожидание 1 сек
|
||||
|
||||
8. **Ожидание расчёта комиссии** (15 × 1.5 сек):
|
||||
- Поиск текста "Комиссия" + парсинг суммы после него
|
||||
- Извлечение цифр из формата "45 ₽"
|
||||
- Fallback: комиссия = 0
|
||||
|
||||
9. **Тап "Перевести"** (contains-match) → ожидание 3 сек
|
||||
|
||||
10. **Проверка результата** (15 × 2 сек):
|
||||
- **"Успешно"** → сохранение транзакции:
|
||||
- amount = -сумма (отрицательная)
|
||||
- fee = рассчитанная комиссия
|
||||
- type = Card, status = Complete
|
||||
- timestamp = Moscow timezone
|
||||
- **"Не удалось"** → ошибка
|
||||
- **Таймаут** → "Не дождались результата"
|
||||
|
||||
11. **Получение чека** (при успехе):
|
||||
- Тап "Документы"
|
||||
- Ожидание "Поделиться" (20 × 1.5 сек)
|
||||
- Скриншот → OCR → извлечение ID операции
|
||||
- Обновление БД: receipt image + external ID
|
||||
|
||||
### Ответ серверу
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "CREATE_TRANSACTION",
|
||||
"material_id": "...",
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"amount": -50000,
|
||||
"currency": 643,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "2200123456789012",
|
||||
"status": "completed",
|
||||
"bank_transaction_id": "Ф-2025-1234567",
|
||||
"extra": { "fee": 4500 },
|
||||
"created_at": "2025-01-15T14:30:00.000+03:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CREATE_TRANSACTION (top-up — по номеру телефона)
|
||||
|
||||
**Скрипт:** `Ozon::PayByPhoneScript`
|
||||
|
||||
### Цель
|
||||
|
||||
Перевод по номеру телефона с выбором банка-получателя.
|
||||
|
||||
### Входные параметры
|
||||
|
||||
- `event.phone` — номер телефона получателя
|
||||
- `event.amount` — сумма перевода
|
||||
- `event.comment` — имя банка-получателя (для СБП)
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Подготовка** — аналогично PayByCard
|
||||
|
||||
2. **Тап "Перевести"** → ожидание 2 сек
|
||||
|
||||
3. **Ожидание страницы платежей** → поиск EditText (10 × 1.5 сек)
|
||||
|
||||
4. **Навигация к "Кому перевести"**:
|
||||
- Тап на первый EditText → ожидание 2 сек
|
||||
- Ожидание экрана "Кому перевести" (10 × 1.5 сек)
|
||||
|
||||
5. **Ввод номера телефона**:
|
||||
- Поиск первого EditText
|
||||
- `AdbUtils::inputText(phone)`
|
||||
- Ожидание 2 сек
|
||||
|
||||
6. **Выбор получателя** (цикл 20 попыток):
|
||||
- **Вариант A**: Найдена кнопка с номером телефона → тап
|
||||
- **Вариант B**: Выбор банка:
|
||||
- Тап "Другой банк" (content-desc="other-bank-item")
|
||||
- Включение ADB IME клавиатуры
|
||||
- Ввод названия банка через `AdbUtils::inputAdbIMEText()`
|
||||
- Отключение ADB IME
|
||||
- Поиск банка в списке (10 × 1.5 сек)
|
||||
- Если банк не найден → ошибка "Банк '...' не найден"
|
||||
- **Условие выхода**: текст "По номеру телефона" вверху экрана (y < height/7)
|
||||
|
||||
7. **Ввод суммы**:
|
||||
- Первый EditText → ввод суммы (целое число)
|
||||
- Ожидание 1 сек
|
||||
|
||||
8. **Тап "Продолжить"** → ожидание 2 сек
|
||||
|
||||
9. **Ожидание "Подтвердите перевод"** (10 × 1.5 сек)
|
||||
|
||||
10. **Тап "Перевести ..."** (contains-match) → ожидание 3 сек
|
||||
|
||||
11. **Проверка результата** — аналогично PayByCard:
|
||||
- "Успешно" → транзакция (type = Phone)
|
||||
- "Не удалось" → ошибка
|
||||
- Получение чека через OCR
|
||||
|
||||
### Ответ серверу
|
||||
|
||||
Аналогичен PayByCard, но:
|
||||
- `transaction_type`: `"top-up"`
|
||||
- `bank_participator_id`: номер телефона
|
||||
|
||||
---
|
||||
|
||||
## ScreenXmlParser (Ozon)
|
||||
|
||||
### Определение экранов
|
||||
|
||||
| Метод | Маркеры |
|
||||
|-------|---------|
|
||||
| `isHomeScreen()` | Кнопки "Перевести" + "Пополнить" |
|
||||
| `isProfileScreen()` | Заголовок "Аккаунт" + кнопка "Выйти" |
|
||||
| `isTransactionListScreen()` | WebView с text="Операции" |
|
||||
|
||||
### Баннеры и диалоги
|
||||
|
||||
| Тип | Обнаружение | Действие |
|
||||
|-----|-------------|----------|
|
||||
| Диалог/пуш | Кнопки закрытия, "Позже" | Тап на кнопку закрытия |
|
||||
| Безопасность | "Мы заботимся о вашей безопасности" | Тап "Отложить" |
|
||||
| Биометрия | "Быстрый вход" | Тап "Не сейчас" |
|
||||
| Реклама | resource-id "design_bottom_sheet" | Тап "touch_outside" или back |
|
||||
| Google Play | "Закрыть диалоговое окно" / "Доступно обновление" | Тап закрытия |
|
||||
|
||||
### PIN-код (`tryToFindPinCode`)
|
||||
|
||||
**Метод 1**: Поиск кликабельных нод с текстом `^(?:key\s+)?(\d)$` → нужны все 10 цифр
|
||||
|
||||
**Метод 2** (fallback): Поиск сетки 4×3 из кликабельных элементов одного размера (±10px)
|
||||
|
||||
### Парсинг карт (`parseCardsOnHomeScreen`)
|
||||
|
||||
- Ищет "Основной счёт X ₽" для общего баланса
|
||||
- Парсит кнопки с текстом, оканчивающимся на 4 цифры
|
||||
- Regex для суммы: `([\d\s]+(?:[.,]\d+)?)\s*₽`
|
||||
|
||||
### Парсинг транзакций
|
||||
|
||||
- `parseTransactionButtonText()` — извлекает сумму и описание из текста кнопки в списке
|
||||
- `parseTransactionDetail()` — парсит детали: bankTime (DD.MM.YYYY, HH:mm), phone (+...), name
|
||||
@ -929,6 +929,26 @@ void NetworkService::patchBankProfile() {
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void NetworkService::toggleMaterial() {
|
||||
if (!ensureValidToken()) { emit finished(); return; }
|
||||
|
||||
const QString materialId = m_extraData.value("material_id").toString();
|
||||
const bool enable = m_extraData.value("enable").toBool();
|
||||
|
||||
if (materialId.isEmpty()) {
|
||||
qWarning() << "[NetworkService] toggleMaterial: material_id is empty";
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString enableStr = enable ? "true" : "false";
|
||||
const QString path = m_apiBase + m_pathMaterial + "/" + materialId + "/" + enableStr;
|
||||
patchJson(path, QJsonObject(), true);
|
||||
qDebug() << "[NetworkService] toggleMaterial done:" << materialId << "enable=" << enable;
|
||||
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void NetworkService::toggleBankProfile() {
|
||||
if (!ensureValidToken()) { emit finished(); return; }
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ public slots:
|
||||
|
||||
void postMaterial();
|
||||
|
||||
void toggleMaterial();
|
||||
|
||||
void postDeviceScreenshot();
|
||||
|
||||
void getPayments();
|
||||
|
||||
@ -495,19 +495,45 @@ void AccountSettingsWindow::updateAccountData(const MaterialInfo &account) {
|
||||
paymentThread->start();
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account, QTableWidget *tableWidget) {
|
||||
const bool isActive = account.status == MaterialStatus::Active;
|
||||
const QString newStatus = isActive ? "off" : "active";
|
||||
|
||||
if (!MaterialDAO::updateAccountById(account.id, newStatus, account.description, account.currency)) {
|
||||
qWarning() << "[AccountSettings] Failed to update material status:" << account.id;
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем на сервер PATCH /api/v1/profile/material/{id}/{true|false}
|
||||
if (!account.materialId.isEmpty()) {
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
net->moveToThread(thread);
|
||||
net->addExtra("material_id", account.materialId);
|
||||
net->addExtra("enable", !isActive);
|
||||
connect(thread, &QThread::started, net, &NetworkService::toggleMaterial);
|
||||
connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
reloadContent(tableWidget);
|
||||
tableWidget->resizeColumnsToContents();
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
QList<MaterialInfo> accounts = MaterialDAO::getAccounts(m_deviceId, m_applicationInfo.code);
|
||||
|
||||
tableWidget->clearContents();
|
||||
tableWidget->setRowCount(accounts.length());
|
||||
tableWidget->setColumnCount(9);
|
||||
tableWidget->setColumnCount(8);
|
||||
tableWidget->setHorizontalHeaderLabels({
|
||||
"Время обновления",
|
||||
"Номер",
|
||||
"",
|
||||
"Сумма",
|
||||
"Валюта",
|
||||
"Статус",
|
||||
"",
|
||||
"Сервер",
|
||||
"",
|
||||
""
|
||||
@ -516,7 +542,6 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
tableWidget->setWordWrap(true);
|
||||
tableWidget->setTextElideMode(Qt::ElideNone);
|
||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents);
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
|
||||
|
||||
@ -534,44 +559,42 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
desc->setFlags(desc->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 1, desc);
|
||||
|
||||
QTableWidgetItem *lastNumbers = new QTableWidgetItem(account.lastNumbers);
|
||||
lastNumbers->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
lastNumbers->setFlags(lastNumbers->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 2, lastNumbers);
|
||||
|
||||
QTableWidgetItem *amount = new QTableWidgetItem(QString::number(account.amount));
|
||||
amount->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
amount->setFlags(amount->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 3, amount);
|
||||
tableWidget->setItem(k, 2, amount);
|
||||
|
||||
QTableWidgetItem *currency = new QTableWidgetItem(account.currency);
|
||||
currency->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
currency->setFlags(currency->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 4, currency);
|
||||
tableWidget->setItem(k, 3, currency);
|
||||
|
||||
QTableWidgetItem *status = new QTableWidgetItem(materialStatusToUiString(account.status));
|
||||
status->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
status->setFlags(status->flags() & ~Qt::ItemIsEditable);
|
||||
if (account.status != MaterialStatus::Active) {
|
||||
status->setForeground(QBrush(Qt::red));
|
||||
}
|
||||
tableWidget->setItem(k, 5, status);
|
||||
// Кнопка вкл/выкл карты
|
||||
const bool isActive = account.status == MaterialStatus::Active;
|
||||
auto *toggleBtn = new QPushButton(isActive ? "Выкл" : "Вкл", tableWidget);
|
||||
toggleBtn->setStyleSheet(isActive
|
||||
? "background-color: orange; color: white;"
|
||||
: "background-color: green; color: white;");
|
||||
tableWidget->setCellWidget(k, 4, toggleBtn);
|
||||
|
||||
connect(toggleBtn, &QPushButton::clicked, this, [this, account, tableWidget]() {
|
||||
toggleMaterialStatus(account, tableWidget);
|
||||
});
|
||||
|
||||
auto *syncItem = new QTableWidgetItem(account.materialId.isEmpty() ? "Не синхр." : "Синхр.");
|
||||
syncItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
syncItem->setForeground(account.materialId.isEmpty() ? QBrush(Qt::red) : QBrush(Qt::darkGreen));
|
||||
syncItem->setFlags(syncItem->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 6, syncItem);
|
||||
|
||||
tableWidget->setItem(k, 5, syncItem);
|
||||
|
||||
auto *deleteBankAccountBtn = new QPushButton("Удалить", tableWidget);
|
||||
deleteBankAccountBtn->setStyleSheet("background-color: red; color: white;");
|
||||
tableWidget->setCellWidget(k, 7, deleteBankAccountBtn);
|
||||
tableWidget->setCellWidget(k, 6, deleteBankAccountBtn);
|
||||
|
||||
connect(deleteBankAccountBtn, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle("Удалить аккаунт?");
|
||||
msgBox.setText("Вы уверены, что хотите удалить аккаунт " + account.lastNumbers + "?");
|
||||
msgBox.setText("Вы уверены, что хотите удалить аккаунт?");
|
||||
QPushButton *deleteBtn = msgBox.addButton("Удалить", QMessageBox::AcceptRole);
|
||||
QPushButton *cancelBtn = msgBox.addButton("Отмена", QMessageBox::RejectRole);
|
||||
msgBox.setDefaultButton(cancelBtn);
|
||||
@ -588,7 +611,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
|
||||
auto *transactionsBtn = new QPushButton("Транзакции", tableWidget);
|
||||
transactionsBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
tableWidget->setCellWidget(k, 8, transactionsBtn);
|
||||
tableWidget->setCellWidget(k, 7, transactionsBtn);
|
||||
|
||||
connect(transactionsBtn, &QPushButton::clicked, this, [this, account]() {
|
||||
auto *txWindow = new TransactionWindow(account.id, this);
|
||||
|
||||
@ -33,5 +33,7 @@ private:
|
||||
|
||||
void sendAppStatus(const QString &deviceId, const QString &code, const QString &status);
|
||||
|
||||
void toggleMaterialStatus(const MaterialInfo &account, QTableWidget *tableWidget);
|
||||
|
||||
void updateAccountData(const MaterialInfo &account);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user