diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp index 38124fa..bad48ed 100644 --- a/android/adb/AdbUtils.cpp +++ b/android/adb/AdbUtils.cpp @@ -104,16 +104,18 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) { process.start("sh", {"-c", xml}); process.waitForFinished(); - // TODO делаем скрин, убрать потом - // 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' - QProcess processScreenshot; - QString screenshot = QString( - "adb -s %1 shell 'screencap -p /sdcard/screen.png' && " - "adb -s %1 pull /sdcard/screen.png screen_step_%2.png && " - "adb -s %1 shell 'rm /sdcard/screen.png'" - ).arg(deviceId, QString::number(idx)); - processScreenshot.start("sh", {"-c", screenshot}); - processScreenshot.waitForFinished(); + if (false) { + // TODO делаем скрин, убрать потом + // 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' + QProcess processScreenshot; + QString screenshot = QString( + "adb -s %1 shell 'screencap -p /sdcard/screen.png' && " + "adb -s %1 pull /sdcard/screen.png screen_step_%2.png && " + "adb -s %1 shell 'rm /sdcard/screen.png'" + ).arg(deviceId, QString::number(idx)); + processScreenshot.start("sh", {"-c", screenshot}); + processScreenshot.waitForFinished(); + } // чтобы убедиться, что мы прочитали без ошибок 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); } +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) { QProcess process; process.start("adb", { diff --git a/android/adb/AdbUtils.h b/android/adb/AdbUtils.h index 23c40de..2550cef 100644 --- a/android/adb/AdbUtils.h +++ b/android/adb/AdbUtils.h @@ -17,7 +17,9 @@ public: 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); diff --git a/android/rshb/CommonScript.cpp b/android/rshb/CommonScript.cpp new file mode 100644 index 0000000..41b2c75 --- /dev/null +++ b/android/rshb/CommonScript.cpp @@ -0,0 +1,230 @@ +#include "CommonScript.h" +#include +#include + +#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 &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 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; +} diff --git a/android/rshb/CommonScript.h b/android/rshb/CommonScript.h new file mode 100644 index 0000000..ded17f7 --- /dev/null +++ b/android/rshb/CommonScript.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#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 &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); +}; diff --git a/android/rshb/HomeScreenScript.cpp b/android/rshb/HomeScreenScript.cpp index 51e5cba..7a01f94 100644 --- a/android/rshb/HomeScreenScript.cpp +++ b/android/rshb/HomeScreenScript.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "ScreenXmlParser.h" @@ -112,6 +113,40 @@ bool HomeScreenScript::goToHomeScreen( } } +QString getPriceAsString(const double value) { + QString sign = value < 0 ? "Минус" : "Плюс"; + const auto intValue = static_cast(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() { // Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные @@ -130,8 +165,53 @@ void HomeScreenScript::start() { const QString bankName = "Альфа-Банк"; 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 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; int payCounter = 0; @@ -294,10 +374,8 @@ void HomeScreenScript::start() { if (!paymentCompleted.isEmpty()) { // Если вдруг исполнен - } else if (!paymentInProgress.isEmpty()) { // Если все еще в ожидании - } else { qWarning() << "--- paymentCompleted or paymentInProgress not found..."; } diff --git a/android/rshb/PayByPhoneScript.cpp b/android/rshb/PayByPhoneScript.cpp new file mode 100644 index 0000000..08bea1a --- /dev/null +++ b/android/rshb/PayByPhoneScript.cpp @@ -0,0 +1,55 @@ +#include "PayByPhoneScript.h" + +#include + +#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 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() << "Не смогли дойти до домашней страницы...."; + } +} diff --git a/android/rshb/PayByPhoneScript.h b/android/rshb/PayByPhoneScript.h new file mode 100644 index 0000000..eec6488 --- /dev/null +++ b/android/rshb/PayByPhoneScript.h @@ -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; +}; diff --git a/android/rshb/ScreenXmlParser.cpp b/android/rshb/ScreenXmlParser.cpp index 955c572..ebbbf56 100644 --- a/android/rshb/ScreenXmlParser.cpp +++ b/android/rshb/ScreenXmlParser.cpp @@ -12,6 +12,7 @@ #include "adb/AdbUtils.h" #include "android/xml/Node.h" +#include "db/AccountInfo.h" #include "db/TransactionInfo.h" 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 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 reFromBank(R"raw(из (.+?) на)raw"); @@ -31,6 +33,8 @@ static const QRegularExpression reDate(R"((?:Дата операции|дата) static const QRegularExpression reSender(R"(от ([^\(]+))"); 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) { // 1. Ищем push banner - он перекрывает весь экран Node first = m_nodes[0]; @@ -106,7 +110,6 @@ Node ScreenXmlParser::findFirstEditText() { return {}; } - Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) { for (const Node &node: m_nodes) { if (contains) { @@ -122,7 +125,6 @@ Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) { return {}; } - bool ScreenXmlParser::isHomeScreen() { // Есть заголовок "Счета и карты" и кнопка "Все продукты" 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()) { QDomNodeList subInfoList = elem.childNodes(); for (QDomNode subInfoNode: subInfoList) { if (!subInfoNode.isElement()) continue; QDomElement subInfo = subInfoNode.toElement(); - if (subInfo.attribute("index") == "1") { - return subInfo.attribute("text"); + if (subInfo.attribute("index") == index) { + return subInfo; } } } + return {}; +} + +QString findSubInfo(const QDomElement &elem, const QString &index) { + QDomElement subInfo = findSubElement(elem, index); + if (!subInfo.isNull()) { + return subInfo.attribute("text"); + } return ""; } @@ -347,19 +357,19 @@ void traverseNodes(const QDomNode &node) { QString index = infoElement.attribute("index"); switch (index.toInt()) { case 2: - qDebug() << "Дата операции:" << findSubInfo(infoElement); - continue; + qDebug() << "Дата операции:" << findSubInfo(infoElement, "1"); + break; case 5: - qDebug() << "Название:" << findSubInfo(infoElement); + qDebug() << "Название:" << findSubInfo(infoElement, "1"); qDebug() << "-------------------------------------"; // parseTransfer(findSubInfo(infoElement)); - continue; + break; case 7: - qDebug() << "Комиссия:" << findSubInfo(infoElement);; - continue; + qDebug() << "Комиссия:" << findSubInfo(infoElement, "1"); + break; default: - qDebug() << "Прочее:" << findSubInfo(infoElement);; - continue; + qDebug() << "Прочее:" << findSubInfo(infoElement, "1"); + break; } /** @@ -661,15 +671,15 @@ TransactionInfo parseExpandedInfoHistory(const QDomElement &root) { switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) { case 2: // Дата операции - transaction.bankTime = findSubInfo(e); + transaction.bankTime = findSubInfo(e, "1"); break; case 5: - transaction.description = findSubInfo(e); + transaction.description = findSubInfo(e, "1"); parseTransferInfo(transaction.description, transaction); break; case 7: // Комиссия - transaction.fee = findSubInfo(e).remove(onlyDigitsRegex).toFloat(); + transaction.fee = findSubInfo(e, "1").remove(onlyDigitsRegex).toFloat(); break; default: // qDebug() << "Прочее:" << findSubInfo(e); @@ -705,9 +715,90 @@ TransactionInfo ScreenXmlParser::parseTransactionInfoHistory(int screenWidth, in 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 ScreenXmlParser::parseAccountsInfo() { + QList 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() { - return; - QFile file("assets/rshb/test/spb_test_send.xml"); + // TransactionInfo info2 = parseExpandedInfo(m_xml); + // qDebug().noquote().nospace() << convertTransactionToString(info2); + // return; + + QFile file("assets/rshb/all_products.xml"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Cannot open file"; } @@ -720,6 +811,13 @@ void ScreenXmlParser::test() { qDebug() << "Start parsing XML:"; + // Номер карты с последними цифрами 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); // ЕСПП } diff --git a/android/rshb/ScreenXmlParser.h b/android/rshb/ScreenXmlParser.h index 624b389..bf9559b 100644 --- a/android/rshb/ScreenXmlParser.h +++ b/android/rshb/ScreenXmlParser.h @@ -3,6 +3,7 @@ #include #include #include "android/xml/Node.h" +#include "db/AccountInfo.h" #include "db/TransactionInfo.h" class ScreenXmlParser final : public QObject { @@ -36,6 +37,8 @@ public: TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight); QList parseTodayAndYesterdayHistory(); + QList parseAccountsInfo(); + void test(); private: diff --git a/database/DatabaseManager.cpp b/database/DatabaseManager.cpp index 63ab141..b981a15 100644 --- a/database/DatabaseManager.cpp +++ b/database/DatabaseManager.cpp @@ -1,26 +1,64 @@ #include "DatabaseManager.h" +#include #include -#include +#include +#include -DatabaseManager::DatabaseManager() { - db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db"); - if (!db.open()) { - qWarning() << "Ошибка открытия базы данных:" << db.lastError().text(); - } -} +DatabaseManager::DatabaseManager() = default; DatabaseManager::~DatabaseManager() { - if (db.isOpen()) { - db.close(); + QMutexLocker locker(&mutex); + + for (auto it = connectionPool.begin(); it != connectionPool.end(); ++it) { + QSqlDatabase::removeDatabase(it.key()); } + qDebug() << "Все соединения закрыты"; } -DatabaseManager& DatabaseManager::instance() { +DatabaseManager &DatabaseManager::instance() { static DatabaseManager instance; return instance; } -QSqlDatabase& DatabaseManager::database() { - return db; -} \ No newline at end of file +QSqlDatabase DatabaseManager::database() { + 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(QThread::currentThreadId())); +} + + +void DatabaseManager::closeConnection() { + QMutexLocker locker(&mutex); + if (const QString name = connectionName(); connectionPool.contains(name)) { + connectionPool.remove(name); + QSqlDatabase::removeDatabase(name); + qDebug() << "Соединение" << name << "удалено вручную"; + } +} diff --git a/database/DatabaseManager.h b/database/DatabaseManager.h index 7321c17..b95ea61 100644 --- a/database/DatabaseManager.h +++ b/database/DatabaseManager.h @@ -1,16 +1,25 @@ #pragma once #include +#include +#include +#include +#include -class DatabaseManager final : public QObject { - Q_OBJECT +class DatabaseManager { public: static DatabaseManager& instance(); - QSqlDatabase& database(); + QSqlDatabase database(); + void closeConnection(); private: DatabaseManager(); - ~DatabaseManager() override; - QSqlDatabase db; + ~DatabaseManager(); + + static QString connectionName(); + + QMutex mutex; + QSet activeConnections; + QMap connectionPool; }; \ No newline at end of file diff --git a/database/app_data.db b/database/app_data.db index 64483f7..03cd6f8 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/database/dao/AccountDAO.cpp b/database/dao/AccountDAO.cpp index 9c9a9b8..f2c579e 100644 --- a/database/dao/AccountDAO.cpp +++ b/database/dao/AccountDAO.cpp @@ -9,9 +9,9 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - INSERT INTO accounts (device_id, app_name, card_name, amount, update_time, status, description) - VALUES (:device_id, :app_name, :card_name, :amount, :update_time, :status, :description) - ON CONFLICT(device_id, app_name, card_name) + INSERT INTO accounts (device_id, app_name, numbers, last_numbers, amount, update_time, status, description) + VALUES (:device_id, :app_name, :numbers, :last_numbers, :amount, :update_time, :status, :description) + ON CONFLICT(device_id, app_name, last_numbers) DO UPDATE SET amount = excluded.amount, update_time = excluded.update_time, @@ -20,10 +20,11 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) { query.bindValue(":device_id", info.deviceId); 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(":update_time", info.updateTime.toString(Qt::ISODate)); - query.bindValue(":status", info.status); + query.bindValue(":status", accountStatusToString(info.status)); query.bindValue(":description", info.description); if (!query.exec()) { @@ -46,7 +47,7 @@ QList AccountDAO::getAccounts(const QString &deviceId, const QStrin QSqlQuery query(DatabaseManager::instance().database()); 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 WHERE device_id = :device_id AND app_name = :app_name )"); @@ -64,10 +65,11 @@ QList AccountDAO::getAccounts(const QString &deviceId, const QStrin acc.id = query.value("id").toInt(); acc.deviceId = query.value("device_id").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.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(); accounts.append(acc); } @@ -75,12 +77,25 @@ QList AccountDAO::getAccounts(const QString &deviceId, const QStrin return accounts; } -AccountInfo AccountDAO::getAccountById(const int accountId) { +AccountInfo parseAccount(const QSqlQuery &query) { 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()); 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 WHERE id = :id LIMIT 1 @@ -90,19 +105,36 @@ AccountInfo AccountDAO::getAccountById(const int accountId) { if (!query.exec()) { qWarning() << "Ошибка при получении account по ID:" << query.lastError().text(); - return acc; + return {}; } if (query.next()) { - acc.id = query.value("id").toInt(); - acc.deviceId = query.value("device_id").toString(); - acc.appName = query.value("app_name").toString(); - acc.cardName = query.value("card_name").toString(); - acc.amount = query.value("amount").toDouble(); - acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate); - acc.status = query.value("status").toString(); - acc.description = query.value("description").toString(); + return parseAccount(query); + } + return {}; +} + +AccountInfo AccountDAO::findAppAccount(const QString &appName, const QString &cardNumber) { + QSqlQuery query(DatabaseManager::instance().database()); + + 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 {}; } diff --git a/database/dao/AccountDAO.h b/database/dao/AccountDAO.h index 07a2e5b..ba6eefb 100644 --- a/database/dao/AccountDAO.h +++ b/database/dao/AccountDAO.h @@ -10,4 +10,6 @@ public: [[nodiscard]] static QList getAccounts(const QString &deviceId, const QString &appName); [[nodiscard]] static AccountInfo getAccountById(int accountId); + + [[nodiscard]] static AccountInfo findAppAccount(const QString &appName, const QString &cardNumber); }; diff --git a/database/dao/ApplicationDAO.cpp b/database/dao/ApplicationDAO.cpp new file mode 100644 index 0000000..d81f73e --- /dev/null +++ b/database/dao/ApplicationDAO.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#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 {}; +} diff --git a/database/dao/ApplicationDAO.h b/database/dao/ApplicationDAO.h new file mode 100644 index 0000000..e4e6997 --- /dev/null +++ b/database/dao/ApplicationDAO.h @@ -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); +}; diff --git a/database/dao/DeviceDAO.cpp b/database/dao/DeviceDAO.cpp index 59d608c..2f61f80 100644 --- a/database/dao/DeviceDAO.cpp +++ b/database/dao/DeviceDAO.cpp @@ -4,6 +4,20 @@ #include "DatabaseManager.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 DeviceDAO::getAllDevices(const bool online) { QList devices; @@ -24,23 +38,34 @@ QList DeviceDAO::getAllDevices(const bool online) { } while (query.next()) { - 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(); - - devices.append(device); + devices.append(parseDevice(query)); } 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) { QSqlQuery query(DatabaseManager::instance().database()); diff --git a/database/dao/DeviceDAO.h b/database/dao/DeviceDAO.h index 58d8bb4..2987c92 100644 --- a/database/dao/DeviceDAO.h +++ b/database/dao/DeviceDAO.h @@ -4,6 +4,8 @@ class DeviceDAO { public: + [[nodiscard]] static DeviceInfo getDeviceById(const QString &deviceId); + [[nodiscard]] static bool upsertDevice(const DeviceInfo &device); [[nodiscard]] static bool markDevicesOfflineExcept(const QStringList &onlineDeviceIds); diff --git a/main.cpp b/main.cpp index ddfe0aa..6d0526b 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,7 @@ #include #include "rshb/HomeScreenScript.h" +#include "rshb/PayByPhoneScript.h" void setupWorkerAndThread(QCoreApplication &app) { auto *thread = new QThread; @@ -48,26 +49,30 @@ void setupWorkerAndThread(QCoreApplication &app) { } void setupScriptAutoPaymentAndThread(QCoreApplication &app) { - auto *thread = new QThread; - auto *scriptWorker = new HomeScreenScript(0); + const QString appName = "rshb"; + const QString cardNumber = "2200380317107183"; - // Перемещаем worker в новый поток thread. - // Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоновом потоке. - scriptWorker->moveToThread(thread); - // Когда поток стартует, запускаем 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); + const QString phoneNumber = "79641815146"; + const QString bankName = "Альфа-Банк"; + const double ammount = 250; - QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() { - scriptWorker->stop(); - }); + AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber); + 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(); // // - MainWindow window; - window.show(); + // MainWindow window; + // window.show(); return app.exec(); } diff --git a/migrations.sql b/migrations.sql index 085134f..02e83db 100644 --- a/migrations.sql +++ b/migrations.sql @@ -18,24 +18,27 @@ CREATE TABLE IF NOT EXISTS applications device_id TEXT, pin_code TEXT, name TEXT, + package TEXT, status TEXT, comment TEXT, 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 ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - device_id TEXT, - app_name TEXT, - card_name TEXT, - description TEXT, - amount REAL, - update_time DATETIME DEFAULT CURRENT_TIMESTAMP, - status TEXT, + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT, + app_name TEXT, + numbers TEXT, + last_numbers TEXT, + description TEXT, + amount REAL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP, + status TEXT, 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 diff --git a/models/db/AccountInfo.h b/models/db/AccountInfo.h index d7fcdf1..cf66d89 100644 --- a/models/db/AccountInfo.h +++ b/models/db/AccountInfo.h @@ -2,13 +2,58 @@ #include +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 { - int id; + int id = -1; QString deviceId; QString appName; - QString cardName; - double amount; + QString lastNumbers; + QString numbers; + double amount = 0.0; QDateTime updateTime; - QString status; + AccountStatus status = AccountStatus::Active; QString description; -}; \ No newline at end of file +}; + +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'); +} diff --git a/models/db/ApplicationInfo.h b/models/db/ApplicationInfo.h new file mode 100644 index 0000000..c545f26 --- /dev/null +++ b/models/db/ApplicationInfo.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +struct ApplicationInfo { + QString id; + QString deviceId; + QString pinCode; + QString name; + QString package; + QString status; + QString comment; + QByteArray image; + QDateTime updateTime; +}; diff --git a/models/db/DeviceInfo.h b/models/db/DeviceInfo.h index b87ed5c..d63b5f0 100644 --- a/models/db/DeviceInfo.h +++ b/models/db/DeviceInfo.h @@ -7,9 +7,9 @@ struct DeviceInfo { QString status; QString name; QString android; - int screenWidth; - int screenHeight; - int battery; + int screenWidth = 0; + int screenHeight = 0; + int battery = 0; QDateTime updateTime; QByteArray image; }; \ No newline at end of file diff --git a/models/db/TransactionInfo.h b/models/db/TransactionInfo.h index 81996bc..35451e2 100644 --- a/models/db/TransactionInfo.h +++ b/models/db/TransactionInfo.h @@ -71,43 +71,34 @@ struct TransactionInfo { }; inline QString convertTransactionToString(const TransactionInfo &tx) { - return QString( - "TransactionInfo {\n" - " id: %1\n" - " accountId: %2\n" - " amount: %3\n" - " fee: %4\n" - " status: %5\n" - " type: %6\n" - " phone: %7\n" - " shortDescription: %8\n" - " updatedShortDescription: %9\n" - " description: %10\n" - " updatedDescription: %11\n" - " bankName: %12\n" - " bankTime: %13\n" - " name: %14\n" - " trExternalId: %15\n" - " updateTime: %16\n" - " completeTime: %17\n" - " timestamp: %18\n" - "}" - ).arg(tx.id) - .arg(tx.accountId) - .arg(tx.amount) - .arg(tx.fee) - .arg(transactionStatusToString(tx.status)) - .arg(transactionTypeToString(tx.type)) - .arg(tx.phone) - .arg(tx.shortDescription) - .arg(tx.updatedShortDescription) - .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)); + QStringList lines; + QStringList emptyFields; + + lines << "TransactionInfo {"; + + if (tx.id != -1) lines << " id: " + QString::number(tx.id); else emptyFields << "id"; + if (tx.accountId != -1) lines << " accountId: " + QString::number(tx.accountId); else emptyFields << "accountId"; + if (tx.amount != 0.0) lines << " amount: " + QString::number(tx.amount); else emptyFields << "amount"; + if (tx.fee != 0.0) lines << " fee: " + QString::number(tx.fee); else emptyFields << "fee"; + if (tx.status != TransactionStatus::Unknown) lines << " status: " + transactionStatusToString(tx.status); else emptyFields << "status"; + if (tx.type != TransactionType::Unknown) lines << " type: " + transactionTypeToString(tx.type); else emptyFields << "type"; + if (!tx.phone.isEmpty()) lines << " phone: " + tx.phone; else emptyFields << "phone"; + if (!tx.shortDescription.isEmpty()) lines << " shortDescription: " + tx.shortDescription; else emptyFields << "shortDescription"; + if (!tx.updatedShortDescription.isEmpty()) lines << " updatedShortDescription: " + tx.updatedShortDescription; else emptyFields << "updatedShortDescription"; + if (!tx.description.isEmpty()) lines << " description: " + tx.description; else emptyFields << "description"; + if (!tx.updatedDescription.isEmpty()) lines << " updatedDescription: " + tx.updatedDescription; else emptyFields << "updatedDescription"; + if (!tx.bankName.isEmpty()) lines << " bankName: " + tx.bankName; else emptyFields << "bankName"; + if (!tx.bankTime.isEmpty()) lines << " bankTime: " + tx.bankTime; else emptyFields << "bankTime"; + if (!tx.name.isEmpty()) lines << " name: " + tx.name; else emptyFields << "name"; + 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"; + if (tx.completeTime.isValid()) lines << " completeTime: " + tx.completeTime.toString(Qt::ISODate); else emptyFields << "completeTime"; + if (tx.timestamp.isValid()) lines << " timestamp: " + tx.timestamp.toString(Qt::ISODate); else emptyFields << "timestamp"; + + if (!emptyFields.isEmpty()) { + lines << " --- empty: " + emptyFields.join(", "); + } + lines << "}"; + + return lines.join('\n'); } diff --git a/views/AccountWindow.cpp b/views/AccountWindow.cpp index 0a64189..94f3182 100644 --- a/views/AccountWindow.cpp +++ b/views/AccountWindow.cpp @@ -55,13 +55,13 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) { for (int k = 0; k < accounts.size(); ++k) { AccountInfo account = accounts[k]; - QTableWidgetItem *item = new QTableWidgetItem("*" + account.cardName); + QTableWidgetItem *item = new QTableWidgetItem("*" + account.lastNumbers); item->setData(Qt::UserRole, account.id); tableWidget->setItem(k, 0, item); 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, 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); } tableWidget->setFixedHeight(totalHeight + 2); diff --git a/views/TransactionWindow.cpp b/views/TransactionWindow.cpp index a5e3766..6cdf058 100644 --- a/views/TransactionWindow.cpp +++ b/views/TransactionWindow.cpp @@ -14,7 +14,7 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia 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); setModal(true);