Add enhanced account and transaction handling features
Refactored code to improve account and transaction data parsing and management, introducing support for additional fields like card numbers and account statuses. Added new utilities for database connections, device-specific screenshot handling, and XML parsing for enriched account and transaction details.
This commit is contained in:
parent
15a9e788eb
commit
59cef50ad3
@ -104,16 +104,18 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
|||||||
process.start("sh", {"-c", xml});
|
process.start("sh", {"-c", xml});
|
||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
|
|
||||||
// TODO делаем скрин, убрать потом
|
if (false) {
|
||||||
// adb -s 6edc4a47 shell 'screencap -p /sdcard/screen.png' && adb -s 6edc4a47 pull /sdcard/screen.png screen_step_1.png && adb -s 6edc4a47 shell 'rm /sdcard/screen.png'
|
// TODO делаем скрин, убрать потом
|
||||||
QProcess processScreenshot;
|
// adb -s 6edc4a47 shell 'screencap -p /sdcard/screen.png' && adb -s 6edc4a47 pull /sdcard/screen.png screen_step_1.png && adb -s 6edc4a47 shell 'rm /sdcard/screen.png'
|
||||||
QString screenshot = QString(
|
QProcess processScreenshot;
|
||||||
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
|
QString screenshot = QString(
|
||||||
"adb -s %1 pull /sdcard/screen.png screen_step_%2.png && "
|
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
|
||||||
"adb -s %1 shell 'rm /sdcard/screen.png'"
|
"adb -s %1 pull /sdcard/screen.png screen_step_%2.png && "
|
||||||
).arg(deviceId, QString::number(idx));
|
"adb -s %1 shell 'rm /sdcard/screen.png'"
|
||||||
processScreenshot.start("sh", {"-c", screenshot});
|
).arg(deviceId, QString::number(idx));
|
||||||
processScreenshot.waitForFinished();
|
processScreenshot.start("sh", {"-c", screenshot});
|
||||||
|
processScreenshot.waitForFinished();
|
||||||
|
}
|
||||||
|
|
||||||
// чтобы убедиться, что мы прочитали без ошибок
|
// чтобы убедиться, что мы прочитали без ошибок
|
||||||
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
||||||
@ -128,6 +130,18 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
|||||||
return QString::fromUtf8(xmlBytes);
|
return QString::fromUtf8(xmlBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
|
||||||
|
QProcess process;
|
||||||
|
QString screenshot = QString(
|
||||||
|
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
|
||||||
|
"adb -s %1 pull /sdcard/screen.png screen_%2.png && "
|
||||||
|
"adb -s %1 shell 'rm /sdcard/screen.png'"
|
||||||
|
).arg(deviceId, name);
|
||||||
|
process.start("sh", {"-c", screenshot});
|
||||||
|
process.waitForFinished();
|
||||||
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
|
bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
|
||||||
QProcess process;
|
QProcess process;
|
||||||
process.start("adb", {
|
process.start("adb", {
|
||||||
|
|||||||
@ -17,7 +17,9 @@ public:
|
|||||||
|
|
||||||
static bool tryToKillApplication(const QString &deviceId, const QString &packageName);
|
static bool tryToKillApplication(const QString &deviceId, const QString &packageName);
|
||||||
|
|
||||||
static QString getScreenDumpAsXml(const QString &deviceId, const int idx);
|
static QString getScreenDumpAsXml(const QString &deviceId, int idx);
|
||||||
|
|
||||||
|
static bool takeScreenshot(const QString &deviceId, const QString &name);
|
||||||
|
|
||||||
static bool makeTap(const QString &deviceId, int x, int y);
|
static bool makeTap(const QString &deviceId, int x, int y);
|
||||||
|
|
||||||
|
|||||||
230
android/rshb/CommonScript.cpp
Normal file
230
android/rshb/CommonScript.cpp
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#include "CommonScript.h"
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "adb/AdbUtils.h"
|
||||||
|
|
||||||
|
CommonScript::CommonScript(QObject *parent)
|
||||||
|
: QObject(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonScript::~CommonScript() = default;
|
||||||
|
|
||||||
|
void CommonScript::start() {
|
||||||
|
doStart();
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node CommonScript::swipeToButton(
|
||||||
|
const QString &deviceId,
|
||||||
|
const QString &text, int &counter, const int width, const int height
|
||||||
|
) {
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
const int x = width / 2;
|
||||||
|
const int offset = height / 4;
|
||||||
|
AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::inputPinCode(const QString &deviceId, const QList<Node> &pincode) {
|
||||||
|
for (const Node &node: pincode) {
|
||||||
|
const bool completePincode = AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||||||
|
if (!completePincode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QThread::msleep(QRandomGenerator::global()->bounded(100, 901));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::goToHomeScreen(
|
||||||
|
const QString &deviceId,
|
||||||
|
const QString &packageName,
|
||||||
|
const QString &pinCode,
|
||||||
|
const int width,
|
||||||
|
const int height
|
||||||
|
) {
|
||||||
|
// Запустить устройство
|
||||||
|
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
|
||||||
|
qDebug() << "Process already running, pid: " << pid;
|
||||||
|
AdbUtils::getScreenDumpAsXml(deviceId, 1000);
|
||||||
|
AdbUtils::tryToKillApplication(deviceId, packageName);
|
||||||
|
}
|
||||||
|
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
|
||||||
|
AdbUtils::takeScreenshot(deviceId, "run_app_error");
|
||||||
|
qWarning() << "Process has not started";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Даем минимальное время на запуск
|
||||||
|
QThread::msleep(4500);
|
||||||
|
int counter = 0;
|
||||||
|
int restarted = 0;
|
||||||
|
int totalCounter = 0;
|
||||||
|
bool findAndInputPincode = false;
|
||||||
|
while (true) {
|
||||||
|
counter++;
|
||||||
|
totalCounter++;
|
||||||
|
|
||||||
|
// 3 раза уже перезапускали приложение, завершаем попытки
|
||||||
|
if (restarted > 2) {
|
||||||
|
AdbUtils::takeScreenshot(deviceId, "3_times_try_start");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем
|
||||||
|
if (counter > 30) {
|
||||||
|
AdbUtils::tryToKillApplication(deviceId, packageName);
|
||||||
|
QThread::msleep(4500);
|
||||||
|
++restarted;
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, totalCounter + 1));
|
||||||
|
|
||||||
|
if (xmlScreenParser.isHomeScreen()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем на банеры после пин-кодом
|
||||||
|
Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
|
||||||
|
if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
|
||||||
|
AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
|
||||||
|
QThread::msleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем на банеры перед пин-кодом
|
||||||
|
Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner();
|
||||||
|
if (!closeBannerNode.isEmpty()) {
|
||||||
|
AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y());
|
||||||
|
QThread::msleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что это экран ввода пин-кода
|
||||||
|
if (!findAndInputPincode) {
|
||||||
|
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||||
|
pincode.size() == 4) {
|
||||||
|
inputPinCode(deviceId, pincode);
|
||||||
|
// После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить
|
||||||
|
findAndInputPincode = true;
|
||||||
|
QThread::msleep(3000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QThread::msleep(1000);
|
||||||
|
|
||||||
|
// Если что-то пойдет не по плану...
|
||||||
|
if (totalCounter > 100) {
|
||||||
|
AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::runAppAndGoToHomeScreen() {
|
||||||
|
const QString deviceId = "6edc4a47";
|
||||||
|
const QString packageName = "ru.rshb.dbo";
|
||||||
|
const QString pinCode = "1102";
|
||||||
|
const int width = 720;
|
||||||
|
const int height = 1600;
|
||||||
|
|
||||||
|
const QString cardNumber = "7 1 8 3";
|
||||||
|
// В том числе и данные на каком устройстве это делать
|
||||||
|
// TODO тут код работы с БД
|
||||||
|
|
||||||
|
// +7 (964) 181-51-46
|
||||||
|
const QString phoneNumber = "+79641815146";
|
||||||
|
const QString bankName = "Альфа-Банк";
|
||||||
|
const QString paySum = "250";
|
||||||
|
|
||||||
|
xmlScreenParser.test();
|
||||||
|
return false;
|
||||||
|
// выше достаем данные и все прочее
|
||||||
|
|
||||||
|
return goToHomeScreen(deviceId, packageName, pinCode, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::tryToCloseAllBannersOnHomePage(const QString &deviceId, const int width, const int height) {
|
||||||
|
Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
|
||||||
|
if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
|
||||||
|
AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO тут все другие банеры попробовать закрыть
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::goToAllProducts() {
|
||||||
|
int counter = 100;
|
||||||
|
const QString deviceId = "6edc4a47";
|
||||||
|
const int width = 720;
|
||||||
|
const int height = 1600;
|
||||||
|
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, counter));
|
||||||
|
if (tryToCloseAllBannersOnHomePage(deviceId, width, height)) {
|
||||||
|
QThread::msleep(500);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmlScreenParser.isHomeScreen()) {
|
||||||
|
// идем на экран "Все продукты"
|
||||||
|
if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) {
|
||||||
|
// 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты"
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false);
|
||||||
|
// Node allProductsBtn = xmlScreenParser.findTextNode("Курсы валют и металлов"); для теста
|
||||||
|
if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) {
|
||||||
|
qDebug() << "--- All Product in focus";
|
||||||
|
AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y());
|
||||||
|
QThread::msleep(1000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AdbUtils::makeSwipe(deviceId,
|
||||||
|
allCardTitle.x(), allCardTitle.y(),
|
||||||
|
allCardTitle.x(), allCardTitle.y() - 200);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты");
|
||||||
|
// Проверяем что вверху заголовок
|
||||||
|
if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QThread::msleep(1000);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// пишем в БД, что не дошли до домашней страницы, и завершаемся
|
||||||
|
// сохраняем скрин приложения
|
||||||
|
// дделаем алерт
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Not home screen...";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommonScript::readTransactionData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. топаем на домашнюю
|
||||||
|
// 2. распарсить мои карты и вернуть данные
|
||||||
|
// 3. синхронизировать историю транзакций (с домашней)
|
||||||
|
|
||||||
|
|
||||||
|
void CommonScript::stop() {
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
57
android/rshb/CommonScript.h
Normal file
57
android/rshb/CommonScript.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include "ScreenXmlParser.h"
|
||||||
|
|
||||||
|
class CommonScript : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CommonScript(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
~CommonScript() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void doStart() = 0;
|
||||||
|
|
||||||
|
// static method
|
||||||
|
static bool inputPinCode(const QString &deviceId, const QList<Node> &pincode);
|
||||||
|
|
||||||
|
bool goToAllProducts();
|
||||||
|
|
||||||
|
Node swipeToButton(
|
||||||
|
const QString &deviceId,
|
||||||
|
const QString &text,
|
||||||
|
int &counter, int width, int height
|
||||||
|
);
|
||||||
|
|
||||||
|
bool runAppAndGoToHomeScreen();
|
||||||
|
|
||||||
|
bool tryToCloseAllBannersOnHomePage(
|
||||||
|
const QString &deviceId, int width, int height
|
||||||
|
);
|
||||||
|
|
||||||
|
bool readTransactionData();
|
||||||
|
|
||||||
|
bool m_running = true;
|
||||||
|
ScreenXmlParser xmlScreenParser;
|
||||||
|
QString m_packageName;
|
||||||
|
QString m_pinCode;
|
||||||
|
int m_width = 0;
|
||||||
|
int m_height = 0;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
virtual void start() final;
|
||||||
|
|
||||||
|
virtual void stop() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Q_DISABLE_COPY(CommonScript);
|
||||||
|
|
||||||
|
bool goToHomeScreen(
|
||||||
|
const QString &deviceId, const QString &packageName,
|
||||||
|
const QString &pinCode, int width, int height);
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QProcess>
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
#include "ScreenXmlParser.h"
|
#include "ScreenXmlParser.h"
|
||||||
@ -112,6 +113,40 @@ bool HomeScreenScript::goToHomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getPriceAsString(const double value) {
|
||||||
|
QString sign = value < 0 ? "Минус" : "Плюс";
|
||||||
|
const auto intValue = static_cast<qint64>(std::abs(value)); // округление до целого
|
||||||
|
|
||||||
|
QString number = QString::number(intValue);
|
||||||
|
QString formatted;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (int i = number.length() - 1; i >= 0; --i) {
|
||||||
|
formatted.prepend(number[i]);
|
||||||
|
++count;
|
||||||
|
if (count % 3 == 0 && i != 0) {
|
||||||
|
formatted.prepend(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("%1 %2").arg(sign, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node swipeToButton(
|
||||||
|
ScreenXmlParser &xmlScreenParser, const QString &deviceId,
|
||||||
|
const QString &text, int &counter, const int width, const int height
|
||||||
|
) {
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
const int x = width / 2;
|
||||||
|
const int offset = height / 4;
|
||||||
|
AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void HomeScreenScript::start() {
|
void HomeScreenScript::start() {
|
||||||
// Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные
|
// Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные
|
||||||
@ -130,8 +165,53 @@ void HomeScreenScript::start() {
|
|||||||
const QString bankName = "Альфа-Банк";
|
const QString bankName = "Альфа-Банк";
|
||||||
const QString paySum = "10";
|
const QString paySum = "10";
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||||
|
|
||||||
|
// xmlScreenParser.test();
|
||||||
|
// тут просто получаем инфу которую спарсил
|
||||||
|
// == 1. Входящий (TransactionType::Phone)
|
||||||
|
// Перевод через СБП от Егор Иванович Ж (+7(000)-000-00-87) из Озон Банк (Ozon) на 40817810328000022077 .
|
||||||
|
// Идентификатор операции в СБП С2С B5121111443333300170011491301 , Дата операции 01.05.2025 14:14:41 Статус перевода Исполнен
|
||||||
|
// == 2. Исходящий (TransactionType::Phone)
|
||||||
|
// Перевод Надежда Иванкина К** в Сбербанк через СБП, по номеру телефона +79000810046, ID перевода 445900141, дата 03.05.2025
|
||||||
|
|
||||||
|
// (TransactionType::Card) -> T-BANK CARD2CARD
|
||||||
|
// TransactionInfo trInfo = xmlScreenParser.parseTransactionInfoHistory(width, height);
|
||||||
|
// qDebug().noquote().nospace() << convertTransactionToString(trInfo);
|
||||||
|
|
||||||
|
// Если начинается с ЕСПП - то не кликаем еще, ждет обработку
|
||||||
|
// А все переводы по карте - они не начинаются со слова "Перевод" ->
|
||||||
|
// .. пробуем кликать, но если не кликается ???
|
||||||
|
QList<TransactionInfo> transactions = xmlScreenParser.parseTodayAndYesterdayHistory();
|
||||||
|
for (const TransactionInfo &info: transactions) {
|
||||||
|
QString text = QString("%1 %2").arg(info.shortDescription, getPriceAsString(info.amount));
|
||||||
|
qDebug().noquote().nospace() << convertTransactionToString(info);
|
||||||
|
|
||||||
|
// Node trButton = xmlScreenParser.findButtonNode(text, true);
|
||||||
|
Node trButton = swipeToButton(xmlScreenParser, deviceId, text, index, width, height);
|
||||||
|
if (!trButton.isEmpty()) {
|
||||||
|
AdbUtils::makeTap(deviceId, trButton.x(), trButton.y());
|
||||||
|
QThread::msleep(500);
|
||||||
|
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||||
|
|
||||||
|
Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false);
|
||||||
|
if (!moreDetailBtn.isEmpty()) {
|
||||||
|
AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y());
|
||||||
|
QThread::msleep(200);
|
||||||
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||||
|
TransactionInfo info = xmlScreenParser.parseTransactionInfoHistory(width, height);
|
||||||
|
qDebug().noquote().nospace() << convertTransactionToString(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdbUtils::goBack(deviceId);
|
||||||
|
} else {
|
||||||
|
qWarning() << "--- trButton not found..........";
|
||||||
|
}
|
||||||
|
// Перевод ВЛАДИСЛАВ НИКОЛАЕВИЧ К** в Т-Банк через СБП, по номеру телефона +7985553... Минус 10 рублей 0 копеек
|
||||||
|
}
|
||||||
|
|
||||||
xmlScreenParser.test();
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int payCounter = 0;
|
int payCounter = 0;
|
||||||
@ -294,10 +374,8 @@ void HomeScreenScript::start() {
|
|||||||
|
|
||||||
if (!paymentCompleted.isEmpty()) {
|
if (!paymentCompleted.isEmpty()) {
|
||||||
// Если вдруг исполнен
|
// Если вдруг исполнен
|
||||||
|
|
||||||
} else if (!paymentInProgress.isEmpty()) {
|
} else if (!paymentInProgress.isEmpty()) {
|
||||||
// Если все еще в ожидании
|
// Если все еще в ожидании
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "--- paymentCompleted or paymentInProgress not found...";
|
qWarning() << "--- paymentCompleted or paymentInProgress not found...";
|
||||||
}
|
}
|
||||||
|
|||||||
55
android/rshb/PayByPhoneScript.cpp
Normal file
55
android/rshb/PayByPhoneScript.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "PayByPhoneScript.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "DatabaseManager.h"
|
||||||
|
#include "dao/AccountDAO.h"
|
||||||
|
#include "dao/ApplicationDAO.h"
|
||||||
|
#include "dao/DeviceDAO.h"
|
||||||
|
#include "dao/TransactionDAO.h"
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
|
#include "db/DeviceInfo.h"
|
||||||
|
|
||||||
|
PayByPhoneScript::PayByPhoneScript(
|
||||||
|
AccountInfo account,
|
||||||
|
QString phone,
|
||||||
|
QString bankName,
|
||||||
|
const double amount,
|
||||||
|
QObject *parent
|
||||||
|
) : CommonScript(parent),
|
||||||
|
m_account(std::move(account)), m_phone(std::move(phone)),
|
||||||
|
m_bankName(std::move(bankName)), m_amount(amount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
PayByPhoneScript::~PayByPhoneScript() = default;
|
||||||
|
|
||||||
|
void PayByPhoneScript::doStart() {
|
||||||
|
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||||
|
const ApplicationInfo app = ApplicationDAO::getApplicationsByDeviceId(m_account.deviceId, m_account.appName);
|
||||||
|
|
||||||
|
if (device.id.isEmpty() || app.id.isEmpty()) {
|
||||||
|
qDebug() << "Device or app not found....";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_width = device.screenWidth;
|
||||||
|
m_height = device.screenHeight;
|
||||||
|
m_pinCode = app.pinCode;
|
||||||
|
m_packageName = app.package;
|
||||||
|
|
||||||
|
if (runAppAndGoToHomeScreen()) {
|
||||||
|
if (goToAllProducts()) {
|
||||||
|
QList<AccountInfo> accounts = xmlScreenParser.parseAccountsInfo();
|
||||||
|
for (AccountInfo &account: accounts) {
|
||||||
|
account.deviceId = m_account.deviceId;
|
||||||
|
account.appName = m_account.appName;
|
||||||
|
if (!AccountDAO::upsertAccount(account)) {
|
||||||
|
qDebug() << "Not updated: " << account.lastNumbers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Не смогли открыть 'Все продукты'";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Не смогли дойти до домашней страницы....";
|
||||||
|
}
|
||||||
|
}
|
||||||
27
android/rshb/PayByPhoneScript.h
Normal file
27
android/rshb/PayByPhoneScript.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CommonScript.h"
|
||||||
|
|
||||||
|
class PayByPhoneScript final : public CommonScript {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PayByPhoneScript(
|
||||||
|
AccountInfo account,
|
||||||
|
QString phone,
|
||||||
|
QString bankName,
|
||||||
|
double amount,
|
||||||
|
QObject *parent = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
~PayByPhoneScript() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void doStart() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const AccountInfo m_account;
|
||||||
|
const QString m_phone;
|
||||||
|
const QString m_bankName;
|
||||||
|
const double m_amount;
|
||||||
|
};
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "android/xml/Node.h"
|
#include "android/xml/Node.h"
|
||||||
|
#include "db/AccountInfo.h"
|
||||||
#include "db/TransactionInfo.h"
|
#include "db/TransactionInfo.h"
|
||||||
|
|
||||||
ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) {
|
ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) {
|
||||||
@ -21,6 +22,7 @@ ScreenXmlParser::~ScreenXmlParser() = default;
|
|||||||
|
|
||||||
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
||||||
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
|
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
|
||||||
|
static const QRegularExpression onlyDigitsAndDotRegex(R"([^0-9.])");
|
||||||
|
|
||||||
static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
|
static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
|
||||||
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
|
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
|
||||||
@ -31,6 +33,8 @@ static const QRegularExpression reDate(R"((?:Дата операции|дата)
|
|||||||
static const QRegularExpression reSender(R"(от ([^\(]+))");
|
static const QRegularExpression reSender(R"(от ([^\(]+))");
|
||||||
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");
|
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");
|
||||||
|
|
||||||
|
static const QRegularExpression reCardData(R"(цифрами\s+(\d\s\d\s\d\s\d).*?Сумма:\s([\d\s]+)\sруб)");
|
||||||
|
|
||||||
Node ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) {
|
Node ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) {
|
||||||
// 1. Ищем push banner - он перекрывает весь экран
|
// 1. Ищем push banner - он перекрывает весь экран
|
||||||
Node first = m_nodes[0];
|
Node first = m_nodes[0];
|
||||||
@ -106,7 +110,6 @@ Node ScreenXmlParser::findFirstEditText() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
||||||
for (const Node &node: m_nodes) {
|
for (const Node &node: m_nodes) {
|
||||||
if (contains) {
|
if (contains) {
|
||||||
@ -122,7 +125,6 @@ Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool ScreenXmlParser::isHomeScreen() {
|
bool ScreenXmlParser::isHomeScreen() {
|
||||||
// Есть заголовок "Счета и карты" и кнопка "Все продукты"
|
// Есть заголовок "Счета и карты" и кнопка "Все продукты"
|
||||||
Node text;
|
Node text;
|
||||||
@ -268,17 +270,25 @@ QString getMonthAsString(const int month) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString findSubInfo(const QDomElement &elem) {
|
QDomElement findSubElement(const QDomElement &elem, const QString &index) {
|
||||||
if (elem.hasChildNodes()) {
|
if (elem.hasChildNodes()) {
|
||||||
QDomNodeList subInfoList = elem.childNodes();
|
QDomNodeList subInfoList = elem.childNodes();
|
||||||
for (QDomNode subInfoNode: subInfoList) {
|
for (QDomNode subInfoNode: subInfoList) {
|
||||||
if (!subInfoNode.isElement()) continue;
|
if (!subInfoNode.isElement()) continue;
|
||||||
QDomElement subInfo = subInfoNode.toElement();
|
QDomElement subInfo = subInfoNode.toElement();
|
||||||
if (subInfo.attribute("index") == "1") {
|
if (subInfo.attribute("index") == index) {
|
||||||
return subInfo.attribute("text");
|
return subInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString findSubInfo(const QDomElement &elem, const QString &index) {
|
||||||
|
QDomElement subInfo = findSubElement(elem, index);
|
||||||
|
if (!subInfo.isNull()) {
|
||||||
|
return subInfo.attribute("text");
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,19 +357,19 @@ void traverseNodes(const QDomNode &node) {
|
|||||||
QString index = infoElement.attribute("index");
|
QString index = infoElement.attribute("index");
|
||||||
switch (index.toInt()) {
|
switch (index.toInt()) {
|
||||||
case 2:
|
case 2:
|
||||||
qDebug() << "Дата операции:" << findSubInfo(infoElement);
|
qDebug() << "Дата операции:" << findSubInfo(infoElement, "1");
|
||||||
continue;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
qDebug() << "Название:" << findSubInfo(infoElement);
|
qDebug() << "Название:" << findSubInfo(infoElement, "1");
|
||||||
qDebug() << "-------------------------------------";
|
qDebug() << "-------------------------------------";
|
||||||
// parseTransfer(findSubInfo(infoElement));
|
// parseTransfer(findSubInfo(infoElement));
|
||||||
continue;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
qDebug() << "Комиссия:" << findSubInfo(infoElement);;
|
qDebug() << "Комиссия:" << findSubInfo(infoElement, "1");
|
||||||
continue;
|
break;
|
||||||
default:
|
default:
|
||||||
qDebug() << "Прочее:" << findSubInfo(infoElement);;
|
qDebug() << "Прочее:" << findSubInfo(infoElement, "1");
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -661,15 +671,15 @@ TransactionInfo parseExpandedInfoHistory(const QDomElement &root) {
|
|||||||
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
|
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
|
||||||
case 2:
|
case 2:
|
||||||
// Дата операции
|
// Дата операции
|
||||||
transaction.bankTime = findSubInfo(e);
|
transaction.bankTime = findSubInfo(e, "1");
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
transaction.description = findSubInfo(e);
|
transaction.description = findSubInfo(e, "1");
|
||||||
parseTransferInfo(transaction.description, transaction);
|
parseTransferInfo(transaction.description, transaction);
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
// Комиссия
|
// Комиссия
|
||||||
transaction.fee = findSubInfo(e).remove(onlyDigitsRegex).toFloat();
|
transaction.fee = findSubInfo(e, "1").remove(onlyDigitsRegex).toFloat();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// qDebug() << "Прочее:" << findSubInfo(e);
|
// qDebug() << "Прочее:" << findSubInfo(e);
|
||||||
@ -705,9 +715,90 @@ TransactionInfo ScreenXmlParser::parseTransactionInfoHistory(int screenWidth, in
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionInfo parseExpandedInfo(const QDomElement &root) {
|
||||||
|
if (root.isElement()) {
|
||||||
|
QDomElement elem = root.toElement();
|
||||||
|
if (elem.tagName() == "node"
|
||||||
|
&& elem.attribute("class") == "android.widget.Button"
|
||||||
|
&& elem.attribute("text") == "Детали операции"
|
||||||
|
) {
|
||||||
|
TransactionInfo transaction;
|
||||||
|
// Все что ниже деталей операции
|
||||||
|
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < detailInfo.count(); ++i) {
|
||||||
|
QDomNode info = detailInfo.at(i);
|
||||||
|
if (!info.isElement()) continue;
|
||||||
|
|
||||||
|
QDomElement infoElement = info.toElement();
|
||||||
|
QString index = infoElement.attribute("index");
|
||||||
|
QDomElement ee;
|
||||||
|
switch (index.toInt()) {
|
||||||
|
case 3:
|
||||||
|
transaction.bankTime = findSubInfo(infoElement, "1");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ee = findSubElement(infoElement, "0");
|
||||||
|
ee = findSubElement(ee, "1");
|
||||||
|
transaction.phone = findSubInfo(ee, "0").remove(onlyDigitsRegex);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
transaction.name = findSubInfo(infoElement, "1");
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
transaction.bankName = findSubInfo(infoElement, "1");
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
transaction.fee = findSubInfo(infoElement, "1").remove(onlyDigitsAndDotRegex).toFloat();
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
ee = findSubElement(infoElement, "1");
|
||||||
|
transaction.trExternalId = findSubInfo(ee, "1");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рекурсивно обходим всех детей
|
||||||
|
QDomNode child = root.firstChild();
|
||||||
|
while (!child.isNull()) {
|
||||||
|
if (TransactionInfo info = parseExpandedInfo(child.toElement()); !info.bankTime.isEmpty()) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
child = child.nextSibling();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<AccountInfo> ScreenXmlParser::parseAccountsInfo() {
|
||||||
|
QList<AccountInfo> accounts;
|
||||||
|
for (const Node &node: m_nodes) {
|
||||||
|
if (node.text.contains("Номер карты с последними цифрами")) {
|
||||||
|
if (QRegularExpressionMatch match = reCardData.match(node.text); match.hasMatch()) {
|
||||||
|
AccountInfo info;
|
||||||
|
info.lastNumbers = match.captured(1).remove(" ");
|
||||||
|
info.amount = match.captured(2).remove(" ").toFloat();
|
||||||
|
info.description = node.text.mid(0, node.text.indexOf("Номер карты"));
|
||||||
|
info.updateTime = QDateTime::currentDateTime();
|
||||||
|
accounts.append(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
void ScreenXmlParser::test() {
|
void ScreenXmlParser::test() {
|
||||||
return;
|
// TransactionInfo info2 = parseExpandedInfo(m_xml);
|
||||||
QFile file("assets/rshb/test/spb_test_send.xml");
|
// qDebug().noquote().nospace() << convertTransactionToString(info2);
|
||||||
|
// return;
|
||||||
|
|
||||||
|
QFile file("assets/rshb/all_products.xml");
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
qDebug() << "Cannot open file";
|
qDebug() << "Cannot open file";
|
||||||
}
|
}
|
||||||
@ -720,6 +811,13 @@ void ScreenXmlParser::test() {
|
|||||||
|
|
||||||
qDebug() << "Start parsing XML:";
|
qDebug() << "Start parsing XML:";
|
||||||
|
|
||||||
|
// Номер карты с последними цифрами
|
||||||
QDomElement root = doc.documentElement();
|
QDomElement root = doc.documentElement();
|
||||||
|
parseAndSaveXml(doc.toString());
|
||||||
|
for (const AccountInfo& info : parseAccountsInfo()) {
|
||||||
|
qDebug().noquote().nospace() << convertAccountToString(info);
|
||||||
|
}
|
||||||
|
// TransactionInfo info = parseExpandedInfo(root);
|
||||||
|
// qDebug().noquote().nospace() << convertTransactionToString(info);
|
||||||
// ЕСПП
|
// ЕСПП
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include "android/xml/Node.h"
|
#include "android/xml/Node.h"
|
||||||
|
#include "db/AccountInfo.h"
|
||||||
#include "db/TransactionInfo.h"
|
#include "db/TransactionInfo.h"
|
||||||
|
|
||||||
class ScreenXmlParser final : public QObject {
|
class ScreenXmlParser final : public QObject {
|
||||||
@ -36,6 +37,8 @@ public:
|
|||||||
TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight);
|
TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight);
|
||||||
QList<TransactionInfo> parseTodayAndYesterdayHistory();
|
QList<TransactionInfo> parseTodayAndYesterdayHistory();
|
||||||
|
|
||||||
|
QList<AccountInfo> parseAccountsInfo();
|
||||||
|
|
||||||
void test();
|
void test();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -1,26 +1,64 @@
|
|||||||
#include "DatabaseManager.h"
|
#include "DatabaseManager.h"
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QtCore/qcoreapplication.h>
|
#include <QDebug>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
DatabaseManager::DatabaseManager() {
|
DatabaseManager::DatabaseManager() = default;
|
||||||
db = QSqlDatabase::addDatabase("QSQLITE");
|
|
||||||
db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
|
|
||||||
if (!db.open()) {
|
|
||||||
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseManager::~DatabaseManager() {
|
DatabaseManager::~DatabaseManager() {
|
||||||
if (db.isOpen()) {
|
QMutexLocker locker(&mutex);
|
||||||
db.close();
|
|
||||||
|
for (auto it = connectionPool.begin(); it != connectionPool.end(); ++it) {
|
||||||
|
QSqlDatabase::removeDatabase(it.key());
|
||||||
}
|
}
|
||||||
|
qDebug() << "Все соединения закрыты";
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseManager& DatabaseManager::instance() {
|
DatabaseManager &DatabaseManager::instance() {
|
||||||
static DatabaseManager instance;
|
static DatabaseManager instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlDatabase& DatabaseManager::database() {
|
QSqlDatabase DatabaseManager::database() {
|
||||||
return db;
|
QMutexLocker locker(&mutex);
|
||||||
}
|
QString name = connectionName();
|
||||||
|
|
||||||
|
// Создаем новое соединение, если его ещё нет
|
||||||
|
if (!connectionPool.contains(name)) {
|
||||||
|
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", name);
|
||||||
|
db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
|
||||||
|
|
||||||
|
if (!db.open()) {
|
||||||
|
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionPool[name] = db;
|
||||||
|
|
||||||
|
// Автоматическое удаление соединения при завершении потока
|
||||||
|
QObject::connect(QThread::currentThread(), &QThread::finished, [this, name]() {
|
||||||
|
QMutexLocker cLocker(&mutex);
|
||||||
|
if (connectionPool.contains(name)) {
|
||||||
|
connectionPool.remove(name);
|
||||||
|
QSqlDatabase::removeDatabase(name);
|
||||||
|
qDebug() << "Соединение" << name << "удалено";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionPool[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseManager::connectionName() {
|
||||||
|
return QString("connection_%1").arg(reinterpret_cast<quintptr>(QThread::currentThreadId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DatabaseManager::closeConnection() {
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
if (const QString name = connectionName(); connectionPool.contains(name)) {
|
||||||
|
connectionPool.remove(name);
|
||||||
|
QSqlDatabase::removeDatabase(name);
|
||||||
|
qDebug() << "Соединение" << name << "удалено вручную";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
|
#include <QString>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
class DatabaseManager final : public QObject {
|
class DatabaseManager {
|
||||||
Q_OBJECT
|
|
||||||
public:
|
public:
|
||||||
static DatabaseManager& instance();
|
static DatabaseManager& instance();
|
||||||
|
|
||||||
QSqlDatabase& database();
|
QSqlDatabase database();
|
||||||
|
void closeConnection();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DatabaseManager();
|
DatabaseManager();
|
||||||
~DatabaseManager() override;
|
~DatabaseManager();
|
||||||
QSqlDatabase db;
|
|
||||||
|
static QString connectionName();
|
||||||
|
|
||||||
|
QMutex mutex;
|
||||||
|
QSet<QString> activeConnections;
|
||||||
|
QMap<QString, QSqlDatabase> connectionPool;
|
||||||
};
|
};
|
||||||
Binary file not shown.
@ -9,9 +9,9 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
INSERT INTO accounts (device_id, app_name, card_name, amount, update_time, status, description)
|
INSERT INTO accounts (device_id, app_name, numbers, last_numbers, amount, update_time, status, description)
|
||||||
VALUES (:device_id, :app_name, :card_name, :amount, :update_time, :status, :description)
|
VALUES (:device_id, :app_name, :numbers, :last_numbers, :amount, :update_time, :status, :description)
|
||||||
ON CONFLICT(device_id, app_name, card_name)
|
ON CONFLICT(device_id, app_name, last_numbers)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
amount = excluded.amount,
|
amount = excluded.amount,
|
||||||
update_time = excluded.update_time,
|
update_time = excluded.update_time,
|
||||||
@ -20,10 +20,11 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
|||||||
|
|
||||||
query.bindValue(":device_id", info.deviceId);
|
query.bindValue(":device_id", info.deviceId);
|
||||||
query.bindValue(":app_name", info.appName);
|
query.bindValue(":app_name", info.appName);
|
||||||
query.bindValue(":card_name", info.cardName);
|
query.bindValue(":numbers", info.numbers);
|
||||||
|
query.bindValue(":last_numbers", info.lastNumbers);
|
||||||
query.bindValue(":amount", info.amount);
|
query.bindValue(":amount", info.amount);
|
||||||
query.bindValue(":update_time", info.updateTime.toString(Qt::ISODate));
|
query.bindValue(":update_time", info.updateTime.toString(Qt::ISODate));
|
||||||
query.bindValue(":status", info.status);
|
query.bindValue(":status", accountStatusToString(info.status));
|
||||||
query.bindValue(":description", info.description);
|
query.bindValue(":description", info.description);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
@ -46,7 +47,7 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_name, card_name, amount, update_time, status, description
|
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||||
FROM accounts
|
FROM accounts
|
||||||
WHERE device_id = :device_id AND app_name = :app_name
|
WHERE device_id = :device_id AND app_name = :app_name
|
||||||
)");
|
)");
|
||||||
@ -64,10 +65,11 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
|||||||
acc.id = query.value("id").toInt();
|
acc.id = query.value("id").toInt();
|
||||||
acc.deviceId = query.value("device_id").toString();
|
acc.deviceId = query.value("device_id").toString();
|
||||||
acc.appName = query.value("app_name").toString();
|
acc.appName = query.value("app_name").toString();
|
||||||
acc.cardName = query.value("card_name").toString();
|
acc.numbers = query.value("numbers").toString();
|
||||||
|
acc.lastNumbers = query.value("last_numbers").toString();
|
||||||
acc.amount = query.value("amount").toDouble();
|
acc.amount = query.value("amount").toDouble();
|
||||||
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||||
acc.status = query.value("status").toString();
|
acc.status = accountStatusFromString(query.value("status").toString());
|
||||||
acc.description = query.value("description").toString();
|
acc.description = query.value("description").toString();
|
||||||
accounts.append(acc);
|
accounts.append(acc);
|
||||||
}
|
}
|
||||||
@ -75,12 +77,25 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
|||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
AccountInfo parseAccount(const QSqlQuery &query) {
|
||||||
AccountInfo acc;
|
AccountInfo acc;
|
||||||
|
acc.id = query.value("id").toInt();
|
||||||
|
acc.deviceId = query.value("device_id").toString();
|
||||||
|
acc.appName = query.value("app_name").toString();
|
||||||
|
acc.lastNumbers = query.value("last_numbers").toString();
|
||||||
|
acc.numbers = query.value("numbers").toString();
|
||||||
|
acc.amount = query.value("amount").toDouble();
|
||||||
|
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||||
|
acc.status = accountStatusFromString(query.value("status").toString());
|
||||||
|
acc.description = query.value("description").toString();
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_name, card_name, amount, update_time, status, description
|
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||||
FROM accounts
|
FROM accounts
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@ -90,19 +105,36 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
|||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();
|
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();
|
||||||
return acc;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.next()) {
|
if (query.next()) {
|
||||||
acc.id = query.value("id").toInt();
|
return parseAccount(query);
|
||||||
acc.deviceId = query.value("device_id").toString();
|
}
|
||||||
acc.appName = query.value("app_name").toString();
|
return {};
|
||||||
acc.cardName = query.value("card_name").toString();
|
}
|
||||||
acc.amount = query.value("amount").toDouble();
|
|
||||||
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
AccountInfo AccountDAO::findAppAccount(const QString &appName, const QString &cardNumber) {
|
||||||
acc.status = query.value("status").toString();
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
acc.description = query.value("description").toString();
|
|
||||||
|
query.prepare(R"(
|
||||||
|
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||||
|
FROM accounts
|
||||||
|
WHERE app_name = :app_name AND numbers = :numbers
|
||||||
|
LIMIT 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":app_name", appName);
|
||||||
|
query.bindValue(":numbers", cardNumber);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
if (query.next()) {
|
||||||
|
return parseAccount(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,4 +10,6 @@ public:
|
|||||||
[[nodiscard]] static QList<AccountInfo> getAccounts(const QString &deviceId, const QString &appName);
|
[[nodiscard]] static QList<AccountInfo> getAccounts(const QString &deviceId, const QString &appName);
|
||||||
|
|
||||||
[[nodiscard]] static AccountInfo getAccountById(int accountId);
|
[[nodiscard]] static AccountInfo getAccountById(int accountId);
|
||||||
|
|
||||||
|
[[nodiscard]] static AccountInfo findAppAccount(const QString &appName, const QString &cardNumber);
|
||||||
};
|
};
|
||||||
|
|||||||
94
database/dao/ApplicationDAO.cpp
Normal file
94
database/dao/ApplicationDAO.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QVariant>
|
||||||
|
#include "ApplicationDAO.h"
|
||||||
|
#include "DatabaseManager.h"
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
|
|
||||||
|
bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
INSERT INTO applications (id, device_id, pin_code, name, package, status, comment, image)
|
||||||
|
VALUES (:id, :device_id, :pin_code, :name, :package, :status, :comment, :image)
|
||||||
|
ON CONFLICT(device_id, name)
|
||||||
|
DO UPDATE SET
|
||||||
|
device_id = excluded.device_id,
|
||||||
|
pin_code = excluded.pin_code,
|
||||||
|
name = excluded.name,
|
||||||
|
package = excluded.package,
|
||||||
|
status = excluded.status,
|
||||||
|
comment = excluded.comment,
|
||||||
|
image = excluded.image
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":id", info.id);
|
||||||
|
query.bindValue(":device_id", info.deviceId);
|
||||||
|
query.bindValue(":pin_code", info.pinCode);
|
||||||
|
query.bindValue(":name", info.name);
|
||||||
|
query.bindValue(":package", info.package);
|
||||||
|
query.bindValue(":status", info.status);
|
||||||
|
query.bindValue(":comment", info.comment);
|
||||||
|
query.bindValue(":image", info.image);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
UPDATE applications
|
||||||
|
SET pin_code = :pin_code
|
||||||
|
WHERE id = :id
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":pin_code", pinCode);
|
||||||
|
query.bindValue(":id", appId);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationInfo ApplicationDAO::getApplicationsByDeviceId(const QString &deviceId, const QString &appName) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
SELECT id, device_id, pin_code, name, package, status, comment, image
|
||||||
|
FROM applications
|
||||||
|
WHERE device_id = :device_id AND name = :name
|
||||||
|
LIMIT 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":device_id", deviceId);
|
||||||
|
query.bindValue(":name", appName);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при получении приложений по deviceId:" << query.lastError().text();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (query.next()) {
|
||||||
|
ApplicationInfo app;
|
||||||
|
app.id = query.value("id").toString();
|
||||||
|
app.deviceId = query.value("device_id").toString();
|
||||||
|
app.pinCode = query.value("pin_code").toString();
|
||||||
|
app.name = query.value("name").toString();
|
||||||
|
app.package = query.value("package").toString();
|
||||||
|
app.status = query.value("status").toString();
|
||||||
|
app.comment = query.value("comment").toString();
|
||||||
|
app.image = query.value("image").toByteArray();
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
12
database/dao/ApplicationDAO.h
Normal file
12
database/dao/ApplicationDAO.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
|
|
||||||
|
class ApplicationDAO {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] static bool upsertApplication(const ApplicationInfo &info);
|
||||||
|
|
||||||
|
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
||||||
|
|
||||||
|
[[nodiscard]] static ApplicationInfo getApplicationsByDeviceId(const QString &deviceId, const QString &appName);
|
||||||
|
};
|
||||||
@ -4,6 +4,20 @@
|
|||||||
#include "DatabaseManager.h"
|
#include "DatabaseManager.h"
|
||||||
#include "db/DeviceInfo.h"
|
#include "db/DeviceInfo.h"
|
||||||
|
|
||||||
|
DeviceInfo parseDevice(const QSqlQuery &query) {
|
||||||
|
DeviceInfo device;
|
||||||
|
device.id = query.value("id").toString();
|
||||||
|
device.status = query.value("status").toString();
|
||||||
|
device.name = query.value("name").toString();
|
||||||
|
device.android = query.value("android").toString();
|
||||||
|
device.screenWidth = query.value("screen_width").toInt();
|
||||||
|
device.screenHeight = query.value("screen_height").toInt();
|
||||||
|
device.battery = query.value("battery").toInt();
|
||||||
|
device.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||||
|
device.image = query.value("image").toByteArray();
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
||||||
QList<DeviceInfo> devices;
|
QList<DeviceInfo> devices;
|
||||||
|
|
||||||
@ -24,23 +38,34 @@ QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (query.next()) {
|
while (query.next()) {
|
||||||
DeviceInfo device;
|
devices.append(parseDevice(query));
|
||||||
device.id = query.value("id").toString();
|
|
||||||
device.status = query.value("status").toString();
|
|
||||||
device.name = query.value("name").toString();
|
|
||||||
device.android = query.value("android").toString();
|
|
||||||
device.screenWidth = query.value("screen_width").toInt();
|
|
||||||
device.screenHeight = query.value("screen_height").toInt();
|
|
||||||
device.battery = query.value("battery").toInt();
|
|
||||||
device.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
|
||||||
device.image = query.value("image").toByteArray();
|
|
||||||
|
|
||||||
devices.append(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
SELECT id, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||||
|
FROM devices
|
||||||
|
WHERE id = :deviceId
|
||||||
|
)");
|
||||||
|
query.bindValue(":deviceId", deviceId);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при получении устройства:" << query.lastError().text();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (query.next()) {
|
||||||
|
return parseDevice(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
class DeviceDAO {
|
class DeviceDAO {
|
||||||
public:
|
public:
|
||||||
|
[[nodiscard]] static DeviceInfo getDeviceById(const QString &deviceId);
|
||||||
|
|
||||||
[[nodiscard]] static bool upsertDevice(const DeviceInfo &device);
|
[[nodiscard]] static bool upsertDevice(const DeviceInfo &device);
|
||||||
|
|
||||||
[[nodiscard]] static bool markDevicesOfflineExcept(const QStringList &onlineDeviceIds);
|
[[nodiscard]] static bool markDevicesOfflineExcept(const QStringList &onlineDeviceIds);
|
||||||
|
|||||||
43
main.cpp
43
main.cpp
@ -16,6 +16,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include "rshb/HomeScreenScript.h"
|
#include "rshb/HomeScreenScript.h"
|
||||||
|
#include "rshb/PayByPhoneScript.h"
|
||||||
|
|
||||||
void setupWorkerAndThread(QCoreApplication &app) {
|
void setupWorkerAndThread(QCoreApplication &app) {
|
||||||
auto *thread = new QThread;
|
auto *thread = new QThread;
|
||||||
@ -48,26 +49,30 @@ void setupWorkerAndThread(QCoreApplication &app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
|
void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
|
||||||
auto *thread = new QThread;
|
const QString appName = "rshb";
|
||||||
auto *scriptWorker = new HomeScreenScript(0);
|
const QString cardNumber = "2200380317107183";
|
||||||
|
|
||||||
// Перемещаем worker в новый поток thread.
|
const QString phoneNumber = "79641815146";
|
||||||
// Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоновом потоке.
|
const QString bankName = "Альфа-Банк";
|
||||||
scriptWorker->moveToThread(thread);
|
const double ammount = 250;
|
||||||
// Когда поток стартует, запускаем start()
|
|
||||||
QObject::connect(thread, &QThread::started, scriptWorker, &HomeScreenScript::start);
|
|
||||||
// Когда работа (worker) завершена (emit finished()), останавливаем поток
|
|
||||||
QObject::connect(scriptWorker, &HomeScreenScript::finished, thread, &QThread::quit);
|
|
||||||
// Удаляем работу (worker) (deleteLater)
|
|
||||||
QObject::connect(scriptWorker, &HomeScreenScript::finished, scriptWorker, &QObject::deleteLater);
|
|
||||||
// Удаляем поток (deleteLater)
|
|
||||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber);
|
||||||
scriptWorker->stop();
|
if (account.id != -1) {
|
||||||
});
|
auto *thread = new QThread;
|
||||||
|
auto *scriptWorker = new PayByPhoneScript(account, phoneNumber, bankName, ammount);
|
||||||
|
|
||||||
thread->start();
|
scriptWorker->moveToThread(thread);
|
||||||
|
QObject::connect(thread, &QThread::started, scriptWorker, &PayByPhoneScript::start);
|
||||||
|
QObject::connect(scriptWorker, &PayByPhoneScript::finished, thread, &QThread::quit);
|
||||||
|
QObject::connect(scriptWorker, &PayByPhoneScript::finished, scriptWorker, &QObject::deleteLater);
|
||||||
|
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
||||||
|
scriptWorker->stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -98,7 +103,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// qDebug() << "Сохранившееся время:" << savedTime.toString();
|
// qDebug() << "Сохранившееся время:" << savedTime.toString();
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
MainWindow window;
|
// MainWindow window;
|
||||||
window.show();
|
// window.show();
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,24 +18,27 @@ CREATE TABLE IF NOT EXISTS applications
|
|||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
pin_code TEXT,
|
pin_code TEXT,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
|
package TEXT,
|
||||||
status TEXT,
|
status TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
image BLOB,
|
image BLOB,
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
|
UNIQUE (device_id, name)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS accounts
|
CREATE TABLE IF NOT EXISTS accounts
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
app_name TEXT,
|
app_name TEXT,
|
||||||
card_name TEXT,
|
numbers TEXT,
|
||||||
description TEXT,
|
last_numbers TEXT,
|
||||||
amount REAL,
|
description TEXT,
|
||||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
amount REAL,
|
||||||
status TEXT,
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
status TEXT,
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
UNIQUE (device_id, app_name, card_name)
|
UNIQUE (device_id, app_name, last_numbers)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS transactions
|
CREATE TABLE IF NOT EXISTS transactions
|
||||||
|
|||||||
@ -2,13 +2,58 @@
|
|||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
|
enum class AccountStatus {
|
||||||
|
Active,
|
||||||
|
Blocked
|
||||||
|
};
|
||||||
|
|
||||||
|
inline QString accountStatusToString(const AccountStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case AccountStatus::Active: return "active";
|
||||||
|
case AccountStatus::Blocked: return "blocked";
|
||||||
|
default: return "active";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline AccountStatus accountStatusFromString(const QString &str) {
|
||||||
|
if (str == "active") return AccountStatus::Active;
|
||||||
|
if (str == "blocked") return AccountStatus::Blocked;
|
||||||
|
return AccountStatus::Active;
|
||||||
|
}
|
||||||
|
|
||||||
struct AccountInfo {
|
struct AccountInfo {
|
||||||
int id;
|
int id = -1;
|
||||||
QString deviceId;
|
QString deviceId;
|
||||||
QString appName;
|
QString appName;
|
||||||
QString cardName;
|
QString lastNumbers;
|
||||||
double amount;
|
QString numbers;
|
||||||
|
double amount = 0.0;
|
||||||
QDateTime updateTime;
|
QDateTime updateTime;
|
||||||
QString status;
|
AccountStatus status = AccountStatus::Active;
|
||||||
QString description;
|
QString description;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline QString convertAccountToString(const AccountInfo &info) {
|
||||||
|
QStringList lines;
|
||||||
|
QStringList emptyFields;
|
||||||
|
|
||||||
|
lines << "AccountInfo {";
|
||||||
|
|
||||||
|
if (info.id != -1) lines << " id: " + QString::number(info.id); else emptyFields << "id";
|
||||||
|
if (!info.deviceId.isEmpty()) lines << " deviceId: " + info.deviceId; else emptyFields << "deviceId";
|
||||||
|
if (!info.appName.isEmpty()) lines << " appName: " + info.appName; else emptyFields << "appName";
|
||||||
|
if (!info.numbers.isEmpty()) lines << " numbers: " + info.numbers; else emptyFields << "numbers";
|
||||||
|
if (!info.lastNumbers.isEmpty()) lines << " lastNumbers: " + info.lastNumbers; else emptyFields << "lastNumbers";
|
||||||
|
if (info.amount != 0.0) lines << " amount: " + QString::number(info.amount, 'f', 2); else emptyFields << "amount";
|
||||||
|
if (info.updateTime.isValid()) lines << " updateTime: " + info.updateTime.toString(Qt::ISODate); else emptyFields << "updateTime";
|
||||||
|
if (info.status != AccountStatus::Active) lines << " status: " + accountStatusToString(info.status); else emptyFields << "status";
|
||||||
|
if (!info.description.isEmpty()) lines << " description: " + info.description; else emptyFields << "description";
|
||||||
|
|
||||||
|
if (!emptyFields.isEmpty()) {
|
||||||
|
lines << " --- empty: " + emptyFields.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines << "}";
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|||||||
17
models/db/ApplicationInfo.h
Normal file
17
models/db/ApplicationInfo.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
struct ApplicationInfo {
|
||||||
|
QString id;
|
||||||
|
QString deviceId;
|
||||||
|
QString pinCode;
|
||||||
|
QString name;
|
||||||
|
QString package;
|
||||||
|
QString status;
|
||||||
|
QString comment;
|
||||||
|
QByteArray image;
|
||||||
|
QDateTime updateTime;
|
||||||
|
};
|
||||||
@ -7,9 +7,9 @@ struct DeviceInfo {
|
|||||||
QString status;
|
QString status;
|
||||||
QString name;
|
QString name;
|
||||||
QString android;
|
QString android;
|
||||||
int screenWidth;
|
int screenWidth = 0;
|
||||||
int screenHeight;
|
int screenHeight = 0;
|
||||||
int battery;
|
int battery = 0;
|
||||||
QDateTime updateTime;
|
QDateTime updateTime;
|
||||||
QByteArray image;
|
QByteArray image;
|
||||||
};
|
};
|
||||||
@ -71,43 +71,34 @@ struct TransactionInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline QString convertTransactionToString(const TransactionInfo &tx) {
|
inline QString convertTransactionToString(const TransactionInfo &tx) {
|
||||||
return QString(
|
QStringList lines;
|
||||||
"TransactionInfo {\n"
|
QStringList emptyFields;
|
||||||
" id: %1\n"
|
|
||||||
" accountId: %2\n"
|
lines << "TransactionInfo {";
|
||||||
" amount: %3\n"
|
|
||||||
" fee: %4\n"
|
if (tx.id != -1) lines << " id: " + QString::number(tx.id); else emptyFields << "id";
|
||||||
" status: %5\n"
|
if (tx.accountId != -1) lines << " accountId: " + QString::number(tx.accountId); else emptyFields << "accountId";
|
||||||
" type: %6\n"
|
if (tx.amount != 0.0) lines << " amount: " + QString::number(tx.amount); else emptyFields << "amount";
|
||||||
" phone: %7\n"
|
if (tx.fee != 0.0) lines << " fee: " + QString::number(tx.fee); else emptyFields << "fee";
|
||||||
" shortDescription: %8\n"
|
if (tx.status != TransactionStatus::Unknown) lines << " status: " + transactionStatusToString(tx.status); else emptyFields << "status";
|
||||||
" updatedShortDescription: %9\n"
|
if (tx.type != TransactionType::Unknown) lines << " type: " + transactionTypeToString(tx.type); else emptyFields << "type";
|
||||||
" description: %10\n"
|
if (!tx.phone.isEmpty()) lines << " phone: " + tx.phone; else emptyFields << "phone";
|
||||||
" updatedDescription: %11\n"
|
if (!tx.shortDescription.isEmpty()) lines << " shortDescription: " + tx.shortDescription; else emptyFields << "shortDescription";
|
||||||
" bankName: %12\n"
|
if (!tx.updatedShortDescription.isEmpty()) lines << " updatedShortDescription: " + tx.updatedShortDescription; else emptyFields << "updatedShortDescription";
|
||||||
" bankTime: %13\n"
|
if (!tx.description.isEmpty()) lines << " description: " + tx.description; else emptyFields << "description";
|
||||||
" name: %14\n"
|
if (!tx.updatedDescription.isEmpty()) lines << " updatedDescription: " + tx.updatedDescription; else emptyFields << "updatedDescription";
|
||||||
" trExternalId: %15\n"
|
if (!tx.bankName.isEmpty()) lines << " bankName: " + tx.bankName; else emptyFields << "bankName";
|
||||||
" updateTime: %16\n"
|
if (!tx.bankTime.isEmpty()) lines << " bankTime: " + tx.bankTime; else emptyFields << "bankTime";
|
||||||
" completeTime: %17\n"
|
if (!tx.name.isEmpty()) lines << " name: " + tx.name; else emptyFields << "name";
|
||||||
" timestamp: %18\n"
|
if (!tx.trExternalId.isEmpty()) lines << " trExternalId: " + tx.trExternalId; else emptyFields << "trExternalId";
|
||||||
"}"
|
if (tx.updateTime.isValid()) lines << " updateTime: " + tx.updateTime.toString(Qt::ISODate); else emptyFields << "updateTime";
|
||||||
).arg(tx.id)
|
if (tx.completeTime.isValid()) lines << " completeTime: " + tx.completeTime.toString(Qt::ISODate); else emptyFields << "completeTime";
|
||||||
.arg(tx.accountId)
|
if (tx.timestamp.isValid()) lines << " timestamp: " + tx.timestamp.toString(Qt::ISODate); else emptyFields << "timestamp";
|
||||||
.arg(tx.amount)
|
|
||||||
.arg(tx.fee)
|
if (!emptyFields.isEmpty()) {
|
||||||
.arg(transactionStatusToString(tx.status))
|
lines << " --- empty: " + emptyFields.join(", ");
|
||||||
.arg(transactionTypeToString(tx.type))
|
}
|
||||||
.arg(tx.phone)
|
lines << "}";
|
||||||
.arg(tx.shortDescription)
|
|
||||||
.arg(tx.updatedShortDescription)
|
return lines.join('\n');
|
||||||
.arg(tx.description)
|
|
||||||
.arg(tx.updatedDescription)
|
|
||||||
.arg(tx.bankName)
|
|
||||||
.arg(tx.bankTime)
|
|
||||||
.arg(tx.name)
|
|
||||||
.arg(tx.trExternalId)
|
|
||||||
.arg(tx.updateTime.toString(Qt::ISODate))
|
|
||||||
.arg(tx.completeTime.toString(Qt::ISODate))
|
|
||||||
.arg(tx.timestamp.toString(Qt::ISODate));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,13 +55,13 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
|||||||
|
|
||||||
for (int k = 0; k < accounts.size(); ++k) {
|
for (int k = 0; k < accounts.size(); ++k) {
|
||||||
AccountInfo account = accounts[k];
|
AccountInfo account = accounts[k];
|
||||||
QTableWidgetItem *item = new QTableWidgetItem("*" + account.cardName);
|
QTableWidgetItem *item = new QTableWidgetItem("*" + account.lastNumbers);
|
||||||
item->setData(Qt::UserRole, account.id);
|
item->setData(Qt::UserRole, account.id);
|
||||||
tableWidget->setItem(k, 0, item);
|
tableWidget->setItem(k, 0, item);
|
||||||
tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount)));
|
tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount)));
|
||||||
tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss")));
|
tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss")));
|
||||||
tableWidget->setItem(k, 3, new QTableWidgetItem(account.description));
|
tableWidget->setItem(k, 3, new QTableWidgetItem(account.description));
|
||||||
tableWidget->setItem(k, 4, new QTableWidgetItem(account.status));
|
tableWidget->setItem(k, 4, new QTableWidgetItem(accountStatusToString(account.status)));
|
||||||
totalHeight += tableWidget->rowHeight(k);
|
totalHeight += tableWidget->rowHeight(k);
|
||||||
}
|
}
|
||||||
tableWidget->setFixedHeight(totalHeight + 2);
|
tableWidget->setFixedHeight(totalHeight + 2);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
|||||||
|
|
||||||
AccountInfo account = AccountDAO::getAccountById(accountId);
|
AccountInfo account = AccountDAO::getAccountById(accountId);
|
||||||
|
|
||||||
QLabel *label = new QLabel(QString("*%1 | %2").arg(account.cardName, account.description), this);
|
QLabel *label = new QLabel(QString("*%1 | %2").arg(account.lastNumbers, account.description), this);
|
||||||
layout->addWidget(label);
|
layout->addWidget(label);
|
||||||
|
|
||||||
setModal(true);
|
setModal(true);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user