Remove AccountDAO, ApplicationDAO, and related models, replacing them with BankProfileDAO and BankProfileInfo.
This commit is contained in:
parent
9a7ec1d087
commit
7e826a22f1
@ -8,7 +8,8 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
namespace Black {
|
||||
@ -108,7 +109,7 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node>> &accounts) {
|
||||
void CommonScript::postAccountsData(const QList<QPair<MaterialInfo, Node>> &accounts) {
|
||||
if (accounts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -117,15 +118,15 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node>> &accou
|
||||
auto *worker = new NetworkService;
|
||||
|
||||
QJsonArray list;
|
||||
for (const QPair<AccountInfo, Node> &pair : accounts) {
|
||||
for (const QPair<MaterialInfo, Node> &pair : accounts) {
|
||||
QJsonObject obj;
|
||||
AccountInfo account = pair.first;
|
||||
MaterialInfo account = pair.first;
|
||||
obj["appName"] = account.appCode;
|
||||
obj["lastNumbers"] = account.lastNumbers;
|
||||
obj["amount"] = account.amount;
|
||||
obj["description"] = account.description;
|
||||
obj["currency"] = account.currency;
|
||||
obj["status"] = accountStatusToString(account.status);
|
||||
obj["status"] = materialStatusToString(account.status);
|
||||
list.append(obj);
|
||||
}
|
||||
|
||||
@ -139,7 +140,7 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node>> &accou
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void CommonScript::postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction) {
|
||||
void CommonScript::postNewTransactionData(const MaterialInfo &account, const TransactionInfo &transaction) {
|
||||
auto *thread = new QThread;
|
||||
auto *worker = new NetworkService;
|
||||
|
||||
@ -170,7 +171,7 @@ void CommonScript::postNewTransactionData(const AccountInfo &account, const Tran
|
||||
}
|
||||
|
||||
void CommonScript::updateTransactionData(
|
||||
const AccountInfo &account,
|
||||
const MaterialInfo &account,
|
||||
const int transactionId,
|
||||
const TransactionStatus status,
|
||||
const QString &oldDesc,
|
||||
@ -197,7 +198,7 @@ void CommonScript::updateTransactionData(
|
||||
}
|
||||
|
||||
void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
|
||||
|
||||
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
|
||||
qWarning() << "[Black] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
||||
@ -224,6 +225,8 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
||||
}
|
||||
phone.prepend('+');
|
||||
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
|
||||
|
||||
QJsonObject profileBody;
|
||||
profileBody["first_name"] = parts.value(0);
|
||||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
|
||||
@ -232,6 +235,7 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
||||
profileBody["phone_number"] = phone;
|
||||
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
||||
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
||||
profileBody["device_id"] = device.apiId;
|
||||
profileBody["currency"] = currencyCode;
|
||||
|
||||
NetworkService ns;
|
||||
|
||||
@ -34,12 +34,12 @@ protected:
|
||||
int &counter, int width, int height
|
||||
);
|
||||
|
||||
void postAccountsData(const QList<QPair<AccountInfo, Node>> &accounts);
|
||||
void postAccountsData(const QList<QPair<MaterialInfo, Node>> &accounts);
|
||||
|
||||
void postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction);
|
||||
void postNewTransactionData(const MaterialInfo &account, const TransactionInfo &transaction);
|
||||
|
||||
void updateTransactionData(
|
||||
const AccountInfo &account,
|
||||
const MaterialInfo &account,
|
||||
int transactionId,
|
||||
TransactionStatus status,
|
||||
const QString &oldDesc,
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
@ -25,8 +25,8 @@ GetCardInfoScript::GetCardInfoScript(
|
||||
GetCardInfoScript::~GetCardInfoScript() = default;
|
||||
|
||||
void GetCardInfoScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -41,7 +41,7 @@ void GetCardInfoScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[Black::GetCardInfo] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||
qWarning() << m_error;
|
||||
@ -55,7 +55,7 @@ void GetCardInfoScript::doStart() {
|
||||
// 1. Парсим баланс с домашнего экрана
|
||||
const double balance = xmlScreenParser.parseBalanceFromHomeScreen();
|
||||
qDebug() << "[Black::GetCardInfo] balance from home:" << balance;
|
||||
ApplicationDAO::updateAmount(app.id, balance);
|
||||
BankProfileDAO::updateAmount(app.id, balance);
|
||||
|
||||
// 2. Нажимаем "Tu CVU" на домашнем экране
|
||||
Node cvuButton = xmlScreenParser.findButtonNode("Tu CVU", false);
|
||||
@ -104,24 +104,24 @@ void GetCardInfoScript::doStart() {
|
||||
// 5. Сохраняем аккаунт в БД (lastNumbers = последние 4 цифры CVU)
|
||||
const QString lastNumbers = cvuNumber.right(4);
|
||||
|
||||
AccountInfo account;
|
||||
account.deviceId = m_deviceId;
|
||||
MaterialInfo account;
|
||||
account.deviceId = device.id;
|
||||
account.appCode = m_appCode;
|
||||
account.lastNumbers = lastNumbers;
|
||||
account.cardNumber = cvuNumber;
|
||||
account.amount = balance;
|
||||
account.currency = "USD";
|
||||
account.description = "CVU";
|
||||
account.status = AccountStatus::Active;
|
||||
account.status = MaterialStatus::Active;
|
||||
account.updateTime = QDateTime::currentDateTimeUtc();
|
||||
|
||||
if (!AccountDAO::upsertAccount(account)) {
|
||||
if (!MaterialDAO::upsertAccount(account)) {
|
||||
qWarning() << "[Black::GetCardInfo] Failed to upsert account";
|
||||
}
|
||||
|
||||
// 6. Отправляем материал на сервер (если ещё нет material_id)
|
||||
const AccountInfo savedAccount = AccountDAO::findAppAccount(m_deviceId, m_appCode, lastNumbers);
|
||||
const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const MaterialInfo savedAccount = MaterialDAO::findAppAccount(device.id, m_appCode, lastNumbers);
|
||||
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (savedAccount.id != -1 && !updatedApp.bankProfileId.isEmpty()) {
|
||||
if (savedAccount.materialId.isEmpty()) {
|
||||
|
||||
@ -19,7 +19,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "AppLogger.h"
|
||||
@ -23,8 +23,8 @@ GetLastTransactionsScript::GetLastTransactionsScript(
|
||||
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
||||
|
||||
void GetLastTransactionsScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -38,7 +38,7 @@ void GetLastTransactionsScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем приложение
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[Black::GetLastTransactions] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
@ -113,7 +113,7 @@ void GetLastTransactionsScript::doStart() {
|
||||
|
||||
// Находим accountId для сохранения транзакций
|
||||
int accountId = -1;
|
||||
const QList<AccountInfo> accounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
||||
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||
if (!accounts.isEmpty()) {
|
||||
accountId = accounts.first().id;
|
||||
qDebug() << "[Black::GetLastTransactions] Using accountId:" << accountId;
|
||||
|
||||
@ -19,7 +19,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
@ -24,8 +24,8 @@ GetProfileInfoScript::GetProfileInfoScript(
|
||||
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
||||
|
||||
void GetProfileInfoScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -40,7 +40,7 @@ void GetProfileInfoScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[Black::GetProfileInfo] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||
qWarning() << m_error;
|
||||
@ -67,7 +67,7 @@ void GetProfileInfoScript::doStart() {
|
||||
qDebug() << "[Black::GetProfileInfo] fullName from home:" << fullName << "balance:" << balance;
|
||||
|
||||
// Сохраняем баланс
|
||||
ApplicationDAO::updateAmount(app.id, balance);
|
||||
BankProfileDAO::updateAmount(app.id, balance);
|
||||
|
||||
AdbUtils::makeTap(m_deviceId, holaNode.x(), holaNode.y());
|
||||
QThread::msleep(2000);
|
||||
@ -155,7 +155,7 @@ void GetProfileInfoScript::doStart() {
|
||||
<< "fullName:" << parsedFullName;
|
||||
|
||||
// 7. Сохраняем в БД
|
||||
if (!ApplicationDAO::updateProfileInfo(app.id, parsedFullName, phone, email)) {
|
||||
if (!BankProfileDAO::updateProfileInfo(app.id, parsedFullName, phone, email)) {
|
||||
qWarning() << "[Black::GetProfileInfo] Failed to save profile info to DB";
|
||||
}
|
||||
|
||||
@ -163,8 +163,8 @@ void GetProfileInfoScript::doStart() {
|
||||
createBankProfileIfNeeded(m_deviceId, m_appCode);
|
||||
|
||||
// 8. Отправляем данные аккаунтов на сервер если нет material_id
|
||||
const QList<AccountInfo> allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
||||
QList<QPair<AccountInfo, Node>> accountsToPost;
|
||||
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||
QList<QPair<MaterialInfo, Node>> accountsToPost;
|
||||
for (const auto &acc : allAccounts) {
|
||||
if (acc.materialId.isEmpty()) {
|
||||
accountsToPost.append({acc, Node()});
|
||||
|
||||
@ -19,7 +19,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "AppLogger.h"
|
||||
#include "GetProfileInfoScript.h"
|
||||
@ -22,8 +22,9 @@ LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
||||
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
||||
|
||||
void LoginAndCheckAccountsScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
// m_deviceId = ADB serial, device.id = android_id (для БД)
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -34,26 +35,20 @@ void LoginAndCheckAccountsScript::doStart() {
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cant open the home screen: " + m_deviceId + " " + m_appCode;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("black/LoginAndCheck", m_deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||
"FETCH_PROFILE");
|
||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "no", "Не прошла проверка [дата]")) {
|
||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
||||
}
|
||||
BankProfileDAO::updateComment(app.id,
|
||||
"Не прошла проверка " + QDateTime::currentDateTime().toString("dd.MM.yyyy HH:mm:ss"));
|
||||
|
||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// PIN-код прошёл
|
||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) {
|
||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
||||
}
|
||||
|
||||
// Проверяем профиль и аккаунты (приложение уже на home screen)
|
||||
GetProfileInfoScript profileScript(m_deviceId, m_appCode);
|
||||
profileScript.start();
|
||||
|
||||
@ -19,7 +19,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "AppLogger.h"
|
||||
@ -12,7 +12,7 @@
|
||||
namespace Black {
|
||||
|
||||
PayByCardScript::PayByCardScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString cardNumber,
|
||||
double amount,
|
||||
QObject *parent
|
||||
@ -26,7 +26,7 @@ PayByCardScript::~PayByCardScript() = default;
|
||||
|
||||
void PayByCardScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_account.deviceId;
|
||||
@ -35,7 +35,7 @@ void PayByCardScript::doStart() {
|
||||
return;
|
||||
}
|
||||
|
||||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ void PayByCardScript::makePayment(
|
||||
// 0. Убеждаемся что на домашнем экране
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[Black::PayByCard] Not on home screen, restarting app...";
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
if (!runAppAndGoToHomeScreen(deviceId, app.package, pinCode, width, height)) {
|
||||
m_error = "Cannot reach home screen: " + deviceId;
|
||||
qWarning() << m_error;
|
||||
|
||||
@ -8,7 +8,7 @@ class PayByCardScript final : public CommonScript {
|
||||
|
||||
public:
|
||||
PayByCardScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString cardNumber,
|
||||
double amount,
|
||||
QObject *parent = nullptr
|
||||
@ -20,7 +20,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const AccountInfo m_account;
|
||||
const MaterialInfo m_account;
|
||||
const QString m_cardNumber;
|
||||
const double m_amount;
|
||||
QString m_error;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <QObject>
|
||||
#include <QDomElement>
|
||||
#include "android/xml/Node.h"
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "db/TransactionInfo.h"
|
||||
|
||||
namespace Black {
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
namespace Ozon {
|
||||
@ -113,11 +114,18 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем на рекламный bottom sheet (ипотека и т.п.)
|
||||
// Проверяем на рекламный bottom sheet (ипотека, открыть счёт и т.п.)
|
||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||
qDebug() << "[runAppAndGoToHomeScreen] Ad bottom sheet detected, closing...";
|
||||
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
|
||||
QThread::msleep(1000);
|
||||
// Если touch_outside не помог — жмём назад
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
if (Node stillAd = xmlScreenParser.tryToFindAdBanner(); !stillAd.isEmpty()) {
|
||||
qDebug() << "[runAppAndGoToHomeScreen] Ad still visible, pressing back...";
|
||||
AdbUtils::goBack(deviceId);
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -187,7 +195,7 @@ bool CommonScript::goToAllProducts(
|
||||
}
|
||||
|
||||
bool CommonScript::findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers) {
|
||||
QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
QList<QPair<MaterialInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
for (auto &[account, node]: accounts) {
|
||||
// FIXME будем считать что числа последние везде уникальные
|
||||
if (accountNumbers == account.lastNumbers) {
|
||||
@ -201,7 +209,7 @@ bool CommonScript::findAndTapOnAccount(const QString &deviceId, const QString &a
|
||||
}
|
||||
|
||||
|
||||
void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &accounts) {
|
||||
void CommonScript::postAccountsData(const QList<QPair<MaterialInfo, Node> > &accounts) {
|
||||
if (accounts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -210,15 +218,15 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &acco
|
||||
auto *worker = new NetworkService;
|
||||
|
||||
QJsonArray list;
|
||||
for (const QPair<AccountInfo, Node> &pair: accounts) {
|
||||
for (const QPair<MaterialInfo, Node> &pair: accounts) {
|
||||
QJsonObject obj;
|
||||
AccountInfo account = pair.first;
|
||||
MaterialInfo account = pair.first;
|
||||
obj["appName"] = account.appCode;
|
||||
obj["lastNumbers"] = account.lastNumbers;
|
||||
obj["amount"] = account.amount;
|
||||
obj["description"] = account.description;
|
||||
obj["currency"] = account.currency;
|
||||
obj["status"] = accountStatusToString(account.status);
|
||||
obj["status"] = materialStatusToString(account.status);
|
||||
|
||||
list.append(obj);
|
||||
}
|
||||
@ -233,7 +241,7 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &acco
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void CommonScript::postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction) {
|
||||
void CommonScript::postNewTransactionData(const MaterialInfo &account, const TransactionInfo &transaction) {
|
||||
auto *thread = new QThread;
|
||||
auto *worker = new NetworkService;
|
||||
|
||||
@ -265,7 +273,7 @@ void CommonScript::postNewTransactionData(const AccountInfo &account, const Tran
|
||||
}
|
||||
|
||||
void CommonScript::updateTransactionData(
|
||||
const AccountInfo &account,
|
||||
const MaterialInfo &account,
|
||||
const int transactionId,
|
||||
const TransactionStatus status,
|
||||
const QString &oldDesc,
|
||||
@ -293,7 +301,7 @@ void CommonScript::updateTransactionData(
|
||||
}
|
||||
|
||||
void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
|
||||
|
||||
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
|
||||
qWarning() << "[Ozon] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
||||
@ -320,6 +328,8 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
||||
}
|
||||
phone.prepend('+');
|
||||
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
|
||||
|
||||
QJsonObject profileBody;
|
||||
profileBody["first_name"] = parts.value(0);
|
||||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
|
||||
@ -328,6 +338,7 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
||||
profileBody["phone_number"] = phone;
|
||||
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
||||
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
||||
profileBody["device_id"] = device.apiId;
|
||||
profileBody["currency"] = currencyCode;
|
||||
|
||||
NetworkService ns;
|
||||
|
||||
@ -27,12 +27,12 @@ protected:
|
||||
|
||||
bool findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers);
|
||||
|
||||
void postAccountsData(const QList<QPair<AccountInfo, Node> > &accounts);
|
||||
void postAccountsData(const QList<QPair<MaterialInfo, Node> > &accounts);
|
||||
|
||||
void postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction);
|
||||
void postNewTransactionData(const MaterialInfo &account, const TransactionInfo &transaction);
|
||||
|
||||
void updateTransactionData(
|
||||
const AccountInfo &account,
|
||||
const MaterialInfo &account,
|
||||
int transactionId,
|
||||
TransactionStatus status,
|
||||
const QString &oldDesc,
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
@ -30,8 +30,8 @@ GetCardInfoScript::GetCardInfoScript(
|
||||
GetCardInfoScript::~GetCardInfoScript() = default;
|
||||
|
||||
void GetCardInfoScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -57,7 +57,7 @@ void GetCardInfoScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем приложение
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[GetCardInfo] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
@ -97,7 +97,7 @@ void GetCardInfoScript::doStart() {
|
||||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||||
const QString phone = xmlScreenParser.parseProfilePhone();
|
||||
qDebug() << "[GetCardInfo] Profile parsed - fullName:" << fullName << "phone:" << phone;
|
||||
ApplicationDAO::updateProfileInfo(app.id, fullName, phone);
|
||||
BankProfileDAO::updateProfileInfo(app.id, fullName, phone);
|
||||
} else {
|
||||
qWarning() << "[GetCardInfo] Profile screen not reached, skipping profile parse";
|
||||
}
|
||||
@ -113,7 +113,7 @@ void GetCardInfoScript::doStart() {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[GetCardInfo] Not on home screen before card search, restarting...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen before card search: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
@ -129,7 +129,7 @@ void GetCardInfoScript::doStart() {
|
||||
}
|
||||
|
||||
// Ждём пока все карты подгрузятся — до 5 попыток
|
||||
QList<AccountInfo> cards;
|
||||
QList<MaterialInfo> cards;
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
cards = xmlScreenParser.parseCardsOnHomeScreen();
|
||||
@ -149,12 +149,12 @@ void GetCardInfoScript::doStart() {
|
||||
qDebug() << "[GetCardInfo] Found" << cards.size() << "card(s) on home screen";
|
||||
|
||||
// Апсёртим аккаунты для всех найденных карт
|
||||
for (AccountInfo &card : cards) {
|
||||
card.deviceId = m_deviceId;
|
||||
for (MaterialInfo &card : cards) {
|
||||
card.deviceId = device.id;
|
||||
card.appCode = m_appCode;
|
||||
card.updateTime = QDateTime::currentDateTimeUtc();
|
||||
card.status = AccountStatus::Active;
|
||||
if (!AccountDAO::upsertAccount(card)) {
|
||||
card.status = MaterialStatus::Active;
|
||||
if (!MaterialDAO::upsertAccount(card)) {
|
||||
qWarning() << "[GetCardInfo] Failed to upsert account for card" << card.lastNumbers;
|
||||
} else {
|
||||
qDebug() << "[GetCardInfo] Upserted account:" << card.lastNumbers
|
||||
@ -165,7 +165,7 @@ void GetCardInfoScript::doStart() {
|
||||
// Сохраняем баланс домашнего экрана в application
|
||||
const double totalAmount = cards.isEmpty() ? 0.0 : cards[0].amount;
|
||||
if (totalAmount > 0.0) {
|
||||
ApplicationDAO::updateAmount(app.id, totalAmount);
|
||||
BankProfileDAO::updateAmount(app.id, totalAmount);
|
||||
}
|
||||
|
||||
// Синхронная отправка на сервер (NetworkService блокируется через QEventLoop внутри)
|
||||
@ -188,7 +188,7 @@ void GetCardInfoScript::doStart() {
|
||||
|
||||
// 2. Отправляем банковский профиль (используем свежие данные из БД — имя/телефон уже спарсены)
|
||||
{
|
||||
const ApplicationInfo freshApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const BankProfileInfo freshApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
const QStringList parts = freshApp.fullName.split(' ', Qt::SkipEmptyParts);
|
||||
|
||||
// ISO 4217 numeric currency codes
|
||||
@ -207,6 +207,7 @@ void GetCardInfoScript::doStart() {
|
||||
profileBody["phone_number"] = phone;
|
||||
profileBody["balance"] = QString::number(qRound64(totalAmount * 100));
|
||||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||||
profileBody["device_id"] = device.apiId;
|
||||
profileBody["currency"] = currencyCode;
|
||||
ns.setPayload(QJsonDocument(profileBody).toJson());
|
||||
|
||||
@ -214,7 +215,7 @@ void GetCardInfoScript::doStart() {
|
||||
ns.addExtra("app_id", freshApp.id);
|
||||
ns.postBankProfile();
|
||||
// Проверяем что bankProfileId сохранился
|
||||
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
if (afterPost.bankProfileId.isEmpty()) {
|
||||
const QString err = "postBankProfile failed for " + m_deviceId;
|
||||
qWarning() << "[GetCardInfo]" << err;
|
||||
@ -233,7 +234,7 @@ void GetCardInfoScript::doStart() {
|
||||
|
||||
// Для каждой карты переходим на её экран и получаем полный номер через OCR
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
const AccountInfo &card = cards[i];
|
||||
const MaterialInfo &card = cards[i];
|
||||
|
||||
// Если передан конкретный lastNumbers — обрабатываем только его
|
||||
if (!m_lastNumbers.isEmpty() && card.lastNumbers != m_lastNumbers) continue;
|
||||
@ -241,7 +242,7 @@ void GetCardInfoScript::doStart() {
|
||||
qDebug() << "[GetCardInfo] Getting card number for" << card.lastNumbers;
|
||||
|
||||
// Если номер карты уже есть в БД — сумма уже обновлена через upsert, навигация не нужна
|
||||
AccountInfo existing = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
|
||||
MaterialInfo existing = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||||
if (existing.id != -1 && !existing.cardNumber.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "already has number, skipping";
|
||||
continue;
|
||||
@ -339,16 +340,16 @@ void GetCardInfoScript::doStart() {
|
||||
if (!cardNumber.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
|
||||
// Получаем id аккаунта из БД и сохраняем номер карты
|
||||
AccountInfo saved = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
|
||||
MaterialInfo saved = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||||
if (saved.id != -1) {
|
||||
AccountDAO::updateCardNumber(saved.id, cardNumber);
|
||||
MaterialDAO::updateCardNumber(saved.id, cardNumber);
|
||||
}
|
||||
} else {
|
||||
const QString err = "Card number not found for " + card.lastNumbers;
|
||||
qWarning() << "[GetCardInfo]" << err;
|
||||
AccountInfo saved = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
|
||||
MaterialInfo saved = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||||
if (saved.id != -1) {
|
||||
AccountDAO::updateAccountById(saved.id, accountStatusToString(AccountStatus::Off),
|
||||
MaterialDAO::updateAccountById(saved.id, materialStatusToString(MaterialStatus::Off),
|
||||
saved.description, saved.currency);
|
||||
}
|
||||
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
|
||||
@ -365,10 +366,10 @@ void GetCardInfoScript::doStart() {
|
||||
}
|
||||
|
||||
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
|
||||
const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
if (!updatedApp.bankProfileId.isEmpty()) {
|
||||
for (const AccountInfo &card : cards) {
|
||||
const AccountInfo dbCard = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
|
||||
for (const MaterialInfo &card : cards) {
|
||||
const MaterialInfo dbCard = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||||
if (dbCard.cardNumber.isEmpty()) continue;
|
||||
if (!dbCard.materialId.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] material already exists for card" << dbCard.lastNumbers << ", skipping";
|
||||
@ -379,7 +380,7 @@ void GetCardInfoScript::doStart() {
|
||||
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
||||
materialBody["card_number"] = dbCard.cardNumber;
|
||||
materialBody["name"] = updatedApp.fullName;
|
||||
materialBody["state"] = accountStatusToString(dbCard.status) == "active" ? "enabled" : "disabled";
|
||||
materialBody["state"] = materialStatusToString(dbCard.status) == "active" ? "enabled" : "disabled";
|
||||
ns.setPayload(QJsonDocument(materialBody).toJson());
|
||||
ns.addExtra("account_id", dbCard.id);
|
||||
ns.postMaterial();
|
||||
|
||||
@ -23,7 +23,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
const QString m_lastNumbers;
|
||||
QString m_error;
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
#include <leptonica/allheaders.h>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "time/DateUtils.h"
|
||||
@ -33,7 +33,6 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Convert to grayscale for better OCR accuracy
|
||||
Pix *gray = pixConvertRGBToGray(pix, 0.0f, 0.0f, 0.0f);
|
||||
pixDestroy(&pix);
|
||||
if (!gray) {
|
||||
@ -44,8 +43,6 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
||||
}
|
||||
|
||||
api->SetImage(gray);
|
||||
// adb screencap PNGs lack DPI metadata; set 300 DPI so Tesseract
|
||||
// interprets character sizes correctly
|
||||
api->SetSourceResolution(300);
|
||||
char *text = api->GetUTF8Text();
|
||||
QString result = QString::fromUtf8(text).trimmed();
|
||||
@ -58,12 +55,9 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
||||
}
|
||||
|
||||
static QString extractTransactionId(const QString &ocrText) {
|
||||
// Ищем по слову "операции" — OCR часто искажает "ID" перед ним
|
||||
// \S{1,3} вместо точного "ID" покрывает любые OCR-ошибки (1О, 1Ю, ID, 10 и т.д.)
|
||||
QRegularExpression reId(R"(\S{1,3}\s+операции\s*=?\s*([A-Za-zА-Яа-яЁё0-9]+))");
|
||||
if (auto m = reId.match(ocrText); m.hasMatch()) return m.captured(1);
|
||||
|
||||
// "№ Ф-2026-25235663"
|
||||
QRegularExpression reRef(R"(№\s*(Ф-[\d-]+))");
|
||||
if (auto m = reRef.match(ocrText); m.hasMatch()) return m.captured(1);
|
||||
|
||||
@ -76,16 +70,24 @@ GetLastTransactionsScript::GetLastTransactionsScript(
|
||||
QString deviceId,
|
||||
QString appCode,
|
||||
int maxCount,
|
||||
double searchAmount,
|
||||
QDateTime searchTime,
|
||||
QObject *parent
|
||||
) : CommonScript(parent),
|
||||
m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)), m_maxCount(maxCount) {
|
||||
m_deviceId(std::move(deviceId)),
|
||||
m_appCode(std::move(appCode)),
|
||||
m_maxCount(maxCount),
|
||||
m_searchAmount(searchAmount),
|
||||
m_searchTime(std::move(searchTime)) {
|
||||
}
|
||||
|
||||
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
||||
|
||||
void GetLastTransactionsScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId);
|
||||
if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -94,22 +96,49 @@ void GetLastTransactionsScript::doStart() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!navigateToTransactionList(device, app)) {
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим accountId
|
||||
int accountId = -1;
|
||||
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||
if (!accounts.isEmpty()) {
|
||||
accountId = accounts.first().id;
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] No account found for" << device.id << m_appCode;
|
||||
}
|
||||
|
||||
// Выбор режима: поиск конкретной транзакции или сканирование последних
|
||||
if (m_searchAmount > 0) {
|
||||
searchTransaction(device, accountId);
|
||||
} else {
|
||||
scanRecentTransactions(device, accountId);
|
||||
}
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
bool GetLastTransactionsScript::navigateToTransactionList(const DeviceInfo &device, const BankProfileInfo &app) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
// Закрываем рекламный bottom sheet, если открыт
|
||||
// Закрываем рекламный bottom sheet
|
||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||
qDebug() << "[GetLastTransactions] Ad banner detected, closing...";
|
||||
AdbUtils::makeTap(m_deviceId, adNode.x(), adNode.y());
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (Node stillAd = xmlScreenParser.tryToFindAdBanner(); !stillAd.isEmpty()) {
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
// Если не на домашнем экране — перезапускаем приложение
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[GetLastTransactions] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
@ -117,8 +146,7 @@ void GetLastTransactionsScript::doStart() {
|
||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||
"FETCH_TRANSACTIONS");
|
||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
}
|
||||
@ -128,13 +156,10 @@ void GetLastTransactionsScript::doStart() {
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
// Скроллим вниз до кнопки "Все" и тапаем по ней
|
||||
// Кнопка "Все" на домашнем экране находится у границы навбара (y ≈ 2190),
|
||||
// поэтому сначала скроллим вниз, чтобы она поднялась выше навигационной панели
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10); // ~90% высоты экрана
|
||||
// Скроллим до "Все" и тапаем
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
||||
Node allBtn;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Сначала скроллим, потом ищем
|
||||
const int x = device.screenWidth / 2;
|
||||
const int offset = device.screenHeight / 4;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset);
|
||||
@ -142,74 +167,159 @@ void GetLastTransactionsScript::doStart() {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
allBtn = xmlScreenParser.findButtonNode("Все", false);
|
||||
if (!allBtn.isEmpty() && allBtn.y() > 0 && allBtn.y() < navBarTop) {
|
||||
break;
|
||||
}
|
||||
if (!allBtn.isEmpty() && allBtn.y() > 0 && allBtn.y() < navBarTop) break;
|
||||
allBtn = {};
|
||||
qDebug() << "[GetLastTransactions] 'Все' not found above navbar, attempt" << i + 1 << "/ 10";
|
||||
}
|
||||
|
||||
if (allBtn.isEmpty()) {
|
||||
m_error = "Button 'Все' not found after scrolling: " + m_deviceId;
|
||||
m_error = "Button 'Все' not found: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions] Found 'Все' at" << allBtn.x() << allBtn.y();
|
||||
AdbUtils::makeTap(m_deviceId, allBtn.x(), allBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём загрузки экрана "Операции"
|
||||
bool loaded = false;
|
||||
// Ждём экран "Операции"
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isTransactionListScreen()) {
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[GetLastTransactions] Waiting for transaction list screen, attempt" << attempt + 1 << "/ 10";
|
||||
if (xmlScreenParser.isTransactionListScreen()) return true;
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
m_error = "Transaction list screen not loaded: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
m_error = "Transaction list screen not loaded: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int accountId) {
|
||||
const QTimeZone msk("Europe/Moscow");
|
||||
const int maxScrolls = 15;
|
||||
|
||||
qDebug() << "[FetchTransactions] Searching: amount=" << m_searchAmount
|
||||
<< "time=" << m_searchTime.toString(Qt::ISODate);
|
||||
|
||||
for (int scroll = 0; scroll < maxScrolls; ++scroll) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
const QList<Node> txButtons = xmlScreenParser.findTransactionButtons(20);
|
||||
|
||||
for (const Node &btn : txButtons) {
|
||||
const TransactionInfo parsed = xmlScreenParser.parseTransactionButtonText(btn.text);
|
||||
if (qAbs(qAbs(parsed.amount) - qAbs(m_searchAmount)) > 0.01) continue;
|
||||
|
||||
qDebug() << "[FetchTransactions] Amount match:" << parsed.amount
|
||||
<< "at" << btn.x() << btn.y();
|
||||
|
||||
AdbUtils::makeTap(m_deviceId, btn.x(), btn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём загрузки деталей
|
||||
TransactionInfo detail;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
detail = xmlScreenParser.parseTransactionDetail();
|
||||
if (!detail.bankTime.isEmpty()) break;
|
||||
if (xmlScreenParser.isTransactionListScreen()) {
|
||||
AdbUtils::makeTap(m_deviceId, btn.x(), btn.y());
|
||||
}
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (detail.bankTime.isEmpty()) {
|
||||
qWarning() << "[FetchTransactions] Could not load detail, going back";
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Парсим время
|
||||
QString dtStr = detail.bankTime;
|
||||
for (int ci = 0; ci < dtStr.size(); ++ci) {
|
||||
const QChar ch = dtStr.at(ci);
|
||||
if (ch != ' ' && ch.category() == QChar::Separator_Space) dtStr[ci] = ' ';
|
||||
}
|
||||
|
||||
QDateTime txTime = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
|
||||
if (!txTime.isValid()) txTime = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
||||
if (txTime.isValid()) txTime.setTimeZone(msk);
|
||||
|
||||
// Проверяем ±1 час
|
||||
if (m_searchTime.isValid() && txTime.isValid()) {
|
||||
const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(m_searchTime.toUTC()));
|
||||
if (diffSecs > 3600) {
|
||||
qDebug() << "[FetchTransactions] Time mismatch: tx=" << dtStr
|
||||
<< "diff=" << diffSecs << "sec, skipping";
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Нашли!
|
||||
qDebug() << "[FetchTransactions] FOUND transaction:"
|
||||
<< "amount=" << parsed.amount << "time=" << dtStr
|
||||
<< "name=" << detail.name;
|
||||
|
||||
// Сохраняем
|
||||
if (accountId != -1) {
|
||||
TransactionInfo tx;
|
||||
tx.accountId = accountId;
|
||||
tx.amount = parsed.amount;
|
||||
tx.bankName = m_appCode;
|
||||
tx.bankTime = dtStr;
|
||||
tx.status = TransactionStatus::Complete;
|
||||
tx.timestamp = DateUtils::getUtcNow();
|
||||
if (txTime.isValid()) tx.completeTime = txTime;
|
||||
if (!detail.name.isEmpty()) tx.name = detail.name;
|
||||
if (!detail.description.isEmpty()) tx.description = detail.description;
|
||||
if (!detail.phone.isEmpty()) {
|
||||
QString cleanPhone;
|
||||
for (const QChar &ch : detail.phone)
|
||||
if (ch.isDigit()) cleanPhone.append(ch);
|
||||
tx.phone = cleanPhone;
|
||||
tx.type = TransactionType::Phone;
|
||||
}
|
||||
TransactionDAO::insertTransactionIfNotExists(tx);
|
||||
}
|
||||
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
return; // успех — m_error пустой
|
||||
}
|
||||
|
||||
// Скроллим вниз
|
||||
const int x = device.screenWidth / 2;
|
||||
const int offset = device.screenHeight / 4;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset);
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
// Ждём появления транзакций (могут подгружаться)
|
||||
m_error = "Transaction not found: amount=" + QString::number(m_searchAmount, 'f', 2)
|
||||
+ " time=" + m_searchTime.toString(Qt::ISODate);
|
||||
qWarning() << "[FetchTransactions]" << m_error;
|
||||
}
|
||||
|
||||
void GetLastTransactionsScript::scanRecentTransactions(const DeviceInfo &device, int accountId) {
|
||||
const QTimeZone msk("Europe/Moscow");
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
||||
|
||||
// Ждём появления транзакций
|
||||
QList<Node> txButtons;
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
if (!txButtons.isEmpty()) break;
|
||||
qDebug() << "[GetLastTransactions] No transactions yet, attempt" << attempt + 1 << "/ 5";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions] Found" << txButtons.size() << "transaction buttons";
|
||||
|
||||
// Находим accountId для сохранения транзакций
|
||||
int accountId = -1;
|
||||
const QList<AccountInfo> accounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
||||
if (!accounts.isEmpty()) {
|
||||
accountId = accounts.first().id;
|
||||
qDebug() << "[GetLastTransactions] Using accountId:" << accountId;
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] No account found for" << m_deviceId << m_appCode;
|
||||
}
|
||||
|
||||
// Для каждой транзакции: клик → берём дату из деталей → назад
|
||||
QList<TransactionInfo> transactions;
|
||||
for (int i = 0; i < txButtons.size(); ++i) {
|
||||
const Node &btn = txButtons[i];
|
||||
|
||||
// Если кнопка внизу экрана (за навбаром) — скроллим до неё
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
||||
// Скроллим до кнопки если за экраном
|
||||
if (btn.y() <= 0 || btn.y() >= navBarTop) {
|
||||
qDebug() << "[GetLastTransactions] Transaction" << i + 1 << "is off-screen (y=" << btn.y() << "), scrolling...";
|
||||
for (int s = 0; s < 5; ++s) {
|
||||
const int x = device.screenWidth / 2;
|
||||
const int offset = device.screenHeight / 4;
|
||||
@ -217,121 +327,84 @@ void GetLastTransactionsScript::doStart() {
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
if (i < txButtons.size() && txButtons[i].y() > 0 && txButtons[i].y() < navBarTop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= txButtons.size()) {
|
||||
qWarning() << "[GetLastTransactions] Transaction" << i + 1 << "not found after scrolling";
|
||||
break;
|
||||
if (i < txButtons.size() && txButtons[i].y() > 0 && txButtons[i].y() < navBarTop) break;
|
||||
}
|
||||
if (i >= txButtons.size()) break;
|
||||
}
|
||||
|
||||
const Node &visibleBtn = txButtons[i];
|
||||
TransactionInfo tx = xmlScreenParser.parseTransactionButtonText(visibleBtn.text);
|
||||
|
||||
qDebug() << "[GetLastTransactions] Tapping transaction" << i + 1 << ":" << visibleBtn.text.left(50);
|
||||
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём загрузки деталей — ищем дату в шапке
|
||||
// Ждём загрузки деталей
|
||||
TransactionInfo detail;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
detail = xmlScreenParser.parseTransactionDetail();
|
||||
if (!detail.bankTime.isEmpty()) break;
|
||||
|
||||
// Если всё ещё на экране операций — тап не сработал, повторяем
|
||||
if (xmlScreenParser.isTransactionListScreen()) {
|
||||
qDebug() << "[GetLastTransactions] Still on transaction list, re-tapping" << i + 1;
|
||||
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions] Detail not loaded, attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
tx.bankName = "ozon";
|
||||
|
||||
tx.bankName = m_appCode;
|
||||
tx.bankTime = detail.bankTime;
|
||||
tx.status = TransactionStatus::Complete;
|
||||
tx.timestamp = DateUtils::getUtcNow();
|
||||
if (!detail.name.isEmpty()) tx.name = detail.name;
|
||||
if (!detail.phone.isEmpty()) {
|
||||
// Оставляем только цифры
|
||||
QString cleanPhone;
|
||||
for (const QChar &ch : detail.phone) {
|
||||
for (const QChar &ch : detail.phone)
|
||||
if (ch.isDigit()) cleanPhone.append(ch);
|
||||
}
|
||||
tx.phone = cleanPhone;
|
||||
tx.type = TransactionType::Phone;
|
||||
}
|
||||
|
||||
// Чистим bankTime от unicode символов: заменяем все не-ASCII пробелы на обычные
|
||||
// Парсим время
|
||||
if (!tx.bankTime.isEmpty()) {
|
||||
QString dtStr = tx.bankTime;
|
||||
for (int ci = 0; ci < dtStr.size(); ++ci) {
|
||||
const QChar ch = dtStr.at(ci);
|
||||
if (ch != ' ' && ch.category() == QChar::Separator_Space) {
|
||||
dtStr[ci] = ' ';
|
||||
}
|
||||
if (ch != ' ' && ch.category() == QChar::Separator_Space) dtStr[ci] = ' ';
|
||||
}
|
||||
tx.bankTime = dtStr;
|
||||
const QTimeZone msk("Europe/Moscow");
|
||||
QDateTime parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
|
||||
if (!parsed.isValid()) {
|
||||
parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
||||
}
|
||||
if (!parsed.isValid()) parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
||||
if (parsed.isValid()) {
|
||||
parsed.setTimeZone(msk);
|
||||
tx.completeTime = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions]"
|
||||
<< "date:" << tx.bankTime
|
||||
<< "name:" << tx.description
|
||||
<< "sender:" << tx.name
|
||||
<< "phone:" << tx.phone
|
||||
<< "amount:" << (tx.amount >= 0 ? "+" : "") + QString::number(tx.amount, 'f', 2);
|
||||
|
||||
// Если задан лимит по количеству — проверяем счётчик
|
||||
// Иначе фильтруем по 24 часам
|
||||
if (m_maxCount > 0) {
|
||||
if (transactions.size() + 1 >= m_maxCount) {
|
||||
// Эта транзакция последняя — сохраним и выйдем после обработки
|
||||
}
|
||||
} else if (tx.completeTime.isValid()) {
|
||||
// Фильтр по времени (24ч) если нет maxCount
|
||||
if (m_maxCount <= 0 && tx.completeTime.isValid()) {
|
||||
const qint64 ageSecs = tx.completeTime.toUTC().secsTo(QDateTime::currentDateTimeUtc());
|
||||
if (ageSecs > 24 * 3600) {
|
||||
qDebug() << "[GetLastTransactions] Transaction older than 24h, stopping";
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Сохраняем в БД если не существует
|
||||
// Берём первый аккаунт для этого устройства и приложения
|
||||
// Сохраняем
|
||||
if (accountId != -1) {
|
||||
tx.accountId = accountId;
|
||||
if (TransactionDAO::insertTransactionIfNotExists(tx)) {
|
||||
qDebug() << "[GetLastTransactions] Saved transaction to DB";
|
||||
|
||||
// Делаем скриншот документа транзакции
|
||||
// Скриншот чека
|
||||
Node docBtn = xmlScreenParser.findButtonNode("Документы", false);
|
||||
if (!docBtn.isEmpty()) {
|
||||
qDebug() << "[GetLastTransactions] Tapping 'Документы'...";
|
||||
AdbUtils::makeTap(m_deviceId, docBtn.x(), docBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём появления кнопки "Поделиться"
|
||||
bool docLoaded = false;
|
||||
for (int da = 0; da < 20; ++da) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
Node shareBtn = xmlScreenParser.findNodeByContentDesc("Поделиться");
|
||||
if (!shareBtn.isEmpty()) {
|
||||
docLoaded = true;
|
||||
break;
|
||||
if (!xmlScreenParser.findNodeByContentDesc("Поделиться").isEmpty()) {
|
||||
docLoaded = true; break;
|
||||
}
|
||||
qDebug() << "[GetLastTransactions] Waiting for 'Поделиться', attempt" << da + 1 << "/ 20";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
@ -346,70 +419,42 @@ void GetLastTransactionsScript::doStart() {
|
||||
accountId, tx.bankName, tx.bankTime, tx.amount);
|
||||
if (saved.id != -1) {
|
||||
TransactionDAO::updateReceiptImage(saved.id, screenshot);
|
||||
qDebug() << "[GetLastTransactions] Receipt saved to DB, txId:" << saved.id;
|
||||
|
||||
// OCR: распознаём текст и достаём ID операции
|
||||
const QString ocrText = recognizeTextFromImage(screenshot);
|
||||
if (!ocrText.isEmpty()) {
|
||||
qDebug() << "[GetLastTransactions] OCR result, txId:" << saved.id
|
||||
<< "\n--- OCR TEXT START ---\n" << ocrText
|
||||
<< "\n--- OCR TEXT END ---";
|
||||
qDebug() << "[GetLastTransactions] OCR hex:"
|
||||
<< ocrText.left(200).toUtf8().toHex(' ');
|
||||
const QString txId = extractTransactionId(ocrText);
|
||||
if (!txId.isEmpty()) {
|
||||
TransactionDAO::updateBankTrExternalId(saved.id, txId);
|
||||
qDebug() << "[GetLastTransactions] Extracted transaction ID:" << txId;
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] Could not extract transaction ID from OCR text";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] OCR returned empty text";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] Screenshot is empty";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] 'Поделиться' not found, skipping receipt";
|
||||
}
|
||||
|
||||
// Возвращаемся из документов в детали транзакции
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "[GetLastTransactions] Transaction already exists or error";
|
||||
}
|
||||
}
|
||||
|
||||
transactions.append(tx);
|
||||
|
||||
// Если достигнут лимит по количеству — выходим
|
||||
if (m_maxCount > 0 && transactions.size() >= m_maxCount) {
|
||||
qDebug() << "[GetLastTransactions] Reached maxCount limit:" << m_maxCount;
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
break;
|
||||
}
|
||||
|
||||
// Возвращаемся в список
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1500);
|
||||
|
||||
// Ждём возврата на экран списка
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isTransactionListScreen()) break;
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
// Обновляем кнопки (после возврата координаты могут измениться)
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
}
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
} // namespace Ozon
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include "CommonScript.h"
|
||||
#include <QDateTime>
|
||||
#include "db/DeviceInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
namespace Ozon {
|
||||
|
||||
@ -11,6 +14,8 @@ public:
|
||||
QString deviceId,
|
||||
QString appCode,
|
||||
int maxCount = 0,
|
||||
double searchAmount = 0,
|
||||
QDateTime searchTime = {},
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
@ -20,9 +25,20 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
// Навигация до экрана "Операции", возвращает true если успешно
|
||||
bool navigateToTransactionList(const DeviceInfo &device, const BankProfileInfo &app);
|
||||
|
||||
// Сканирование последних транзакций (для UI: 24ч или maxCount)
|
||||
void scanRecentTransactions(const DeviceInfo &device, int accountId);
|
||||
|
||||
// Поиск конкретной транзакции по сумме и времени (для задач FETCH_TRANSACTIONS)
|
||||
void searchTransaction(const DeviceInfo &device, int accountId);
|
||||
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
const int m_maxCount; // 0 = без лимита (фильтр по 24ч), >0 = только N последних
|
||||
const int m_maxCount; // 0 = фильтр по 24ч, >0 = N последних
|
||||
const double m_searchAmount; // сумма в рублях (0 = не искать конкретную)
|
||||
const QDateTime m_searchTime; // примерное время (UTC)
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
#include "GetProfileInfoScript.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "AppLogger.h"
|
||||
@ -26,8 +27,8 @@ GetProfileInfoScript::GetProfileInfoScript(
|
||||
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
||||
|
||||
void GetProfileInfoScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -53,7 +54,7 @@ void GetProfileInfoScript::doStart() {
|
||||
// Если мы не на домашнем экране — перезапускаем приложение и вводим пин-код
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[GetProfileInfo] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||
qWarning() << m_error;
|
||||
@ -107,17 +108,18 @@ void GetProfileInfoScript::doStart() {
|
||||
|
||||
// Извлекаем полное имя и телефон
|
||||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||||
const QString phone = xmlScreenParser.parseProfilePhone().simplified().remove(' ');
|
||||
QString phone = xmlScreenParser.parseProfilePhone();
|
||||
phone.remove(QRegularExpression(R"(\s+)")); // убираем все виды пробелов (включая \u00A0)
|
||||
|
||||
qDebug() << "[GetProfileInfo] fullName:" << fullName << "phone:" << phone;
|
||||
|
||||
if (!ApplicationDAO::updateProfileInfo(app.id, fullName, phone)) {
|
||||
if (!BankProfileDAO::updateProfileInfo(app.id, fullName, phone)) {
|
||||
m_error = "Failed to save profile info to DB: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
}
|
||||
|
||||
// Отправляем bank_profile на сервер
|
||||
const ApplicationInfo freshApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const BankProfileInfo freshApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
const QMap<QString, int> currencyCodes = {
|
||||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}
|
||||
@ -133,6 +135,7 @@ void GetProfileInfoScript::doStart() {
|
||||
profileBody["phone_number"] = phone;
|
||||
profileBody["balance"] = QString::number(qRound64(freshApp.amount * 100));
|
||||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||||
profileBody["device_id"] = device.apiId;
|
||||
profileBody["currency"] = currencyCode;
|
||||
|
||||
NetworkService ns;
|
||||
@ -142,7 +145,7 @@ void GetProfileInfoScript::doStart() {
|
||||
if (freshApp.bankProfileId.isEmpty()) {
|
||||
ns.addExtra("app_id", freshApp.id);
|
||||
ns.postBankProfile();
|
||||
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
if (afterPost.bankProfileId.isEmpty()) {
|
||||
const QString err = "postBankProfile failed for " + m_deviceId;
|
||||
qWarning() << "[GetProfileInfo]" << err;
|
||||
@ -159,8 +162,8 @@ void GetProfileInfoScript::doStart() {
|
||||
}
|
||||
|
||||
// Отправляем данные аккаунтов на сервер если нет material_id
|
||||
const QList<AccountInfo> allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
||||
QList<QPair<AccountInfo, Node>> accountsToPost;
|
||||
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||
QList<QPair<MaterialInfo, Node>> accountsToPost;
|
||||
for (const auto &acc : allAccounts) {
|
||||
if (acc.materialId.isEmpty()) {
|
||||
accountsToPost.append({acc, Node()});
|
||||
|
||||
@ -19,7 +19,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "AppLogger.h"
|
||||
#include "GetCardInfoScript.h"
|
||||
@ -14,18 +14,21 @@ namespace Ozon {
|
||||
LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
||||
QString deviceId,
|
||||
QString appCode,
|
||||
QObject *parent
|
||||
QObject *parent,
|
||||
bool fetchProfileOnly
|
||||
) : CommonScript(parent),
|
||||
m_deviceId(std::move(deviceId)),
|
||||
m_appCode(std::move(appCode)) {
|
||||
m_appCode(std::move(appCode)),
|
||||
m_fetchProfileOnly(fetchProfileOnly) {
|
||||
}
|
||||
|
||||
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
||||
|
||||
|
||||
void LoginAndCheckAccountsScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
||||
// m_deviceId = ADB serial, device.id = android_id (для БД)
|
||||
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||
@ -36,46 +39,45 @@ void LoginAndCheckAccountsScript::doStart() {
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cant open the home screen: " + m_deviceId + " " + m_appCode;
|
||||
qWarning() << m_error;
|
||||
AppLogger::log("ozon/LoginAndCheck", m_deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||
"FETCH_PROFILE");
|
||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "no", "Не прошла проверка [дата]")) {
|
||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
||||
}
|
||||
BankProfileDAO::updateComment(app.id,
|
||||
"Не прошла проверка " + QDateTime::currentDateTime().toString("dd.MM.yyyy HH:mm:ss"));
|
||||
|
||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
} else {
|
||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) {
|
||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
||||
}
|
||||
|
||||
// Создаём bank profile если ещё не создан
|
||||
createBankProfileIfNeeded(m_deviceId, m_appCode);
|
||||
createBankProfileIfNeeded(device.id, m_appCode);
|
||||
|
||||
if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) {
|
||||
QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
if (goToAllProducts(m_deviceId, device.screenWidth, device.screenHeight)) {
|
||||
QList<QPair<MaterialInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
for (auto &[account, node]: accounts) {
|
||||
account.deviceId = m_deviceId;
|
||||
account.deviceId = device.id;
|
||||
account.appCode = m_appCode;
|
||||
if (!AccountDAO::upsertAccount(account)) {
|
||||
if (!MaterialDAO::upsertAccount(account)) {
|
||||
m_error = "Not updated: " + account.lastNumbers;
|
||||
qCritical() << m_error;
|
||||
}
|
||||
}
|
||||
|
||||
postAccountsData(accounts);
|
||||
if (!m_fetchProfileOnly) {
|
||||
postAccountsData(accounts);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Cant go to all products: " << m_deviceId << " " << m_appCode;
|
||||
}
|
||||
|
||||
// Запускаем сканирование баланса, данных карт и отправку на сервер (синхронно)
|
||||
GetCardInfoScript cardScript(m_deviceId, m_appCode, "");
|
||||
cardScript.start();
|
||||
if (!m_fetchProfileOnly) {
|
||||
// Запускаем сканирование баланса, данных карт и отправку на сервер (синхронно)
|
||||
GetCardInfoScript cardScript(m_deviceId, m_appCode, "");
|
||||
cardScript.start();
|
||||
}
|
||||
}
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
|
||||
@ -12,15 +12,17 @@ public:
|
||||
explicit LoginAndCheckAccountsScript(
|
||||
QString deviceId,
|
||||
QString appCode,
|
||||
QObject *parent = nullptr
|
||||
QObject *parent = nullptr,
|
||||
bool fetchProfileOnly = false
|
||||
);
|
||||
|
||||
protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
QString m_deviceId;
|
||||
const QString m_appCode;
|
||||
const bool m_fetchProfileOnly;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
#include <leptonica/allheaders.h>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "time/DateUtils.h"
|
||||
@ -59,7 +59,7 @@ static QString extractTransactionId(const QString &ocrText) {
|
||||
namespace Ozon {
|
||||
|
||||
PayByCardScript::PayByCardScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString cardNumber,
|
||||
const double amount,
|
||||
QObject *parent
|
||||
@ -72,7 +72,7 @@ PayByCardScript::~PayByCardScript() = default;
|
||||
|
||||
void PayByCardScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_account.deviceId;
|
||||
@ -94,11 +94,11 @@ void PayByCardScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[PayByCard] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(device.adbSerial, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + device.id;
|
||||
qWarning() << m_error;
|
||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
||||
AdbUtils::tryToKillApplication(device.adbSerial, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
@ -126,7 +126,7 @@ void PayByCardScript::doStart() {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ public:
|
||||
~PayByCardScript() override;
|
||||
|
||||
PayByCardScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString cardNumber,
|
||||
double amount,
|
||||
QObject *parent = nullptr
|
||||
@ -20,7 +20,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const AccountInfo m_account;
|
||||
const MaterialInfo m_account;
|
||||
const QString m_cardNumber;
|
||||
const double m_amount;
|
||||
QString m_error;
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
#include <leptonica/allheaders.h>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "time/DateUtils.h"
|
||||
@ -59,7 +59,7 @@ static QString extractTransactionId(const QString &ocrText) {
|
||||
namespace Ozon {
|
||||
|
||||
PayByPhoneScript::PayByPhoneScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString phone,
|
||||
QString bankName,
|
||||
const double amount,
|
||||
@ -73,7 +73,7 @@ PayByPhoneScript::~PayByPhoneScript() = default;
|
||||
|
||||
void PayByPhoneScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found: " + m_account.deviceId;
|
||||
@ -95,11 +95,11 @@ void PayByPhoneScript::doStart() {
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[PayByPhone] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
if (!runAppAndGoToHomeScreen(device.adbSerial, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + device.id;
|
||||
qWarning() << m_error;
|
||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
||||
AdbUtils::tryToKillApplication(device.adbSerial, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
@ -127,7 +127,7 @@ void PayByPhoneScript::doStart() {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ public:
|
||||
~PayByPhoneScript() override;
|
||||
|
||||
PayByPhoneScript(
|
||||
AccountInfo account,
|
||||
MaterialInfo account,
|
||||
QString phone,
|
||||
QString bankName,
|
||||
double amount,
|
||||
@ -21,7 +21,7 @@ protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const AccountInfo m_account;
|
||||
const MaterialInfo m_account;
|
||||
const QString m_phone;
|
||||
const QString m_bankName;
|
||||
const double m_amount;
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "android/xml/Node.h"
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "db/TransactionInfo.h"
|
||||
#include "time/DateUtils.h"
|
||||
|
||||
@ -358,7 +358,7 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<AccountInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
||||
QList<MaterialInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
||||
// "Основной счёт X ₽" — общая кнопка с балансом для всех карт.
|
||||
// Карты — отдельные кнопки с текстом из 4 цифр (могут быть любые non-breaking spaces).
|
||||
// Сумма из "Основной счёт" проставляется на все карты.
|
||||
@ -388,13 +388,13 @@ QList<AccountInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
||||
qWarning() << "[parseCards] Balance button not found or amount=0";
|
||||
|
||||
// 2. Собираем все кнопки карт (текст заканчивается на 4 цифры)
|
||||
QList<AccountInfo> cards;
|
||||
QList<MaterialInfo> cards;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
||||
QString t = node.text.trimmed();
|
||||
if (!endsWithFourDigits.match(t).hasMatch()) continue;
|
||||
|
||||
AccountInfo acc;
|
||||
MaterialInfo acc;
|
||||
acc.lastNumbers = t.right(4);
|
||||
acc.amount = commonAmount;
|
||||
acc.currency = "RUB";
|
||||
@ -1329,8 +1329,8 @@ TransactionInfo ScreenXmlParser::parseTransactionInfo(int screenWidth, int scree
|
||||
}
|
||||
|
||||
|
||||
QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
||||
QList<QPair<AccountInfo, Node> > accounts;
|
||||
QList<QPair<MaterialInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
||||
QList<QPair<MaterialInfo, Node> > accounts;
|
||||
for (const Node &node: m_nodes) {
|
||||
const QString text = node.text;
|
||||
|
||||
@ -1343,7 +1343,7 @@ QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
||||
continue;
|
||||
}
|
||||
|
||||
AccountInfo info;
|
||||
MaterialInfo info;
|
||||
info.lastNumbers = match.captured(1).remove(" ");
|
||||
info.amount = match.captured(2).remove(" ").toFloat();
|
||||
info.description = text.mid(0, text.indexOf("Номер карты"));
|
||||
@ -1495,7 +1495,7 @@ void ScreenXmlParser::test() {
|
||||
// Номер карты с последними цифрами
|
||||
QDomElement root = doc.documentElement();
|
||||
parseAndSaveXml(doc.toString());
|
||||
// for (const AccountInfo &info: parseAccountsInfo()) {
|
||||
// for (const MaterialInfo &info: parseAccountsInfo()) {
|
||||
// qDebug().noquote().nospace() << convertAccountToString(info);
|
||||
// }
|
||||
// TransactionInfo info = parseExpandedInfo(root);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <QObject>
|
||||
#include <QDomElement>
|
||||
#include "android/xml/Node.h"
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "db/TransactionInfo.h"
|
||||
|
||||
namespace Ozon {
|
||||
@ -50,9 +50,9 @@ public:
|
||||
// Finds a card button whose text ends with the given last 4 digits
|
||||
Node findCardButtonByLastNumbers(const QString &lastNumbers);
|
||||
|
||||
// Returns all cards visible on the home screen as AccountInfo stubs
|
||||
// Returns all cards visible on the home screen as MaterialInfo stubs
|
||||
// (lastNumbers, description, amount filled; id=-1, cardNumber empty)
|
||||
QList<AccountInfo> parseCardsOnHomeScreen();
|
||||
QList<MaterialInfo> parseCardsOnHomeScreen();
|
||||
|
||||
bool isProfileScreen();
|
||||
|
||||
@ -76,7 +76,7 @@ public:
|
||||
QList<TransactionInfo> parseTodayAndYesterdayHistory();
|
||||
|
||||
|
||||
QList<QPair<AccountInfo, Node> > parseAccountsInfo();
|
||||
QList<QPair<MaterialInfo, Node> > parseAccountsInfo();
|
||||
|
||||
// Finds a TextView with full card number (format "XXXX XXXX XXXX XXXX")
|
||||
QString findFullCardNumber();
|
||||
|
||||
@ -81,178 +81,143 @@ QString DatabaseManager::connectionName() {
|
||||
}
|
||||
|
||||
|
||||
static bool columnExists(QSqlQuery &q, const QString &table, const QString &column) {
|
||||
// Валидация: допускаем только [a-zA-Z0-9_] чтобы исключить SQL injection
|
||||
static const QRegularExpression valid("^[a-zA-Z_][a-zA-Z0-9_]*$");
|
||||
if (!valid.match(table).hasMatch() || !valid.match(column).hasMatch()) {
|
||||
qCritical() << "[DB] columnExists: invalid identifier" << table << column;
|
||||
return false;
|
||||
}
|
||||
|
||||
q.exec(QString("SELECT COUNT(*) FROM pragma_table_info('%1') WHERE name='%2'")
|
||||
.arg(table, column));
|
||||
return q.next() && q.value(0).toInt() > 0;
|
||||
}
|
||||
|
||||
void DatabaseManager::initSchema() {
|
||||
QSqlDatabase db = database();
|
||||
QSqlQuery q(db);
|
||||
|
||||
// Читаем текущую версию схемы
|
||||
q.exec("PRAGMA user_version");
|
||||
const int version = q.next() ? q.value(0).toInt() : 0;
|
||||
db.transaction();
|
||||
|
||||
// ── Версия 1: полная схема ─────────────────────────────────────────────────
|
||||
if (version < 1) {
|
||||
db.transaction();
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS devices (
|
||||
id TEXT PRIMARY KEY,
|
||||
api_id TEXT,
|
||||
adb_serial TEXT,
|
||||
status TEXT,
|
||||
name TEXT,
|
||||
android TEXT,
|
||||
screen_width INTEGER,
|
||||
screen_height INTEGER,
|
||||
battery INTEGER,
|
||||
update_time DATETIME,
|
||||
timestamp DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
image BLOB
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS devices (
|
||||
id TEXT PRIMARY KEY,
|
||||
api_id TEXT,
|
||||
status TEXT,
|
||||
name TEXT,
|
||||
android TEXT,
|
||||
screen_width INTEGER,
|
||||
screen_height INTEGER,
|
||||
battery INTEGER,
|
||||
update_time DATETIME,
|
||||
timestamp DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
image BLOB
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS bank_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
pin_code TEXT,
|
||||
pin_code_checked TEXT DEFAULT 'no',
|
||||
name TEXT,
|
||||
code TEXT,
|
||||
package TEXT,
|
||||
status TEXT DEFAULT 'off',
|
||||
install TEXT DEFAULT 'no',
|
||||
comment TEXT,
|
||||
phone TEXT DEFAULT '',
|
||||
email TEXT DEFAULT '',
|
||||
full_name TEXT DEFAULT '',
|
||||
bank_profile_id TEXT DEFAULT '',
|
||||
currency TEXT DEFAULT '',
|
||||
amount REAL DEFAULT 0,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, code)
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS applications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
pin_code TEXT,
|
||||
pin_code_checked TEXT DEFAULT 'no',
|
||||
name TEXT,
|
||||
code TEXT,
|
||||
package TEXT,
|
||||
status TEXT DEFAULT 'off',
|
||||
install TEXT DEFAULT 'no',
|
||||
comment TEXT,
|
||||
phone TEXT DEFAULT '',
|
||||
email TEXT DEFAULT '',
|
||||
full_name TEXT DEFAULT '',
|
||||
bank_profile_id TEXT DEFAULT '',
|
||||
currency TEXT DEFAULT '',
|
||||
amount REAL DEFAULT 0,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, code)
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS materials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
app_code TEXT,
|
||||
last_numbers TEXT,
|
||||
description TEXT,
|
||||
amount REAL,
|
||||
currency TEXT DEFAULT '',
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
app_code TEXT,
|
||||
last_numbers TEXT,
|
||||
description TEXT,
|
||||
amount REAL,
|
||||
currency TEXT DEFAULT '',
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id INTEGER,
|
||||
amount REAL,
|
||||
fee REAL,
|
||||
status TEXT,
|
||||
type TEXT,
|
||||
phone TEXT,
|
||||
name TEXT,
|
||||
short_description TEXT,
|
||||
updated_short_description TEXT,
|
||||
description TEXT,
|
||||
updated_description TEXT,
|
||||
bank_name TEXT,
|
||||
bank_time TEXT,
|
||||
bank_tr_external_id TEXT,
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
receipt_image BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id INTEGER,
|
||||
amount REAL,
|
||||
fee REAL,
|
||||
status TEXT,
|
||||
type TEXT,
|
||||
phone TEXT,
|
||||
name TEXT,
|
||||
short_description TEXT,
|
||||
updated_short_description TEXT,
|
||||
description TEXT,
|
||||
updated_description TEXT,
|
||||
bank_name TEXT,
|
||||
bank_time TEXT,
|
||||
bank_tr_external_id TEXT,
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
receipt_image BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id TEXT,
|
||||
device_id TEXT,
|
||||
amount REAL,
|
||||
phone TEXT,
|
||||
bank_name TEXT,
|
||||
comment TEXT,
|
||||
status TEXT,
|
||||
type TEXT,
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
json TEXT DEFAULT '',
|
||||
sent_to_server INTEGER DEFAULT 0,
|
||||
screenshot BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id TEXT,
|
||||
device_id TEXT,
|
||||
amount REAL,
|
||||
phone TEXT,
|
||||
bank_name TEXT,
|
||||
comment TEXT,
|
||||
status TEXT,
|
||||
type TEXT,
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
json TEXT DEFAULT '',
|
||||
sent_to_server INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
))");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
screenshot BLOB,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
screenshot BLOB,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||||
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS general_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
level TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
))");
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_general_logs_id ON general_logs (id DESC)");
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 1");
|
||||
qDebug() << "[DB] Migration 1 applied";
|
||||
}
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
))");
|
||||
|
||||
// ── Версия 2: добавляем email в applications ─────────────────────────────
|
||||
if (version < 2) {
|
||||
db.transaction();
|
||||
q.exec("ALTER TABLE applications ADD COLUMN email TEXT DEFAULT ''");
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 2");
|
||||
qDebug() << "[DB] Migration 2 applied";
|
||||
}
|
||||
|
||||
// ── Версия 3: таблица general_logs для всех Qt-логов ────────────────────
|
||||
if (version < 3) {
|
||||
db.transaction();
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS general_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
level TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL
|
||||
))");
|
||||
q.exec("CREATE INDEX IF NOT EXISTS idx_general_logs_id ON general_logs (id DESC)");
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 3");
|
||||
qDebug() << "[DB] Migration 3 applied";
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
void DatabaseManager::closeConnection() {
|
||||
|
||||
@ -68,3 +68,30 @@ QByteArray AppLogDAO::getScreenshot(int id) {
|
||||
if (!q.exec() || !q.next()) return {};
|
||||
return q.value(0).toByteArray();
|
||||
}
|
||||
|
||||
QList<AppLogEntry> AppLogDAO::getLogsWithScreenshots(int limit) {
|
||||
QList<AppLogEntry> result;
|
||||
QSqlQuery q(DatabaseManager::instance().database());
|
||||
q.prepare(R"(
|
||||
SELECT id, source, device_id, message, screenshot, timestamp
|
||||
FROM app_logs
|
||||
ORDER BY id DESC
|
||||
LIMIT :limit
|
||||
)");
|
||||
q.bindValue(":limit", limit);
|
||||
if (!q.exec()) {
|
||||
qWarning() << "[AppLogDAO] getLogsWithScreenshots failed:" << q.lastError().text();
|
||||
return result;
|
||||
}
|
||||
while (q.next()) {
|
||||
AppLogEntry e;
|
||||
e.id = q.value("id").toInt();
|
||||
e.source = q.value("source").toString();
|
||||
e.deviceId = q.value("device_id").toString();
|
||||
e.message = q.value("message").toString();
|
||||
e.screenshot = q.value("screenshot").toByteArray();
|
||||
e.timestamp = DateUtils::fromUtcString(q.value("timestamp").toString());
|
||||
result.append(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -14,4 +14,7 @@ public:
|
||||
|
||||
// Загружает скриншот отдельно по id (по клику в UI)
|
||||
[[nodiscard]] static QByteArray getScreenshot(int id);
|
||||
|
||||
// Все записи со скриншотами (для экспорта)
|
||||
[[nodiscard]] static QList<AppLogEntry> getLogsWithScreenshots(int limit = 100);
|
||||
};
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
#include <QSqlError>
|
||||
#include <QVariant>
|
||||
#include <QSettings>
|
||||
#include "ApplicationDAO.h"
|
||||
#include "BankProfileDAO.h"
|
||||
#include "DatabaseManager.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
||||
bool BankProfileDAO::upsertApplication(const BankProfileInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO applications (id, device_id, pin_code, name, code, package, status, comment, install, pin_code_checked)
|
||||
INSERT INTO bank_profiles (id, device_id, pin_code, name, code, package, status, comment, install, pin_code_checked)
|
||||
VALUES (:id, :device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked)
|
||||
ON CONFLICT(device_id, code)
|
||||
DO UPDATE SET
|
||||
@ -44,11 +44,11 @@ bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode) {
|
||||
bool BankProfileDAO::updatePinCode(const QString &appId, const QString &pinCode) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE applications
|
||||
UPDATE bank_profiles
|
||||
SET pin_code = :pin_code
|
||||
WHERE id = :id
|
||||
)");
|
||||
@ -63,7 +63,7 @@ bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::update(
|
||||
bool BankProfileDAO::update(
|
||||
const int &appId,
|
||||
const QString &pinCode,
|
||||
const QString &comment,
|
||||
@ -72,7 +72,7 @@ bool ApplicationDAO::update(
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE applications
|
||||
UPDATE bank_profiles
|
||||
SET pin_code = :pin_code, comment = :comment, status = :status
|
||||
WHERE id = :id
|
||||
)");
|
||||
@ -89,7 +89,7 @@ bool ApplicationDAO::update(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updatePinCodeStatus(
|
||||
bool BankProfileDAO::updatePinCodeStatus(
|
||||
const int &appId,
|
||||
const QString &pinCodeStatus,
|
||||
const QString &comment
|
||||
@ -97,15 +97,14 @@ bool ApplicationDAO::updatePinCodeStatus(
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE applications
|
||||
SET pin_code_checked = :pin_code_checked, comment = :comment, status = :status
|
||||
UPDATE bank_profiles
|
||||
SET pin_code_checked = :pin_code_checked, comment = :comment
|
||||
WHERE id = :id
|
||||
)");
|
||||
|
||||
query.bindValue(":id", appId);
|
||||
query.bindValue(":comment", comment);
|
||||
query.bindValue(":pin_code_checked", pinCodeStatus);
|
||||
query.bindValue(":status", pinCodeStatus == "yes" ? "active" : "off");
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||
@ -115,8 +114,20 @@ bool ApplicationDAO::updatePinCodeStatus(
|
||||
}
|
||||
|
||||
|
||||
ApplicationInfo parseApplication(const QSqlQuery &query) {
|
||||
ApplicationInfo app;
|
||||
bool BankProfileDAO::updateComment(const int appId, const QString &comment) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE bank_profiles SET comment = :comment WHERE id = :id");
|
||||
query.bindValue(":comment", comment);
|
||||
query.bindValue(":id", appId);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при обновлении комментария:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BankProfileInfo parseApplication(const QSqlQuery &query) {
|
||||
BankProfileInfo app;
|
||||
app.id = query.value("id").toInt();
|
||||
app.deviceId = query.value("device_id").toString();
|
||||
app.pinCode = query.value("pin_code").toString();
|
||||
@ -136,12 +147,12 @@ ApplicationInfo parseApplication(const QSqlQuery &query) {
|
||||
return app;
|
||||
}
|
||||
|
||||
ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QString &appCode) {
|
||||
BankProfileInfo BankProfileDAO::getApplication(const QString &deviceId, const QString &appCode) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount
|
||||
FROM applications
|
||||
FROM bank_profiles
|
||||
WHERE device_id = :device_id AND code = :code
|
||||
LIMIT 1
|
||||
)");
|
||||
@ -160,13 +171,13 @@ ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QS
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &deviceId) {
|
||||
QList<BankProfileInfo> BankProfileDAO::getApplicationsByDeviceId(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
QList<ApplicationInfo> list;
|
||||
QList<BankProfileInfo> list;
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount
|
||||
FROM applications
|
||||
FROM bank_profiles
|
||||
WHERE device_id = :device_id AND install = 'yes'
|
||||
)");
|
||||
query.bindValue(":device_id", deviceId);
|
||||
@ -183,7 +194,30 @@ QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &
|
||||
return list;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updateProfileInfo(
|
||||
QList<BankProfileInfo> BankProfileDAO::getAllApplicationsByDeviceId(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
QList<BankProfileInfo> list;
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount
|
||||
FROM bank_profiles
|
||||
WHERE device_id = :device_id
|
||||
)");
|
||||
query.bindValue(":device_id", deviceId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении всех приложений по deviceId:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
list.append(parseApplication(query));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
bool BankProfileDAO::updateProfileInfo(
|
||||
const int &appId,
|
||||
const QString &fullName,
|
||||
const QString &phone,
|
||||
@ -192,7 +226,7 @@ bool ApplicationDAO::updateProfileInfo(
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE applications
|
||||
UPDATE bank_profiles
|
||||
SET full_name = :full_name, phone = :phone, email = :email
|
||||
WHERE id = :id
|
||||
)");
|
||||
@ -209,9 +243,9 @@ bool ApplicationDAO::updateProfileInfo(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updateAmount(const int appId, const double amount) {
|
||||
bool BankProfileDAO::updateAmount(const int appId, const double amount) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE applications SET amount = :amount WHERE id = :id");
|
||||
query.prepare("UPDATE bank_profiles SET amount = :amount WHERE id = :id");
|
||||
query.bindValue(":amount", amount);
|
||||
query.bindValue(":id", appId);
|
||||
if (!query.exec()) {
|
||||
@ -221,9 +255,9 @@ bool ApplicationDAO::updateAmount(const int appId, const double amount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updateBankProfileId(const int appId, const QString &bankProfileId) {
|
||||
bool BankProfileDAO::updateBankProfileId(const int appId, const QString &bankProfileId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE applications SET bank_profile_id = :bank_profile_id WHERE id = :id");
|
||||
query.prepare("UPDATE bank_profiles SET bank_profile_id = :bank_profile_id WHERE id = :id");
|
||||
query.bindValue(":bank_profile_id", bankProfileId);
|
||||
query.bindValue(":id", appId);
|
||||
if (!query.exec()) {
|
||||
@ -233,17 +267,17 @@ bool ApplicationDAO::updateBankProfileId(const int appId, const QString &bankPro
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<ApplicationInfo> ApplicationDAO::findAllByBankProfileId(const QString &bankProfileId) {
|
||||
QList<ApplicationInfo> list;
|
||||
QList<BankProfileInfo> BankProfileDAO::findAllByBankProfileId(const QString &bankProfileId) {
|
||||
QList<BankProfileInfo> list;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount
|
||||
FROM applications
|
||||
FROM bank_profiles
|
||||
WHERE bank_profile_id = :bank_profile_id
|
||||
)");
|
||||
query.bindValue(":bank_profile_id", bankProfileId);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при поиске applications по bank_profile_id:" << query.lastError().text();
|
||||
qWarning() << "Ошибка при поиске bank_profiles по bank_profile_id:" << query.lastError().text();
|
||||
return list;
|
||||
}
|
||||
while (query.next()) {
|
||||
@ -252,10 +286,10 @@ QList<ApplicationInfo> ApplicationDAO::findAllByBankProfileId(const QString &ban
|
||||
return list;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::activateAppsForDevice(const QString &deviceId) {
|
||||
bool BankProfileDAO::activateAppsForDevice(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
UPDATE applications SET status = 'active'
|
||||
UPDATE bank_profiles SET status = 'active'
|
||||
WHERE device_id = :device_id AND install = 'yes' AND pin_code_checked = 'yes'
|
||||
)");
|
||||
query.bindValue(":device_id", deviceId);
|
||||
@ -266,10 +300,10 @@ bool ApplicationDAO::activateAppsForDevice(const QString &deviceId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::deactivateAppsForDevice(const QString &deviceId) {
|
||||
bool BankProfileDAO::deactivateAppsForDevice(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
UPDATE applications SET status = 'off'
|
||||
UPDATE bank_profiles SET status = 'off'
|
||||
WHERE device_id = :device_id AND install = 'yes'
|
||||
)");
|
||||
query.bindValue(":device_id", deviceId);
|
||||
@ -280,7 +314,7 @@ bool ApplicationDAO::deactivateAppsForDevice(const QString &deviceId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QString> &apps) {
|
||||
bool BankProfileDAO::checkInstalledApps(const QString &deviceId, const QSet<QString> &apps) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
QStringList banks = settings.value("apps/list", QStringList()).toStringList();
|
||||
@ -290,7 +324,7 @@ bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QStr
|
||||
QStringList placeholders;
|
||||
for (int i = 0; i < banks.size(); ++i) placeholders << "?";
|
||||
query.prepare(QString(R"(
|
||||
UPDATE applications SET install = 'no'
|
||||
UPDATE bank_profiles SET install = 'no'
|
||||
WHERE device_id = ? AND code NOT IN (%1)
|
||||
)").arg(placeholders.join(",")));
|
||||
query.addBindValue(deviceId);
|
||||
@ -307,7 +341,7 @@ bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QStr
|
||||
const QString currency = settings.value(bank + "/currency").toString();
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO applications (device_id, name, code, package, status, install, pin_code_checked, currency)
|
||||
INSERT INTO bank_profiles (device_id, name, code, package, status, install, pin_code_checked, currency)
|
||||
VALUES (:device_id, :name, :code, :package, :status, :install, :pin_code_checked, :currency)
|
||||
ON CONFLICT(device_id, code)
|
||||
DO UPDATE SET
|
||||
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
class ApplicationDAO {
|
||||
class BankProfileDAO {
|
||||
public:
|
||||
[[nodiscard]] static bool upsertApplication(const ApplicationInfo &info);
|
||||
[[nodiscard]] static bool upsertApplication(const BankProfileInfo &info);
|
||||
|
||||
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
||||
|
||||
@ -21,6 +21,8 @@ public:
|
||||
const QString &comment
|
||||
);
|
||||
|
||||
[[nodiscard]] static bool updateComment(int appId, const QString &comment);
|
||||
|
||||
[[nodiscard]] static bool updateProfileInfo(
|
||||
const int &appId,
|
||||
const QString &fullName,
|
||||
@ -30,13 +32,15 @@ public:
|
||||
|
||||
[[nodiscard]] static bool updateBankProfileId(int appId, const QString &bankProfileId);
|
||||
|
||||
[[nodiscard]] static QList<ApplicationInfo> findAllByBankProfileId(const QString &bankProfileId);
|
||||
[[nodiscard]] static QList<BankProfileInfo> findAllByBankProfileId(const QString &bankProfileId);
|
||||
|
||||
[[nodiscard]] static bool updateAmount(int appId, double amount);
|
||||
|
||||
[[nodiscard]] static ApplicationInfo getApplication(const QString &deviceId, const QString &appCode);
|
||||
[[nodiscard]] static BankProfileInfo getApplication(const QString &deviceId, const QString &appCode);
|
||||
|
||||
[[nodiscard]] static QList<ApplicationInfo> getApplicationsByDeviceId(const QString &deviceId);
|
||||
[[nodiscard]] static QList<BankProfileInfo> getApplicationsByDeviceId(const QString &deviceId);
|
||||
|
||||
[[nodiscard]] static QList<BankProfileInfo> getAllApplicationsByDeviceId(const QString &deviceId);
|
||||
|
||||
[[nodiscard]] static bool checkInstalledApps(const QString &deviceId, const QSet<QString> &apps);
|
||||
|
||||
@ -9,6 +9,7 @@ DeviceInfo parseDevice(const QSqlQuery &query) {
|
||||
DeviceInfo device;
|
||||
device.id = query.value("id").toString();
|
||||
device.apiId = query.value("api_id").toString();
|
||||
device.adbSerial = query.value("adb_serial").toString();
|
||||
device.status = query.value("status").toString();
|
||||
device.name = query.value("name").toString();
|
||||
device.android = query.value("android").toString();
|
||||
@ -25,7 +26,7 @@ QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
||||
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
QString sql = R"(
|
||||
SELECT id, api_id, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
SELECT id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
)";
|
||||
if (online) {
|
||||
@ -71,7 +72,7 @@ DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, api_id, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
SELECT id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
WHERE id = :deviceId
|
||||
)");
|
||||
@ -89,6 +90,69 @@ DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
DeviceInfo DeviceDAO::findByAdbSerial(const QString &adbSerial) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
WHERE adb_serial = :adb_serial
|
||||
LIMIT 1
|
||||
)");
|
||||
query.bindValue(":adb_serial", adbSerial);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при поиске устройства по adb_serial:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
return parseDevice(query);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DeviceInfo DeviceDAO::findByName(const QString &name) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
WHERE name = :name
|
||||
LIMIT 1
|
||||
)");
|
||||
query.bindValue(":name", name);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при поиске устройства по name:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
return parseDevice(query);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DeviceInfo DeviceDAO::findUnmatchedDevice() {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
WHERE api_id IS NOT NULL AND api_id != ''
|
||||
AND (adb_serial IS NULL OR adb_serial = '')
|
||||
LIMIT 1
|
||||
)");
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при поиске unmatched device:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
return parseDevice(query);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
@ -107,6 +171,8 @@ bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||
// Обновление
|
||||
query.prepare(R"(
|
||||
UPDATE devices SET
|
||||
api_id = CASE WHEN :api_id != '' THEN :api_id ELSE api_id END,
|
||||
adb_serial = CASE WHEN :adb_serial != '' THEN :adb_serial ELSE adb_serial END,
|
||||
status = :status,
|
||||
name = :name,
|
||||
android = :android,
|
||||
@ -120,14 +186,16 @@ bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||
// Вставка
|
||||
query.prepare(R"(
|
||||
INSERT INTO devices (
|
||||
id, status, name, android, screen_width, screen_height, battery, update_time
|
||||
id, api_id, adb_serial, status, name, android, screen_width, screen_height, battery, update_time
|
||||
) VALUES (
|
||||
:id, :status, :name, :android, :width, :height, :battery, :update_time
|
||||
:id, :api_id, :adb_serial, :status, :name, :android, :width, :height, :battery, :update_time
|
||||
)
|
||||
)");
|
||||
}
|
||||
|
||||
query.bindValue(":id", device.id);
|
||||
query.bindValue(":api_id", device.apiId);
|
||||
query.bindValue(":adb_serial", device.adbSerial);
|
||||
query.bindValue(":status", device.status);
|
||||
query.bindValue(":name", device.name);
|
||||
query.bindValue(":android", device.android);
|
||||
|
||||
@ -6,6 +6,12 @@ class DeviceDAO {
|
||||
public:
|
||||
[[nodiscard]] static DeviceInfo getDeviceById(const QString &deviceId);
|
||||
|
||||
[[nodiscard]] static DeviceInfo findByAdbSerial(const QString &adbSerial);
|
||||
|
||||
[[nodiscard]] static DeviceInfo findByName(const QString &name);
|
||||
|
||||
[[nodiscard]] static DeviceInfo findUnmatchedDevice();
|
||||
|
||||
[[nodiscard]] static bool upsertDevice(const DeviceInfo &device);
|
||||
|
||||
[[nodiscard]] static bool updateApiId(const QString &deviceId, const QString &apiId);
|
||||
|
||||
@ -160,7 +160,14 @@ EventInfo EventDAO::getFirstWaitEventByDeviceId(const QString &key) {
|
||||
status, type, comment, json, sent_to_server, update_time, complete_time, timestamp
|
||||
FROM events
|
||||
WHERE status = :status AND device_id = :device_id
|
||||
ORDER BY timestamp ASC
|
||||
ORDER BY
|
||||
CASE type
|
||||
WHEN 'CREATE_TRANSACTION' THEN 0
|
||||
WHEN 'FETCH_TRANSACTIONS' THEN 1
|
||||
WHEN 'FETCH_PROFILE' THEN 2
|
||||
ELSE 3
|
||||
END ASC,
|
||||
timestamp ASC
|
||||
LIMIT 1
|
||||
)");
|
||||
query.bindValue(":status", eventStatusToString(EventStatus::Wait));
|
||||
@ -258,3 +265,78 @@ bool EventDAO::existsActiveByExternalId(const QString &externalId) {
|
||||
}
|
||||
return query.next();
|
||||
}
|
||||
|
||||
bool EventDAO::cancelWaitingByType(const QString &deviceId, EventType type) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
UPDATE events SET status = :cancelled, comment = 'Cancelled: replaced by new task'
|
||||
WHERE device_id = :device_id AND type = :type AND status = :wait
|
||||
)");
|
||||
query.bindValue(":cancelled", eventStatusToString(EventStatus::Error));
|
||||
query.bindValue(":device_id", deviceId);
|
||||
query.bindValue(":type", eventTypeToString(type));
|
||||
query.bindValue(":wait", eventStatusToString(EventStatus::Wait));
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при cancelWaitingByType:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<EventInfo> EventDAO::cancelWaitingByBank(const QString &deviceId, const QString &bankName) {
|
||||
// Сначала получаем задачи которые будем отменять (для отправки на сервер)
|
||||
QList<EventInfo> cancelled;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT id, account_id, device_id, external_id, amount, phone, bank_name,
|
||||
status, type, comment, json, sent_to_server, update_time, complete_time, timestamp
|
||||
FROM events
|
||||
WHERE device_id = :device_id AND bank_name = :bank_name AND status = :wait
|
||||
)");
|
||||
query.bindValue(":device_id", deviceId);
|
||||
query.bindValue(":bank_name", bankName);
|
||||
query.bindValue(":wait", eventStatusToString(EventStatus::Wait));
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
cancelled.append(parseEvent(query));
|
||||
}
|
||||
}
|
||||
|
||||
// Отменяем
|
||||
query.prepare(R"(
|
||||
UPDATE events SET status = :error, comment = 'Bank profile disabled'
|
||||
WHERE device_id = :device_id AND bank_name = :bank_name AND status = :wait
|
||||
)");
|
||||
query.bindValue(":error", eventStatusToString(EventStatus::Error));
|
||||
query.bindValue(":device_id", deviceId);
|
||||
query.bindValue(":bank_name", bankName);
|
||||
query.bindValue(":wait", eventStatusToString(EventStatus::Wait));
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при cancelWaitingByBank:" << query.lastError().text();
|
||||
}
|
||||
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
bool EventDAO::updateScreenshot(int id, const QByteArray &screenshot) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE events SET screenshot = :screenshot WHERE id = :id");
|
||||
query.bindValue(":screenshot", screenshot.isEmpty()
|
||||
? QVariant(QMetaType(QMetaType::QByteArray))
|
||||
: QVariant(screenshot));
|
||||
query.bindValue(":id", id);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при updateScreenshot:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray EventDAO::getScreenshot(int id) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("SELECT screenshot FROM events WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
if (!query.exec() || !query.next()) return {};
|
||||
return query.value(0).toByteArray();
|
||||
}
|
||||
|
||||
@ -19,6 +19,14 @@ public:
|
||||
|
||||
[[nodiscard]] static bool existsActiveByExternalId(const QString &externalId);
|
||||
|
||||
[[nodiscard]] static bool cancelWaitingByType(const QString &deviceId, EventType type);
|
||||
|
||||
[[nodiscard]] static QList<EventInfo> cancelWaitingByBank(const QString &deviceId, const QString &bankName);
|
||||
|
||||
[[nodiscard]] static bool updateScreenshot(int id, const QByteArray &screenshot);
|
||||
|
||||
[[nodiscard]] static QByteArray getScreenshot(int id);
|
||||
|
||||
[[nodiscard]] static bool updateEventAmount(int id, double amount);
|
||||
|
||||
[[nodiscard]] static bool markSentToServer(int id);
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include "AccountDAO.h"
|
||||
#include "MaterialDAO.h"
|
||||
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "DatabaseManager.h"
|
||||
#include "time/DateUtils.h"
|
||||
|
||||
bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
bool MaterialDAO::upsertAccount(const MaterialInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO accounts (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency)
|
||||
INSERT INTO materials (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency)
|
||||
VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency)
|
||||
ON CONFLICT(device_id, app_code, last_numbers)
|
||||
DO UPDATE SET
|
||||
@ -31,7 +31,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
query.bindValue(":description", info.description);
|
||||
query.bindValue(":amount", info.amount);
|
||||
query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime));
|
||||
query.bindValue(":status", accountStatusToString(info.status));
|
||||
query.bindValue(":status", materialStatusToString(info.status));
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при вставке/обновлении account:" << query.lastError().text();
|
||||
@ -41,7 +41,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountDAO::updateAccountById(
|
||||
bool MaterialDAO::updateAccountById(
|
||||
const int id,
|
||||
const QString &status,
|
||||
const QString &description,
|
||||
@ -50,7 +50,7 @@ bool AccountDAO::updateAccountById(
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE accounts
|
||||
UPDATE materials
|
||||
SET status = :status,
|
||||
description = :description,
|
||||
last_numbers = :last_numbers,
|
||||
@ -72,17 +72,17 @@ bool AccountDAO::updateAccountById(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountDAO::deleteAccount(const int id) {
|
||||
bool MaterialDAO::deleteAccount(const int id) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("DELETE FROM accounts WHERE id = :id");
|
||||
query.prepare("DELETE FROM materials WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
return query.exec();
|
||||
}
|
||||
|
||||
bool AccountDAO::updateCardNumber(const int accountId, const QString &cardNumber) {
|
||||
bool MaterialDAO::updateCardNumber(const int accountId, const QString &cardNumber) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
UPDATE accounts SET card_number = :card_number WHERE id = :id
|
||||
UPDATE materials SET card_number = :card_number WHERE id = :id
|
||||
)");
|
||||
query.bindValue(":card_number", cardNumber);
|
||||
query.bindValue(":id", accountId);
|
||||
@ -93,9 +93,9 @@ bool AccountDAO::updateCardNumber(const int accountId, const QString &cardNumber
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountDAO::updateMaterialId(const int accountId, const QString &materialId) {
|
||||
bool MaterialDAO::updateMaterialId(const int accountId, const QString &materialId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE accounts SET material_id = :material_id WHERE id = :id");
|
||||
query.prepare("UPDATE materials SET material_id = :material_id WHERE id = :id");
|
||||
query.bindValue(":material_id", materialId);
|
||||
query.bindValue(":id", accountId);
|
||||
if (!query.exec()) {
|
||||
@ -105,8 +105,8 @@ bool AccountDAO::updateMaterialId(const int accountId, const QString &materialId
|
||||
return true;
|
||||
}
|
||||
|
||||
AccountInfo parseAccount(const QSqlQuery &query) {
|
||||
AccountInfo acc;
|
||||
MaterialInfo parseAccount(const QSqlQuery &query) {
|
||||
MaterialInfo acc;
|
||||
acc.id = query.value("id").toInt();
|
||||
acc.deviceId = query.value("device_id").toString();
|
||||
acc.appCode = query.value("app_code").toString();
|
||||
@ -115,19 +115,19 @@ AccountInfo parseAccount(const QSqlQuery &query) {
|
||||
acc.materialId = query.value("material_id").toString();
|
||||
acc.amount = query.value("amount").toDouble();
|
||||
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
|
||||
acc.status = accountStatusFromString(query.value("status").toString());
|
||||
acc.status = materialStatusFromString(query.value("status").toString());
|
||||
acc.description = query.value("description").toString();
|
||||
acc.currency = query.value("currency").toString();
|
||||
return acc;
|
||||
}
|
||||
|
||||
QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QString &appName) {
|
||||
QList<AccountInfo> accounts;
|
||||
QList<MaterialInfo> MaterialDAO::getAccounts(const QString &deviceId, const QString &appName) {
|
||||
QList<MaterialInfo> accounts;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
FROM materials
|
||||
WHERE device_id = :device_id AND app_code = :app_code
|
||||
)");
|
||||
|
||||
@ -135,7 +135,7 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
query.bindValue(":app_code", appName);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении accounts:" << query.lastError().text();
|
||||
qWarning() << "Ошибка при получении materials:" << query.lastError().text();
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@ -146,12 +146,12 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
return accounts;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
MaterialInfo MaterialDAO::getAccountById(const int accountId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
FROM materials
|
||||
WHERE id = :id
|
||||
LIMIT 1
|
||||
)");
|
||||
@ -169,20 +169,20 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<AccountInfo> AccountDAO::getAccountsByAppCode(const QString &appCode) {
|
||||
QList<AccountInfo> accounts;
|
||||
QList<MaterialInfo> MaterialDAO::getAccountsByAppCode(const QString &appCode) {
|
||||
QList<MaterialInfo> accounts;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
FROM materials
|
||||
WHERE app_code = :app_code
|
||||
)");
|
||||
|
||||
query.bindValue(":app_code", appCode);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении accounts по app_code:" << query.lastError().text();
|
||||
qWarning() << "Ошибка при получении materials по app_code:" << query.lastError().text();
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@ -193,12 +193,12 @@ QList<AccountInfo> AccountDAO::getAccountsByAppCode(const QString &appCode) {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &appName, const QString &lastNumber) {
|
||||
MaterialInfo MaterialDAO::findAppAccount(const QString &deviceId, const QString &appName, const QString &lastNumber) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
FROM materials
|
||||
WHERE app_code = :appName AND last_numbers = :lastNumber AND device_id = :deviceId
|
||||
LIMIT 1
|
||||
)");
|
||||
@ -219,13 +219,13 @@ AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &a
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList AccountDAO::getActiveMaterialIds() {
|
||||
QStringList MaterialDAO::getActiveMaterialIds() {
|
||||
QStringList ids;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT a.material_id
|
||||
FROM accounts a
|
||||
JOIN applications app ON a.device_id = app.device_id AND a.app_code = app.code
|
||||
FROM materials a
|
||||
JOIN bank_profiles app ON a.device_id = app.device_id AND a.app_code = app.code
|
||||
JOIN devices d ON a.device_id = d.id
|
||||
WHERE app.status = 'active'
|
||||
AND a.material_id IS NOT NULL AND a.material_id != ''
|
||||
@ -243,13 +243,13 @@ QStringList AccountDAO::getActiveMaterialIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
QStringList AccountDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString> &excludeDevices) {
|
||||
QStringList MaterialDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString> &excludeDevices) {
|
||||
const QStringList all = getActiveMaterialIds();
|
||||
if (excludeDevices.isEmpty()) return all;
|
||||
|
||||
QStringList filtered;
|
||||
for (const QString &mid : all) {
|
||||
const AccountInfo acc = findByMaterialId(mid);
|
||||
const MaterialInfo acc = findByMaterialId(mid);
|
||||
if (!excludeDevices.contains(acc.deviceId)) {
|
||||
filtered << mid;
|
||||
}
|
||||
@ -257,12 +257,12 @@ QStringList AccountDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString>
|
||||
return filtered;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::findByMaterialId(const QString &materialId) {
|
||||
MaterialInfo MaterialDAO::findByMaterialId(const QString &materialId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
FROM materials
|
||||
WHERE material_id = :material_id
|
||||
LIMIT 1
|
||||
)");
|
||||
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
#include <QSet>
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
|
||||
class AccountDAO {
|
||||
class MaterialDAO {
|
||||
public:
|
||||
[[nodiscard]] static bool upsertAccount(const AccountInfo &info);
|
||||
[[nodiscard]] static bool upsertAccount(const MaterialInfo &info);
|
||||
|
||||
[[nodiscard]] static bool updateAccountById(
|
||||
int id,
|
||||
@ -19,19 +19,19 @@ public:
|
||||
|
||||
[[nodiscard]] static bool updateMaterialId(int accountId, const QString &materialId);
|
||||
|
||||
[[nodiscard]] static QList<AccountInfo> getAccounts(const QString &deviceId, const QString &appName);
|
||||
[[nodiscard]] static QList<MaterialInfo> getAccounts(const QString &deviceId, const QString &appName);
|
||||
|
||||
[[nodiscard]] static AccountInfo getAccountById(int accountId);
|
||||
[[nodiscard]] static MaterialInfo getAccountById(int accountId);
|
||||
|
||||
[[nodiscard]] static QList<AccountInfo> getAccountsByAppCode(const QString &appCode);
|
||||
[[nodiscard]] static QList<MaterialInfo> getAccountsByAppCode(const QString &appCode);
|
||||
|
||||
[[nodiscard]] static AccountInfo findAppAccount(
|
||||
[[nodiscard]] static MaterialInfo findAppAccount(
|
||||
const QString &deviceId,
|
||||
const QString &appName,
|
||||
const QString &lastNumber
|
||||
);
|
||||
|
||||
[[nodiscard]] static AccountInfo findByMaterialId(const QString &materialId);
|
||||
[[nodiscard]] static MaterialInfo findByMaterialId(const QString &materialId);
|
||||
|
||||
[[nodiscard]] static QStringList getActiveMaterialIds();
|
||||
|
||||
1
docs/api/openapi.json
Normal file
1
docs/api/openapi.json
Normal file
File diff suppressed because one or more lines are too long
26
main.cpp
26
main.cpp
@ -1,7 +1,7 @@
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "views/MainWindow.h"
|
||||
|
||||
#include "database/DatabaseManager.h"
|
||||
@ -27,6 +27,7 @@
|
||||
#include "black/PayByCardScript.h"
|
||||
#include "ozon/PayByPhoneScript.h"
|
||||
#include "widget/loader/ErrorWindow.h"
|
||||
#include "widget/loader/LoaderDialog.h"
|
||||
#include "widget/loader/LoaderWindow.h"
|
||||
#include "widget/loader/RegistrationWindow.h"
|
||||
#include <QMessageBox>
|
||||
@ -79,12 +80,12 @@ void startEventHandler(const QCoreApplication &app) {
|
||||
|
||||
void startPayByPhoneTest() {
|
||||
// Берём первый девайс с ozon аккаунтом
|
||||
QList<AccountInfo> accounts = AccountDAO::getAccountsByAppCode("ozon");
|
||||
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("ozon");
|
||||
if (accounts.isEmpty()) {
|
||||
qWarning() << "[PayByPhoneTest] No ozon accounts found";
|
||||
return;
|
||||
}
|
||||
AccountInfo account = accounts.first();
|
||||
MaterialInfo account = accounts.first();
|
||||
qDebug() << "[PayByPhoneTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
||||
|
||||
auto *thread = new QThread(nullptr);
|
||||
@ -107,12 +108,12 @@ void startPayByPhoneTest() {
|
||||
}
|
||||
|
||||
void startBlackPayByCardTest() {
|
||||
QList<AccountInfo> accounts = AccountDAO::getAccountsByAppCode("black");
|
||||
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("black");
|
||||
if (accounts.isEmpty()) {
|
||||
qWarning() << "[BlackPayByCardTest] No black accounts found";
|
||||
return;
|
||||
}
|
||||
AccountInfo account = accounts.first();
|
||||
MaterialInfo account = accounts.first();
|
||||
qDebug() << "[BlackPayByCardTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
||||
|
||||
auto *thread = new QThread(nullptr);
|
||||
@ -143,7 +144,22 @@ void startNetworkService(const QCoreApplication &app) {
|
||||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||||
QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
||||
auto *loader = new LoaderDialog();
|
||||
loader->setWindowTitle("Завершение...");
|
||||
loader->show();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
QEventLoop loop;
|
||||
QObject::connect(networkService, &NetworkService::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
// Таймаут на случай если сервер недоступен
|
||||
QTimer::singleShot(10000, &loop, &QEventLoop::quit);
|
||||
|
||||
networkService->stop();
|
||||
loop.exec();
|
||||
|
||||
loader->close();
|
||||
delete loader;
|
||||
});
|
||||
|
||||
// Сигнал эмитируется из фонового потока → Qt::QueuedConnection доставит его в главный поток
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
-- ============================================================
|
||||
-- Версия 1 (user_version = 1): начальная схема
|
||||
-- Схема БД ARCDeskProject
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices
|
||||
(
|
||||
id TEXT PRIMARY KEY,
|
||||
api_id TEXT,
|
||||
adb_serial TEXT,
|
||||
status TEXT,
|
||||
name TEXT,
|
||||
android TEXT,
|
||||
@ -17,7 +18,7 @@ CREATE TABLE IF NOT EXISTS devices
|
||||
image BLOB
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS applications
|
||||
CREATE TABLE IF NOT EXISTS bank_profiles
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
@ -29,12 +30,18 @@ CREATE TABLE IF NOT EXISTS applications
|
||||
status TEXT DEFAULT 'off',
|
||||
install TEXT DEFAULT 'no',
|
||||
comment TEXT,
|
||||
phone TEXT DEFAULT '',
|
||||
email TEXT DEFAULT '',
|
||||
full_name TEXT DEFAULT '',
|
||||
bank_profile_id TEXT DEFAULT '',
|
||||
currency TEXT DEFAULT '',
|
||||
amount REAL DEFAULT 0,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, code)
|
||||
);
|
||||
|
||||
-- app_code: код банка ("ozon", "black") — не путать с человекочитаемым name
|
||||
CREATE TABLE IF NOT EXISTS accounts
|
||||
CREATE TABLE IF NOT EXISTS materials
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
@ -45,6 +52,8 @@ CREATE TABLE IF NOT EXISTS accounts
|
||||
currency TEXT DEFAULT '',
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
);
|
||||
@ -70,14 +79,15 @@ CREATE TABLE IF NOT EXISTS transactions
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
|
||||
receipt_image BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS events
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
external_id INTEGER,
|
||||
external_id TEXT,
|
||||
device_id TEXT,
|
||||
amount REAL,
|
||||
phone TEXT,
|
||||
@ -88,15 +98,46 @@ CREATE TABLE IF NOT EXISTS events
|
||||
update_time DATETIME,
|
||||
complete_time DATETIME,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (external_id)
|
||||
json TEXT DEFAULT '',
|
||||
sent_to_server INTEGER DEFAULT 0,
|
||||
screenshot BLOB,
|
||||
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- Версия 2 (user_version = 2): currency + rename app_name→app_code
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS banks
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
);
|
||||
|
||||
-- Для существующих БД (применяется автоматически при старте):
|
||||
-- ALTER TABLE accounts ADD COLUMN currency TEXT DEFAULT '';
|
||||
-- ALTER TABLE accounts RENAME COLUMN app_name TO app_code;
|
||||
CREATE TABLE IF NOT EXISTS app_logs
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
screenshot BLOB,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS general_logs
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
level TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_general_logs_id ON general_logs (id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings
|
||||
(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
);
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
enum class AccountStatus {
|
||||
Active,
|
||||
Off
|
||||
};
|
||||
|
||||
inline QString accountStatusToString(const AccountStatus status) {
|
||||
switch (status) {
|
||||
case AccountStatus::Active: return "active";
|
||||
case AccountStatus::Off: return "off";
|
||||
default: return "active";
|
||||
}
|
||||
}
|
||||
|
||||
inline QString accountStatusToUiString(const AccountStatus status) {
|
||||
switch (status) {
|
||||
case AccountStatus::Active: return "в работе";
|
||||
case AccountStatus::Off: return "выключена";
|
||||
default: return "в работе";
|
||||
}
|
||||
}
|
||||
|
||||
inline AccountStatus accountStatusFromString(const QString &str) {
|
||||
if (str == "active") return AccountStatus::Active;
|
||||
if (str == "off") return AccountStatus::Off;
|
||||
return AccountStatus::Active;
|
||||
}
|
||||
|
||||
struct AccountInfo {
|
||||
int id = -1;
|
||||
QString deviceId; // code
|
||||
QString appCode;
|
||||
QString lastNumbers;
|
||||
QString cardNumber; // full 16-digit card number
|
||||
QString materialId;
|
||||
QString currency;
|
||||
QString description;
|
||||
double amount = 0.0;
|
||||
QDateTime updateTime;
|
||||
AccountStatus status = AccountStatus::Active;
|
||||
};
|
||||
@ -4,7 +4,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
|
||||
struct ApplicationInfo {
|
||||
struct BankProfileInfo {
|
||||
int id = -1;
|
||||
bool install = false;
|
||||
bool pinCodeChecked = false;
|
||||
@ -3,8 +3,9 @@
|
||||
#include <QDateTime>
|
||||
|
||||
struct DeviceInfo {
|
||||
QString id; // ADB device id (e.g. "emulator-5554")
|
||||
QString apiId; // UUID from backend API (DeviceResponseModel.id)
|
||||
QString id; // android_id (unique device identifier)
|
||||
QString apiId; // UUID from backend API (DeviceResponseModel.id)
|
||||
QString adbSerial; // ADB serial (e.g. "emulator-5554", "7dfc444d43fbeab7")
|
||||
QString status;
|
||||
QString name;
|
||||
QString android;
|
||||
|
||||
44
models/db/MaterialInfo.h
Normal file
44
models/db/MaterialInfo.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
enum class MaterialStatus {
|
||||
Active,
|
||||
Off
|
||||
};
|
||||
|
||||
inline QString materialStatusToString(const MaterialStatus status) {
|
||||
switch (status) {
|
||||
case MaterialStatus::Active: return "active";
|
||||
case MaterialStatus::Off: return "off";
|
||||
default: return "active";
|
||||
}
|
||||
}
|
||||
|
||||
inline QString materialStatusToUiString(const MaterialStatus status) {
|
||||
switch (status) {
|
||||
case MaterialStatus::Active: return "в работе";
|
||||
case MaterialStatus::Off: return "выключена";
|
||||
default: return "в работе";
|
||||
}
|
||||
}
|
||||
|
||||
inline MaterialStatus materialStatusFromString(const QString &str) {
|
||||
if (str == "active") return MaterialStatus::Active;
|
||||
if (str == "off") return MaterialStatus::Off;
|
||||
return MaterialStatus::Active;
|
||||
}
|
||||
|
||||
struct MaterialInfo {
|
||||
int id = -1;
|
||||
QString deviceId; // code
|
||||
QString appCode;
|
||||
QString lastNumbers;
|
||||
QString cardNumber; // full 16-digit card number
|
||||
QString materialId;
|
||||
QString currency;
|
||||
QString description;
|
||||
double amount = 0.0;
|
||||
QDateTime updateTime;
|
||||
MaterialStatus status = MaterialStatus::Active;
|
||||
};
|
||||
@ -19,7 +19,8 @@
|
||||
#include <db/DeviceInfo.h>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
|
||||
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
||||
@ -181,12 +182,13 @@ QString DeviceScreener::createDeviceOnApi(const DeviceInfo &device) {
|
||||
}
|
||||
|
||||
QJsonObject body;
|
||||
body["desktop_id"] = desktopId;
|
||||
body["status"] = "ONLINE";
|
||||
body["name"] = device.name;
|
||||
body["screen_width"] = device.screenWidth;
|
||||
body["screen_height"] = device.screenHeight;
|
||||
body["battery"] = device.battery;
|
||||
body["desktop_id"] = desktopId;
|
||||
body["bank_profile_id"] = QJsonValue::Null;
|
||||
body["status"] = "ONLINE";
|
||||
body["name"] = device.name;
|
||||
body["screen_width"] = device.screenWidth;
|
||||
body["screen_height"] = device.screenHeight;
|
||||
body["battery"] = device.battery;
|
||||
|
||||
const QJsonValue result = apiPost("/api/v1/device/", body);
|
||||
if (!result.isNull() && !result.isUndefined()) {
|
||||
@ -239,25 +241,42 @@ void DeviceScreener::postScreenshotToApi(const QString &apiId, const QByteArray
|
||||
}
|
||||
|
||||
void DeviceScreener::updateDeviceOnApi(const DeviceInfo &device) {
|
||||
// Находим первый активный bank_profile для этого устройства
|
||||
QJsonValue bankProfileId = QJsonValue::Null;
|
||||
const QList<BankProfileInfo> apps = BankProfileDAO::getAllApplicationsByDeviceId(device.id);
|
||||
for (const BankProfileInfo &app : apps) {
|
||||
if (!app.bankProfileId.isEmpty() && app.status == "active") {
|
||||
bankProfileId = app.bankProfileId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject body;
|
||||
body["status"] = (device.status == "device") ? "ONLINE" : "OFFLINE";
|
||||
body["battery"] = device.battery;
|
||||
body["update_time"] = QDateTime::currentSecsSinceEpoch();
|
||||
body["status"] = (device.status == "device") ? "ONLINE" : "OFFLINE";
|
||||
body["bank_profile_id"] = bankProfileId;
|
||||
body["battery"] = device.battery;
|
||||
body["update_time"] = QDateTime::currentSecsSinceEpoch();
|
||||
apiPatch("/api/v1/device/" + device.apiId, body);
|
||||
}
|
||||
|
||||
void DeviceScreener::toggleBankProfilesOnApi(const QString &deviceId, bool enable) {
|
||||
const QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(deviceId);
|
||||
for (const ApplicationInfo &app : apps) {
|
||||
if (enable) return; // не включаем автоматически при появлении устройства
|
||||
|
||||
const QList<BankProfileInfo> apps = BankProfileDAO::getAllApplicationsByDeviceId(deviceId);
|
||||
for (const BankProfileInfo &app : apps) {
|
||||
if (app.bankProfileId.isEmpty()) continue;
|
||||
if (app.status != "active") continue;
|
||||
|
||||
// При включении — только если приложение активно в системе
|
||||
if (enable && app.status != "active") continue;
|
||||
apiPatch("/api/v1/profile/bank_profile/" + app.bankProfileId + "/false", QJsonObject());
|
||||
|
||||
const QString enableStr = enable ? "true" : "false";
|
||||
const QString path = "/api/v1/profile/bank_profile/" + app.bankProfileId + "/" + enableStr;
|
||||
apiPatch(path, QJsonObject());
|
||||
const QList<MaterialInfo> materials = MaterialDAO::getAccounts(deviceId, app.code);
|
||||
for (const MaterialInfo &mat : materials) {
|
||||
if (mat.materialId.isEmpty()) continue;
|
||||
apiPatch("/api/v1/profile/material/" + mat.materialId + "/false", QJsonObject());
|
||||
}
|
||||
}
|
||||
|
||||
BankProfileDAO::deactivateAppsForDevice(deviceId);
|
||||
}
|
||||
|
||||
QJsonValue DeviceScreener::apiGet(const QString &path) {
|
||||
@ -304,24 +323,39 @@ void DeviceScreener::start() {
|
||||
QList<QPair<QString, QString> > devices = readAdbDevices();
|
||||
QStringList onlineIds;
|
||||
QList<DeviceInfo> deviceInfos;
|
||||
for (const auto &[id, status]: devices) {
|
||||
for (const auto &[adbSerial, status]: devices) {
|
||||
DeviceInfo device;
|
||||
device.id = id;
|
||||
bool isChecked = true;
|
||||
|
||||
if (status == "device") {
|
||||
device = readDeviceInfo(adbSerial);
|
||||
|
||||
// Проверяем: есть ли уже устройство с таким id в БД (с apiId)
|
||||
const DeviceInfo existingById = DeviceDAO::getDeviceById(device.id);
|
||||
if (!existingById.id.isEmpty() && !existingById.apiId.isEmpty()) {
|
||||
device.apiId = existingById.apiId;
|
||||
} else {
|
||||
// Маппинг с восстановленным устройством — только по name (точное совпадение)
|
||||
const DeviceInfo restored = DeviceDAO::findByName(device.name);
|
||||
if (!restored.id.isEmpty() && restored.id != device.id && !restored.apiId.isEmpty()) {
|
||||
device.apiId = restored.apiId;
|
||||
DeviceDAO::deleteDevice(restored.id);
|
||||
qDebug() << "[DeviceScreener] matched restored device:" << restored.id << "->" << device.id
|
||||
<< "apiId:" << device.apiId;
|
||||
}
|
||||
}
|
||||
|
||||
onlineIds << device.id;
|
||||
const bool isChecked = prevOnline.contains(id);
|
||||
device = readDeviceInfo(id, isChecked);
|
||||
device.image = takeScreenshot(id);
|
||||
isChecked = prevOnline.contains(device.id);
|
||||
|
||||
device.image = takeScreenshot(adbSerial);
|
||||
if (!device.image.isEmpty()) {
|
||||
if (!DeviceDAO::updateImage(id, device.image)) {
|
||||
if (!DeviceDAO::updateImage(device.id, device.image)) {
|
||||
qDebug() << "Cant update screenshot";
|
||||
}
|
||||
}
|
||||
if (!isChecked) {
|
||||
// Новое устройство — включаем active профили
|
||||
toggleBankProfilesOnApi(id, true);
|
||||
}
|
||||
} else {
|
||||
device.id = adbSerial;
|
||||
}
|
||||
device.status = status;
|
||||
device.updateTime = QDateTime::currentDateTime();
|
||||
@ -330,6 +364,16 @@ void DeviceScreener::start() {
|
||||
if (!DeviceDAO::upsertDevice(device)) {
|
||||
qDebug() << "Cant update device";
|
||||
}
|
||||
|
||||
if (!isChecked) {
|
||||
// Новое устройство — проверяем приложения (после upsert, чтобы FK не падал)
|
||||
QSet<QString> apps = AdbUtils::getListApps(adbSerial);
|
||||
if (!BankProfileDAO::checkInstalledApps(device.id, apps)) {
|
||||
qWarning() << "Cant check installed apps";
|
||||
} else {
|
||||
qDebug() << "Apps checked:" << device.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизация с API
|
||||
@ -361,10 +405,17 @@ void DeviceScreener::start() {
|
||||
qDebug() << "Cant mark devices offline";
|
||||
}
|
||||
|
||||
// Было онлайн в БД, сейчас офлайн — выключаем профили на сервере
|
||||
// Было онлайн в БД, сейчас офлайн — выключаем профили и обновляем статус на сервере
|
||||
for (const QString &id : prevOnline) {
|
||||
if (!onlineIds.contains(id)) {
|
||||
toggleBankProfilesOnApi(id, false);
|
||||
// Отправляем OFFLINE статус на сервер
|
||||
const DeviceInfo offlineDevice = DeviceDAO::getDeviceById(id);
|
||||
if (!offlineDevice.apiId.isEmpty()) {
|
||||
DeviceInfo offlineCopy = offlineDevice;
|
||||
offlineCopy.status = "offline";
|
||||
updateDeviceOnApi(offlineCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,9 +503,8 @@ QList<QPair<QString, QString> > DeviceScreener::readAdbDevices() {
|
||||
static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))");
|
||||
static const QRegularExpression batteryRegex(R"(level:\s*(\d+))");
|
||||
|
||||
DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool isChecked) {
|
||||
DeviceInfo DeviceScreener::readDeviceInfo(const QString &adbSerial) {
|
||||
DeviceInfo info;
|
||||
info.id = deviceId;
|
||||
info.screenWidth = 0;
|
||||
info.screenHeight = 0;
|
||||
info.battery = 0;
|
||||
@ -462,12 +512,17 @@ DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool is
|
||||
auto runAdbCommand = [&](const QStringList &args) -> QString {
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
||||
process.start(AdbUtils::adbPath(), QStringList{"-s", deviceId} + args);
|
||||
process.start(AdbUtils::adbPath(), QStringList{"-s", adbSerial} + args);
|
||||
process.waitForFinished();
|
||||
return QString::fromUtf8(process.readAllStandardOutput()).trimmed();
|
||||
};
|
||||
|
||||
info.name = runAdbCommand({"shell", "getprop", "ro.product.model"});
|
||||
const QString androidId = runAdbCommand({"shell", "settings", "get", "secure", "android_id"});
|
||||
const QString model = runAdbCommand({"shell", "getprop", "ro.product.model"});
|
||||
|
||||
info.id = androidId.isEmpty() ? adbSerial : androidId;
|
||||
info.adbSerial = adbSerial;
|
||||
info.name = model + "-" + info.id;
|
||||
info.android = runAdbCommand({"shell", "getprop", "ro.build.version.release"});
|
||||
|
||||
// Размер экрана
|
||||
@ -485,15 +540,6 @@ DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool is
|
||||
info.battery = match.captured(1).toInt();
|
||||
}
|
||||
|
||||
if (!isChecked) {
|
||||
QSet<QString> apps = AdbUtils::getListApps(deviceId);
|
||||
if (!ApplicationDAO::checkInstalledApps(deviceId, apps)) {
|
||||
qWarning() << "Cant check installed apps";
|
||||
} else {
|
||||
qDebug() << "Apps checked: " + deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
info.updateTime = QDateTime::currentDateTime();
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ private:
|
||||
|
||||
static QByteArray takeScreenshot(const QString &deviceId);
|
||||
|
||||
static DeviceInfo readDeviceInfo(const QString &deviceId, bool isChecked);
|
||||
static DeviceInfo readDeviceInfo(const QString &adbSerial);
|
||||
|
||||
void postDevicesData(const QList<DeviceInfo> &devices);
|
||||
|
||||
|
||||
@ -10,13 +10,13 @@
|
||||
#include <QTimer>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/BankDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "black/GetLastTransactionsScript.h"
|
||||
@ -50,7 +50,7 @@ void EventHandler::start() {
|
||||
|
||||
// Запрашиваем новые задачи только для устройств без pending events
|
||||
const QSet<QString> busyDevices = EventDAO::getDevicesWithPendingEvents();
|
||||
const QStringList materialIds = AccountDAO::getActiveMaterialIdsExcludingDevices(busyDevices);
|
||||
const QStringList materialIds = MaterialDAO::getActiveMaterialIdsExcludingDevices(busyDevices);
|
||||
if (!materialIds.isEmpty()) {
|
||||
m_network->getTasks(materialIds);
|
||||
}
|
||||
@ -99,13 +99,18 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
||||
}
|
||||
|
||||
// Ищем аккаунт по material_id напрямую
|
||||
const AccountInfo account = AccountDAO::findByMaterialId(materialId);
|
||||
const MaterialInfo account = MaterialDAO::findByMaterialId(materialId);
|
||||
if (account.id == -1) {
|
||||
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
||||
sendTaskError(typeStr, materialId, bankName, "Account not found for material_id: " + materialId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// FETCH_PROFILE: отменяем старые ожидающие задачи такого же типа на этом устройстве
|
||||
if (eventType == EventType::FetchProfile) {
|
||||
EventDAO::cancelWaitingByType(account.deviceId, EventType::FetchProfile);
|
||||
}
|
||||
|
||||
EventInfo event;
|
||||
event.status = EventStatus::Wait;
|
||||
event.type = eventType;
|
||||
@ -227,18 +232,44 @@ static void sendEventStatus(const QString &deviceId, const QString &externalId,
|
||||
}
|
||||
|
||||
void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QString &result) {
|
||||
// FETCH_TRANSACTIONS: если транзакция не найдена и прошло менее 10 минут — повторяем
|
||||
if (!result.isEmpty() && event.type == EventType::FetchTransactions
|
||||
&& result.startsWith("Transaction not found")) {
|
||||
const qint64 elapsedSecs = event.timestamp.secsTo(QDateTime::currentDateTimeUtc());
|
||||
if (elapsedSecs < 600) {
|
||||
qDebug() << "[EventHandler] FETCH_TRANSACTIONS not found, requeueing (elapsed:" << elapsedSecs << "s)";
|
||||
EventDAO::updateEvent(event.id, EventStatus::Wait, result);
|
||||
m_threads.remove(key);
|
||||
// Повтор через 15 секунд
|
||||
QTimer::singleShot(15000, this, [this, key]() {
|
||||
processNextTask(key);
|
||||
});
|
||||
return;
|
||||
}
|
||||
qWarning() << "[EventHandler] FETCH_TRANSACTIONS timeout (elapsed:" << elapsedSecs << "s), sending error";
|
||||
}
|
||||
|
||||
const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
|
||||
|
||||
if (!EventDAO::updateEvent(event.id, newStatus, result)) {
|
||||
qCritical() << "[EventHandler] Failed to update event:" << event.id;
|
||||
} else {
|
||||
if (newStatus == EventStatus::Error) {
|
||||
// Сохраняем скриншот устройства при ошибке
|
||||
if (!event.deviceId.isEmpty()) {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(event.deviceId);
|
||||
const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial;
|
||||
const QByteArray screenshot = AdbUtils::captureScreenshotBytes(adbSerial);
|
||||
if (!screenshot.isEmpty()) {
|
||||
EventDAO::updateScreenshot(event.id, screenshot);
|
||||
}
|
||||
}
|
||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result);
|
||||
EventDAO::markSentToServer(event.id);
|
||||
}
|
||||
|
||||
if (newStatus != EventStatus::Error) {
|
||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
||||
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
||||
const QMap<QString, int> currencyCodes = {
|
||||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}
|
||||
};
|
||||
@ -353,7 +384,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
||||
processNextTask(key);
|
||||
}
|
||||
|
||||
void EventHandler::startThreadForTask(const AccountInfo &account, const EventInfo &event) {
|
||||
void EventHandler::startThreadForTask(const MaterialInfo &account, const EventInfo &event) {
|
||||
const auto thr = new QThread(this);
|
||||
const QString key = account.deviceId;
|
||||
|
||||
@ -373,17 +404,27 @@ void EventHandler::startThreadForTask(const AccountInfo &account, const EventInf
|
||||
};
|
||||
|
||||
const QString &appCode = account.appCode;
|
||||
const QString &deviceId = account.deviceId;
|
||||
// Скриптам нужен ADB serial для команд, не android_id
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(account.deviceId);
|
||||
const QString adbSerial = device.adbSerial.isEmpty() ? account.deviceId : device.adbSerial;
|
||||
|
||||
if (event.type == EventType::FetchProfile) {
|
||||
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(deviceId, appCode));
|
||||
else if (appCode == "black") setup(new Black::LoginAndCheckAccountsScript(deviceId, appCode));
|
||||
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(adbSerial, appCode, nullptr, true));
|
||||
else if (appCode == "black") setup(new Black::LoginAndCheckAccountsScript(adbSerial, appCode));
|
||||
else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode;
|
||||
}
|
||||
|
||||
if (event.type == EventType::FetchTransactions) {
|
||||
if (appCode == "ozon") setup(new Ozon::GetLastTransactionsScript(deviceId, appCode));
|
||||
else if (appCode == "black") setup(new Black::GetLastTransactionsScript(deviceId, appCode));
|
||||
// Парсим amount и created_at из body задачи
|
||||
const QJsonObject taskJson = QJsonDocument::fromJson(event.json.toUtf8()).object();
|
||||
const QJsonObject body = taskJson["body"].toObject();
|
||||
const double searchAmount = body["amount"].toDouble() / 100.0; // копейки → рубли
|
||||
const QDateTime searchTime = QDateTime::fromString(body["created_at"].toString(), Qt::ISODate);
|
||||
|
||||
if (appCode == "ozon")
|
||||
setup(new Ozon::GetLastTransactionsScript(adbSerial, appCode, 0, searchAmount, searchTime));
|
||||
else if (appCode == "black")
|
||||
setup(new Black::GetLastTransactionsScript(adbSerial, appCode));
|
||||
else qWarning() << "[EventHandler] FETCH_TRANSACTIONS: unknown appCode:" << appCode;
|
||||
}
|
||||
|
||||
@ -448,8 +489,8 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, account.appCode);
|
||||
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, account.appCode);
|
||||
if (app.status != "active") {
|
||||
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "db/EventInfo.h"
|
||||
|
||||
class NetworkService;
|
||||
@ -39,7 +39,7 @@ private:
|
||||
|
||||
void updateEventThread(const QString &key, const EventInfo &event, const QString &result);
|
||||
|
||||
void startThreadForTask(const AccountInfo &account, const EventInfo &event);
|
||||
void startThreadForTask(const MaterialInfo &account, const EventInfo &event);
|
||||
|
||||
void processNextTask(const QString &deviceId);
|
||||
};
|
||||
|
||||
@ -11,8 +11,9 @@
|
||||
#include <QTimer>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/BankDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
@ -181,7 +182,7 @@ void NetworkService::getPayments() {
|
||||
QString deviceId = obj["deviceId"].toString();
|
||||
QString appName = obj["appName"].toString();
|
||||
QString lastNumbers = obj["lastNumbers"].toString();
|
||||
AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumbers);
|
||||
MaterialInfo account = MaterialDAO::findAppAccount(deviceId, appName, lastNumbers);
|
||||
|
||||
eventInfo.accountId = account.id;
|
||||
eventInfo.deviceId = account.deviceId;
|
||||
@ -291,10 +292,37 @@ void NetworkService::loginAndSetup() {
|
||||
const qint64 expiresIn = static_cast<qint64>(loginData["expires_in"].toDouble());
|
||||
SettingsDAO::setLong("token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||
|
||||
// Шаг 2: проверяем есть ли desktop_id
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
// Шаг 2: получаем актуальный desktop с сервера
|
||||
QString desktopId = SettingsDAO::get("desktop_id");
|
||||
|
||||
// Всегда проверяем список десктопов — desktop_id мог устареть или быть удалён
|
||||
const QJsonValue existingDesktops = getJson(m_apiBase + m_pathDesktopCreate, true);
|
||||
if (!existingDesktops.isUndefined() && !existingDesktops.isNull() && existingDesktops.isArray()) {
|
||||
const QJsonArray desktopsArray = existingDesktops.toArray();
|
||||
QString foundId;
|
||||
QString foundProfileId;
|
||||
for (const QJsonValue &dVal : desktopsArray) {
|
||||
const QJsonObject d = dVal.toObject();
|
||||
if (d["hidden"].toBool(false)) continue;
|
||||
|
||||
foundId = d["id"].toString();
|
||||
foundProfileId = d["profile_id"].toString();
|
||||
if (!foundId.isEmpty()) break;
|
||||
}
|
||||
|
||||
if (!foundId.isEmpty() && foundId != desktopId) {
|
||||
// Нашли актуальный десктоп, отличается от сохранённого — обновляем
|
||||
qDebug() << "[loginAndSetup] desktop_id updated:" << desktopId << "->" << foundId;
|
||||
desktopId = foundId;
|
||||
SettingsDAO::set("desktop_id", desktopId);
|
||||
SettingsDAO::set("profile_id", foundProfileId);
|
||||
} else if (!foundId.isEmpty()) {
|
||||
qDebug() << "[loginAndSetup] desktop_id confirmed:" << desktopId;
|
||||
}
|
||||
}
|
||||
|
||||
if (desktopId.isEmpty()) {
|
||||
// Шаг 3: создаём desktop
|
||||
// Десктопов на сервере нет — создаём новый
|
||||
QJsonObject desktopBody;
|
||||
desktopBody["blue_token"] = QJsonValue::Null;
|
||||
desktopBody["name"] = QJsonValue::Null;
|
||||
@ -310,13 +338,38 @@ void NetworkService::loginAndSetup() {
|
||||
}
|
||||
|
||||
const QJsonObject desktopData = desktopResult.toObject();
|
||||
SettingsDAO::set("desktop_id", desktopData["id"].toString());
|
||||
desktopId = desktopData["id"].toString();
|
||||
SettingsDAO::set("desktop_id", desktopId);
|
||||
SettingsDAO::set("profile_id", desktopData["profile_id"].toString());
|
||||
}
|
||||
|
||||
// Шаг 4: синхронизация устройств и профилей
|
||||
// Шаг 3: синхронизация устройств с сервера
|
||||
syncDevicesFromServer(desktopId);
|
||||
|
||||
// Шаг 4: синхронизация банковских профилей и материалов с сервера
|
||||
syncBankProfilesFromServer();
|
||||
|
||||
// Шаг 5: загрузка справочника банков если пустой
|
||||
syncBanksIfEmpty();
|
||||
|
||||
// Шаг 6: синхронизация статусов профилей/материалов с учётом онлайн-устройств
|
||||
syncCurrentProfile();
|
||||
|
||||
// Шаг 7: отправляем OFFLINE статус всех устройств (DeviceScreener обновит на ONLINE при обнаружении)
|
||||
{
|
||||
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
|
||||
for (const DeviceInfo &d : allDevices) {
|
||||
if (d.apiId.isEmpty()) continue;
|
||||
QJsonObject body;
|
||||
body["status"] = "OFFLINE";
|
||||
body["bank_profile_id"] = QJsonValue::Null;
|
||||
body["battery"] = d.battery;
|
||||
body["update_time"] = QDateTime::currentSecsSinceEpoch();
|
||||
patchJson(m_apiBase + m_pathDevice + d.apiId, body, true);
|
||||
qDebug() << "[loginAndSetup] sent OFFLINE for device" << d.id << d.apiId;
|
||||
}
|
||||
}
|
||||
|
||||
emit finishedWithResult(1);
|
||||
emit finished();
|
||||
}
|
||||
@ -412,6 +465,49 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonValue NetworkService::deleteJson(const QString &path, bool withAuth) {
|
||||
const QUrl url(path);
|
||||
QNetworkRequest request(url);
|
||||
if (withAuth && !m_accessToken.isEmpty()) {
|
||||
request.setRawHeader("Authorization", ("Bearer " + m_accessToken).toUtf8());
|
||||
}
|
||||
|
||||
QNetworkReply *reply = m_manager->deleteResource(request);
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
QJsonValue result;
|
||||
if (!timeout.isActive()) {
|
||||
qCritical() << "Таймаут запроса (DELETE):" << path;
|
||||
reply->abort();
|
||||
result = QJsonValue::Undefined;
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
qCritical() << "Network error (DELETE):" << reply->errorString() << path;
|
||||
result = QJsonValue::Undefined;
|
||||
} else {
|
||||
const QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
|
||||
if (err.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
const QJsonObject root = doc.object();
|
||||
if (root["success"].toBool()) {
|
||||
result = root["data"];
|
||||
} else {
|
||||
qCritical() << "API error (DELETE):" << root;
|
||||
result = QJsonValue::Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonValue NetworkService::getJson(const QString &path, bool withAuth) {
|
||||
const QUrl url(path);
|
||||
QNetworkRequest request(url);
|
||||
@ -457,6 +553,255 @@ QJsonValue NetworkService::getJson(const QString &path, bool withAuth) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void NetworkService::syncBankProfilesFromServer() {
|
||||
const QJsonValue profileResult = getJson(m_apiBase + m_pathCurrentProfile, true);
|
||||
if (profileResult.isUndefined() || profileResult.isNull()) {
|
||||
qWarning() << "[syncBankProfilesFromServer] failed to get current_profile";
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject profileData = profileResult.toObject();
|
||||
const QJsonArray bankProfiles = profileData["bank_profiles"].toArray();
|
||||
|
||||
// Маппинг apiId → локальный device_id
|
||||
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
|
||||
QMap<QString, QString> apiIdToLocalId;
|
||||
for (const DeviceInfo &d : allDevices) {
|
||||
if (!d.apiId.isEmpty()) {
|
||||
apiIdToLocalId[d.apiId] = d.id;
|
||||
}
|
||||
}
|
||||
|
||||
const QSettings configSettings("config.ini", QSettings::IniFormat);
|
||||
|
||||
for (const QJsonValue &bpVal : bankProfiles) {
|
||||
const QJsonObject bp = bpVal.toObject();
|
||||
const QString bpId = bp["id"].toString();
|
||||
const QString bankName = bp["bank_name"].toString();
|
||||
const QString bpState = bp["state"].toString();
|
||||
const QString phone = bp["phone_number"].toString();
|
||||
const QString firstName = bp["first_name"].toString();
|
||||
const QString middleName = bp["middle_name"].toString();
|
||||
const QString lastName = bp["last_name"].toString();
|
||||
const QString currency = bp["currency"].isNull() ? "" : QString::number(bp["currency"].toInt());
|
||||
const QString serverDeviceId = bp["device_id"].toString(); // API UUID устройства
|
||||
|
||||
if (bpId.isEmpty() || bankName.isEmpty()) continue;
|
||||
|
||||
// Определяем локальный device_id по API UUID
|
||||
const QString localDeviceId = apiIdToLocalId.value(serverDeviceId);
|
||||
if (localDeviceId.isEmpty()) {
|
||||
qDebug() << "[syncBankProfilesFromServer] device not found locally for api_id" << serverDeviceId << ", skipping bp" << bpId;
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString fullName = (firstName + " " + (middleName.isEmpty() ? "" : middleName + " ") + lastName).trimmed();
|
||||
|
||||
BankProfileInfo app = BankProfileDAO::getApplication(localDeviceId, bankName);
|
||||
if (app.id < 0) {
|
||||
// Приложение не обнаружено на устройстве — создаём запись
|
||||
BankProfileInfo newApp;
|
||||
newApp.deviceId = localDeviceId;
|
||||
newApp.code = bankName;
|
||||
newApp.name = configSettings.value(bankName + "/name", bankName).toString();
|
||||
newApp.package = configSettings.value(bankName + "/android_package_name").toString();
|
||||
newApp.status = (bpState == "disabled") ? "off" : "active";
|
||||
newApp.install = false;
|
||||
newApp.pinCodeChecked = false;
|
||||
newApp.bankProfileId = bpId;
|
||||
newApp.fullName = fullName;
|
||||
newApp.phone = phone;
|
||||
newApp.currency = currency.isEmpty()
|
||||
? configSettings.value(bankName + "/currency").toString()
|
||||
: currency;
|
||||
|
||||
if (BankProfileDAO::upsertApplication(newApp)) {
|
||||
app = BankProfileDAO::getApplication(localDeviceId, bankName);
|
||||
qDebug() << "[syncBankProfilesFromServer] created app" << app.id << bankName << "device" << localDeviceId;
|
||||
} else {
|
||||
qWarning() << "[syncBankProfilesFromServer] failed to create app" << bankName << "device" << localDeviceId;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Application существует — привязываем bank_profile_id если нужно
|
||||
if (app.bankProfileId != bpId) {
|
||||
BankProfileDAO::updateBankProfileId(app.id, bpId);
|
||||
qDebug() << "[syncBankProfilesFromServer] linked bp" << bpId << "to app" << app.id;
|
||||
}
|
||||
if (app.fullName.isEmpty() || app.phone.isEmpty()) {
|
||||
BankProfileDAO::updateProfileInfo(app.id, fullName, phone);
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизируем materials → accounts
|
||||
const QJsonArray materials = bp["materials"].toArray();
|
||||
for (const QJsonValue &matVal : materials) {
|
||||
const QJsonObject mat = matVal.toObject();
|
||||
const QString matId = mat["id"].toString();
|
||||
const QString cardNumber = mat["card_number"].toString();
|
||||
if (matId.isEmpty()) continue;
|
||||
|
||||
// Уже есть account с этим material_id — пропускаем
|
||||
const MaterialInfo existing = MaterialDAO::findByMaterialId(matId);
|
||||
if (existing.id >= 0) continue;
|
||||
|
||||
const QString lastNumbers = cardNumber.length() >= 4 ? cardNumber.right(4) : cardNumber;
|
||||
|
||||
// Ищем существующий account по last_numbers
|
||||
MaterialInfo acc = MaterialDAO::findAppAccount(localDeviceId, bankName, lastNumbers);
|
||||
if (acc.id >= 0) {
|
||||
if (acc.materialId.isEmpty()) {
|
||||
MaterialDAO::updateMaterialId(acc.id, matId);
|
||||
if (acc.cardNumber.isEmpty() && !cardNumber.isEmpty()) {
|
||||
MaterialDAO::updateCardNumber(acc.id, cardNumber);
|
||||
}
|
||||
qDebug() << "[syncBankProfilesFromServer] linked material" << matId << "to account" << acc.id;
|
||||
}
|
||||
} else if (!lastNumbers.isEmpty()) {
|
||||
// Account не существует — создаём
|
||||
MaterialInfo newAcc;
|
||||
newAcc.deviceId = localDeviceId;
|
||||
newAcc.appCode = bankName;
|
||||
newAcc.lastNumbers = lastNumbers;
|
||||
newAcc.cardNumber = cardNumber;
|
||||
newAcc.materialId = matId;
|
||||
newAcc.currency = currency.isEmpty()
|
||||
? configSettings.value(bankName + "/currency").toString()
|
||||
: currency;
|
||||
newAcc.status = MaterialStatus::Active;
|
||||
newAcc.updateTime = QDateTime::currentDateTime();
|
||||
|
||||
if (MaterialDAO::upsertAccount(newAcc)) {
|
||||
qDebug() << "[syncBankProfilesFromServer] created account for material" << matId << "card" << lastNumbers;
|
||||
} else {
|
||||
qWarning() << "[syncBankProfilesFromServer] failed to create account for material" << matId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "[syncBankProfilesFromServer] done";
|
||||
}
|
||||
|
||||
void NetworkService::syncBanksIfEmpty() {
|
||||
const QList<BankInfo> existing = BankDAO::getAll();
|
||||
if (!existing.isEmpty()) {
|
||||
qDebug() << "[syncBanksIfEmpty] banks table has" << existing.size() << "entries, skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "[syncBanksIfEmpty] banks table empty, downloading...";
|
||||
|
||||
QNetworkRequest request(QUrl("https://api.brtservice.io/api/rest/info/"));
|
||||
QNetworkReply *reply = m_manager->get(request);
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) { loop.quit(); });
|
||||
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timeout.start(30000);
|
||||
loop.exec();
|
||||
|
||||
if (!timeout.isActive()) {
|
||||
qWarning() << "[syncBanksIfEmpty] timeout";
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "[syncBanksIfEmpty] error:" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
QJsonArray array;
|
||||
if (doc.isArray()) {
|
||||
array = doc.array();
|
||||
} else if (doc.isObject() && doc.object().contains("data")) {
|
||||
array = doc.object()["data"].toArray();
|
||||
}
|
||||
|
||||
QList<BankInfo> banks;
|
||||
for (const QJsonValue &val : array) {
|
||||
const QJsonObject obj = val.toObject();
|
||||
BankInfo bank;
|
||||
bank.alias = obj["alias"].toString();
|
||||
bank.name = obj["name"].toString();
|
||||
bank.nspkName = obj["nspk_name"].toString();
|
||||
if (!bank.alias.isEmpty()) {
|
||||
banks.append(bank);
|
||||
}
|
||||
}
|
||||
|
||||
if (!banks.isEmpty()) {
|
||||
BankDAO::replaceAll(banks);
|
||||
qDebug() << "[syncBanksIfEmpty] synced" << banks.size() << "banks";
|
||||
} else {
|
||||
qWarning() << "[syncBanksIfEmpty] no banks in response";
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkService::syncDevicesFromServer(const QString &desktopId) {
|
||||
if (desktopId.isEmpty()) return;
|
||||
|
||||
const QJsonValue devicesResult = getJson(m_apiBase + m_pathDevice + desktopId, true);
|
||||
if (devicesResult.isUndefined() || devicesResult.isNull() || !devicesResult.isArray()) {
|
||||
qWarning() << "[syncDevicesFromServer] failed to get devices for desktop" << desktopId;
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonArray devicesArray = devicesResult.toArray();
|
||||
QSet<QString> processedNames; // защита от дублей с сервера
|
||||
|
||||
for (const QJsonValue &devVal : devicesArray) {
|
||||
const QJsonObject dev = devVal.toObject();
|
||||
const QString apiId = dev["id"].toString();
|
||||
const QString name = dev["name"].toString();
|
||||
if (apiId.isEmpty()) continue;
|
||||
|
||||
// Пропускаем дубли по name
|
||||
if (processedNames.contains(name)) {
|
||||
qDebug() << "[syncDevicesFromServer] skipping duplicate:" << apiId << name;
|
||||
continue;
|
||||
}
|
||||
processedNames.insert(name);
|
||||
|
||||
// Проверяем: есть ли уже устройство с таким name в локальной БД
|
||||
const DeviceInfo existing = DeviceDAO::findByName(name);
|
||||
if (!existing.id.isEmpty() && !existing.apiId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!existing.id.isEmpty() && existing.apiId.isEmpty()) {
|
||||
DeviceDAO::updateApiId(existing.id, apiId);
|
||||
qDebug() << "[syncDevicesFromServer] linked apiId" << apiId << "to device" << existing.id;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Устройства нет в БД — создаём одну запись (DeviceScreener потом подхватит)
|
||||
DeviceInfo deviceInfo;
|
||||
deviceInfo.apiId = apiId;
|
||||
deviceInfo.name = name;
|
||||
deviceInfo.status = dev["status"].toString();
|
||||
deviceInfo.screenWidth = dev["screen_width"].toInt();
|
||||
deviceInfo.screenHeight = dev["screen_height"].toInt();
|
||||
deviceInfo.battery = dev["battery"].toInt();
|
||||
deviceInfo.id = apiId;
|
||||
|
||||
if (DeviceDAO::upsertDevice(deviceInfo)) {
|
||||
qDebug() << "[syncDevicesFromServer] restored device:" << apiId << name;
|
||||
} else {
|
||||
qWarning() << "[syncDevicesFromServer] failed to restore device:" << apiId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkService::syncCurrentProfile() {
|
||||
// 1. Определяем онлайн-устройства по БД (DeviceScreener обновит позже)
|
||||
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
|
||||
@ -492,8 +837,8 @@ void NetworkService::syncCurrentProfile() {
|
||||
const QString serverBpState = bp["state"].toString(); // "enabled" / "disabled"
|
||||
|
||||
// Находим applications с этим bank_profile_id
|
||||
const QList<ApplicationInfo> apps = ApplicationDAO::findAllByBankProfileId(bpId);
|
||||
for (const ApplicationInfo &app : apps) {
|
||||
const QList<BankProfileInfo> apps = BankProfileDAO::findAllByBankProfileId(bpId);
|
||||
for (const BankProfileInfo &app : apps) {
|
||||
const bool deviceOnline = onlineDeviceIds.contains(app.deviceId);
|
||||
const QString localStatus = app.status; // "active" / "off"
|
||||
const QString serverStatus = (serverBpState == "disabled") ? "off" : "active";
|
||||
@ -521,11 +866,11 @@ void NetworkService::syncCurrentProfile() {
|
||||
const QString matId = mat["id"].toString();
|
||||
const QString serverMatState = mat["state"].toString();
|
||||
|
||||
const AccountInfo account = AccountDAO::findByMaterialId(matId);
|
||||
const MaterialInfo account = MaterialDAO::findByMaterialId(matId);
|
||||
if (account.id < 0) continue; // не найден в локальной БД
|
||||
|
||||
const bool deviceOnline = onlineDeviceIds.contains(account.deviceId);
|
||||
const QString localStatus = accountStatusToString(account.status);
|
||||
const QString localStatus = materialStatusToString(account.status);
|
||||
const QString serverStatus = (serverMatState == "disabled") ? "off" : "active";
|
||||
|
||||
if (!deviceOnline) {
|
||||
@ -559,7 +904,7 @@ void NetworkService::postBankProfile() {
|
||||
const QString bankProfileId = data["id"].toString();
|
||||
const int appId = m_extraData.value("app_id").toInt();
|
||||
if (!bankProfileId.isEmpty() && appId > 0) {
|
||||
ApplicationDAO::updateBankProfileId(appId, bankProfileId);
|
||||
BankProfileDAO::updateBankProfileId(appId, bankProfileId);
|
||||
qDebug() << "[NetworkService] postBankProfile saved bank_profile_id:" << bankProfileId;
|
||||
}
|
||||
}
|
||||
@ -616,7 +961,7 @@ void NetworkService::postMaterial() {
|
||||
const QString materialId = result.toObject()["id"].toString();
|
||||
const int accountId = m_extraData.value("account_id").toInt();
|
||||
if (!materialId.isEmpty() && accountId > 0) {
|
||||
AccountDAO::updateMaterialId(accountId, materialId);
|
||||
MaterialDAO::updateMaterialId(accountId, materialId);
|
||||
}
|
||||
qDebug() << "[NetworkService] postMaterial ok, card:" << body["card_number"].toString() << "material_id:" << materialId;
|
||||
}
|
||||
@ -802,7 +1147,7 @@ void NetworkService::getTasks(const QStringList &materialIds) {
|
||||
|
||||
QUrl url(m_apiBase + m_pathGetTasks);
|
||||
QUrlQuery urlQuery;
|
||||
const QStringList ids = materialIds.isEmpty() ? AccountDAO::getActiveMaterialIds() : materialIds;
|
||||
const QStringList ids = materialIds.isEmpty() ? MaterialDAO::getActiveMaterialIds() : materialIds;
|
||||
if (ids.isEmpty()) {
|
||||
m_fetchingTasks = false;
|
||||
return;
|
||||
@ -903,8 +1248,62 @@ void NetworkService::start() {
|
||||
scheduleNextRefresh();
|
||||
}
|
||||
|
||||
void NetworkService::deleteDeviceFromServer() {
|
||||
if (!ensureValidToken()) { emit finished(); return; }
|
||||
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||
if (device.apiId.isEmpty()) {
|
||||
qWarning() << "[deleteDeviceFromServer] no api_id for device" << m_deviceId;
|
||||
// Удаляем только локально
|
||||
DeviceDAO::deleteDevice(m_deviceId);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonValue result = deleteJson(m_apiBase + m_pathDevice + device.apiId, true);
|
||||
if (result.isUndefined()) {
|
||||
qWarning() << "[deleteDeviceFromServer] failed to delete device on server:" << device.apiId;
|
||||
} else {
|
||||
qDebug() << "[deleteDeviceFromServer] deleted device on server:" << device.apiId;
|
||||
}
|
||||
|
||||
DeviceDAO::deleteDevice(m_deviceId);
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void NetworkService::disableAllProfilesOnServer() {
|
||||
if (!ensureValidToken()) return;
|
||||
|
||||
const QJsonValue profileResult = getJson(m_apiBase + m_pathCurrentProfile, true);
|
||||
if (profileResult.isUndefined() || profileResult.isNull()) return;
|
||||
|
||||
const QJsonArray bankProfiles = profileResult.toObject()["bank_profiles"].toArray();
|
||||
for (const QJsonValue &bpVal : bankProfiles) {
|
||||
const QJsonObject bp = bpVal.toObject();
|
||||
const QString bpId = bp["id"].toString();
|
||||
const QString bpState = bp["state"].toString();
|
||||
if (bpId.isEmpty() || bpState == "disabled") continue;
|
||||
|
||||
patchJson(m_apiBase + m_pathBankProfile + "/" + bpId + "/false", QJsonObject(), true);
|
||||
qDebug() << "[shutdown] disabled bank_profile" << bpId;
|
||||
|
||||
const QJsonArray materials = bp["materials"].toArray();
|
||||
for (const QJsonValue &matVal : materials) {
|
||||
const QJsonObject mat = matVal.toObject();
|
||||
const QString matId = mat["id"].toString();
|
||||
const QString matState = mat["state"].toString();
|
||||
if (matId.isEmpty() || matState == "disabled") continue;
|
||||
|
||||
patchJson(m_apiBase + m_pathMaterial + "/" + matId + "/false", QJsonObject(), true);
|
||||
qDebug() << "[shutdown] disabled material" << matId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkService::stop() {
|
||||
m_running = false;
|
||||
// Эмитируем finished() в потоке NetworkService, чтобы корректно завершить QThread
|
||||
QMetaObject::invokeMethod(this, [this]() { emit finished(); }, Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
disableAllProfilesOnServer();
|
||||
emit finished();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
@ -60,6 +60,8 @@ public slots:
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void disableAllProfilesOnServer();
|
||||
void deleteDeviceFromServer();
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
@ -103,8 +105,12 @@ private:
|
||||
QJsonValue getJson(const QString &path, bool withAuth = false);
|
||||
QJsonValue postJson(const QString &path, const QJsonObject &body, bool withAuth = false);
|
||||
QJsonValue patchJson(const QString &path, const QJsonObject &body, bool withAuth = false);
|
||||
QJsonValue deleteJson(const QString &path, bool withAuth = false);
|
||||
|
||||
bool refreshToken();
|
||||
bool ensureValidToken();
|
||||
void scheduleNextRefresh();
|
||||
void syncDevicesFromServer(const QString &desktopId);
|
||||
void syncBankProfilesFromServer();
|
||||
void syncBanksIfEmpty();
|
||||
};
|
||||
|
||||
@ -49,14 +49,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
auto *bankListAction = new QAction("Банки", this);
|
||||
auto *tutorialPageAction = new QAction("Инструкция", this);
|
||||
auto *eventAction = new QAction("События", this);
|
||||
auto *errorLogAction = new QAction("Ошибки", this);
|
||||
auto *fileLogAction = new QAction("Логи", this);
|
||||
menu->addAction(devicesPageAction);
|
||||
// menu->addAction(accountPageAction);
|
||||
menu->addAction(bankListAction);
|
||||
menu->addAction(eventAction);
|
||||
menu->addAction(tutorialPageAction);
|
||||
menu->addAction(errorLogAction);
|
||||
menu->addAction(fileLogAction);
|
||||
|
||||
connect(devicesPageAction, &QAction::triggered, this, [=]() {
|
||||
@ -98,15 +96,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
m_eventWindow->activateWindow();
|
||||
});
|
||||
|
||||
connect(errorLogAction, &QAction::triggered, this, [=]() {
|
||||
if (!m_logWindow) {
|
||||
m_logWindow = new LogWindow(this);
|
||||
}
|
||||
m_logWindow->show();
|
||||
m_logWindow->raise();
|
||||
m_logWindow->activateWindow();
|
||||
});
|
||||
|
||||
connect(fileLogAction, &QAction::triggered, this, [=]() {
|
||||
if (!m_fileLogWindow) {
|
||||
m_fileLogWindow = new FileLogWindow(this);
|
||||
|
||||
@ -32,7 +32,6 @@ private:
|
||||
QWidget *m_accountsWindow = nullptr;
|
||||
QWidget *m_tutorialWindow = nullptr;
|
||||
QWidget *m_bankListWindow = nullptr;
|
||||
QWidget *m_logWindow = nullptr;
|
||||
QWidget *m_fileLogWindow = nullptr;
|
||||
QWidget *m_eventWindow = nullptr;
|
||||
|
||||
|
||||
@ -19,15 +19,15 @@
|
||||
#include "black/LoginAndCheckAccountsScript.h"
|
||||
#include "black/GetProfileInfoScript.h"
|
||||
#include "black/GetCardInfoScript.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "bank/TransactionWindow.h"
|
||||
#include "device/EditBankAccountDialog.h"
|
||||
#include "device/EditBankDialog.h"
|
||||
#include "net/NetworkService.h"
|
||||
|
||||
AccountSettingsWindow::AccountSettingsWindow(
|
||||
QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app
|
||||
QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app
|
||||
) : QWidget(parent,
|
||||
Qt::Window
|
||||
| Qt::WindowTitleHint
|
||||
@ -90,7 +90,7 @@ AccountSettingsWindow::AccountSettingsWindow(
|
||||
const QString comment = dlg.secondValue();
|
||||
bool on = dlg.toggled();
|
||||
|
||||
if (!ApplicationDAO::update(m_applicationInfo.id, pinCode, comment, on ? "active" : "off")) {
|
||||
if (!BankProfileDAO::update(m_applicationInfo.id, pinCode, comment, on ? "active" : "off")) {
|
||||
qDebug() << "Cant update bank data";
|
||||
} else {
|
||||
m_applicationInfo.pinCode = pinCode;
|
||||
@ -132,6 +132,17 @@ AccountSettingsWindow::AccountSettingsWindow(
|
||||
m_profileLabel->setTextFormat(Qt::RichText);
|
||||
m_profileLabel->setStyleSheet("margin-bottom: 8px;");
|
||||
profileRow->addWidget(m_profileLabel);
|
||||
|
||||
// Статус синхронизации с сервером
|
||||
const QString syncText = m_applicationInfo.bankProfileId.isEmpty()
|
||||
? "<span style='color:red;'>● Не синхронизирован</span>"
|
||||
: "<span style='color:green;'>● Синхронизирован</span>";
|
||||
auto *syncLabel = new QLabel(syncText);
|
||||
syncLabel->setTextFormat(Qt::RichText);
|
||||
syncLabel->setStyleSheet("margin-bottom: 8px; margin-left: 16px;");
|
||||
profileRow->addWidget(syncLabel);
|
||||
profileRow->addStretch();
|
||||
|
||||
layout->addLayout(profileRow);
|
||||
|
||||
QPushButton *soloButton = new QPushButton("Запустить проверку пин-кода");
|
||||
@ -320,35 +331,6 @@ AccountSettingsWindow::AccountSettingsWindow(
|
||||
|
||||
|
||||
layout->addWidget(tableWidget);
|
||||
QPushButton *addAccountButton = new QPushButton("Добавить аккаунт");
|
||||
addAccountButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
AccountInfo account;
|
||||
connect(addAccountButton, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||
EditBankAccountDialog dlg(account, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
const QString cardNumber = dlg.cardNumberValue();
|
||||
const QString currency = dlg.currencyValue();
|
||||
bool on = dlg.toggled();
|
||||
|
||||
AccountInfo accountInfo;
|
||||
accountInfo.currency = currency;
|
||||
accountInfo.description = cardNumber;
|
||||
accountInfo.amount = 0;
|
||||
accountInfo.appCode = m_applicationInfo.code;
|
||||
accountInfo.deviceId = m_deviceId;
|
||||
accountInfo.status = on ? AccountStatus::Active : AccountStatus::Off;
|
||||
accountInfo.lastNumbers = cardNumber.right(4);
|
||||
if (!AccountDAO::upsertAccount(accountInfo)) {
|
||||
qDebug() << "Cant insert bank account data";
|
||||
} else {
|
||||
reloadContent(tableWidget);
|
||||
tableWidget->resizeColumnsToContents();
|
||||
updateAccountData(accountInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
layout->addWidget(addAccountButton);
|
||||
|
||||
tableWidget->resizeRowsToContents();
|
||||
|
||||
@ -365,11 +347,22 @@ AccountSettingsWindow::AccountSettingsWindow(
|
||||
|
||||
void AccountSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||
QThread *thread = new QThread(nullptr);
|
||||
const QString deviceId = m_deviceId;
|
||||
|
||||
auto onResult = [deviceId, appCode](const QString &result) {
|
||||
if (result.isEmpty()) {
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
|
||||
if (app.id >= 0) {
|
||||
BankProfileDAO::updatePinCodeStatus(app.id, "yes", "");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (appCode == "ozon") {
|
||||
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finishedWithResult, worker, onResult);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, thread, &QThread::quit);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
@ -377,6 +370,7 @@ void AccountSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||
auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finishedWithResult, worker, onResult);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, thread, &QThread::quit);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
@ -438,7 +432,7 @@ void AccountSettingsWindow::startGetCardInfo(const QString &appCode) {
|
||||
void AccountSettingsWindow::startGetLastTransactions(const QString &appCode, int maxCount) {
|
||||
if (appCode == "ozon") {
|
||||
QThread *thread = new QThread(nullptr);
|
||||
auto *worker = new Ozon::GetLastTransactionsScript(m_deviceId, appCode, maxCount, nullptr);
|
||||
auto *worker = new Ozon::GetLastTransactionsScript(m_deviceId, appCode, maxCount);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start);
|
||||
connect(worker, &Ozon::GetLastTransactionsScript::finished, thread, &QThread::quit);
|
||||
@ -458,7 +452,7 @@ void AccountSettingsWindow::startGetLastTransactions(const QString &appCode, int
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString &code, const QString &status) {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, code);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, code);
|
||||
if (app.bankProfileId.isEmpty()) {
|
||||
qWarning() << "[AccountSettings] sendAppStatus: no bankProfileId for" << code;
|
||||
return;
|
||||
@ -477,7 +471,7 @@ void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
||||
void AccountSettingsWindow::updateAccountData(const MaterialInfo &account) {
|
||||
QJsonArray list;
|
||||
QJsonObject obj;
|
||||
obj["appName"] = account.appCode;
|
||||
@ -485,7 +479,7 @@ void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
||||
obj["amount"] = account.amount;
|
||||
obj["description"] = account.description;
|
||||
obj["currency"] = account.currency;
|
||||
obj["status"] = accountStatusToString(account.status);
|
||||
obj["status"] = materialStatusToString(account.status);
|
||||
|
||||
list.append(obj);
|
||||
|
||||
@ -502,11 +496,11 @@ void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
QList<AccountInfo> accounts = AccountDAO::getAccounts(m_deviceId, m_applicationInfo.code);
|
||||
QList<MaterialInfo> accounts = MaterialDAO::getAccounts(m_deviceId, m_applicationInfo.code);
|
||||
|
||||
tableWidget->clearContents();
|
||||
tableWidget->setRowCount(accounts.length());
|
||||
tableWidget->setColumnCount(8);
|
||||
tableWidget->setColumnCount(9);
|
||||
tableWidget->setHorizontalHeaderLabels({
|
||||
"Время обновления",
|
||||
"Номер",
|
||||
@ -514,6 +508,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
"Сумма",
|
||||
"Валюта",
|
||||
"Статус",
|
||||
"Сервер",
|
||||
"",
|
||||
""
|
||||
});
|
||||
@ -526,7 +521,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
|
||||
|
||||
for (int k = 0; k < accounts.size(); ++k) {
|
||||
const AccountInfo &account = accounts[k];
|
||||
const MaterialInfo &account = accounts[k];
|
||||
|
||||
QTableWidgetItem *time = new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss"));
|
||||
time->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
@ -554,18 +549,24 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
currency->setFlags(currency->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 4, currency);
|
||||
|
||||
QTableWidgetItem *status = new QTableWidgetItem(accountStatusToUiString(account.status));
|
||||
QTableWidgetItem *status = new QTableWidgetItem(materialStatusToUiString(account.status));
|
||||
status->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
status->setFlags(status->flags() & ~Qt::ItemIsEditable);
|
||||
if (account.status != AccountStatus::Active) {
|
||||
if (account.status != MaterialStatus::Active) {
|
||||
status->setForeground(QBrush(Qt::red));
|
||||
}
|
||||
tableWidget->setItem(k, 5, status);
|
||||
|
||||
auto *syncItem = new QTableWidgetItem(account.materialId.isEmpty() ? "Не синхр." : "Синхр.");
|
||||
syncItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
syncItem->setForeground(account.materialId.isEmpty() ? QBrush(Qt::red) : QBrush(Qt::darkGreen));
|
||||
syncItem->setFlags(syncItem->flags() & ~Qt::ItemIsEditable);
|
||||
tableWidget->setItem(k, 6, syncItem);
|
||||
|
||||
|
||||
auto *deleteBankAccountBtn = new QPushButton("Удалить", tableWidget);
|
||||
deleteBankAccountBtn->setStyleSheet("background-color: red; color: white;");
|
||||
tableWidget->setCellWidget(k, 6, deleteBankAccountBtn);
|
||||
tableWidget->setCellWidget(k, 7, deleteBankAccountBtn);
|
||||
|
||||
connect(deleteBankAccountBtn, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||
QMessageBox msgBox(this);
|
||||
@ -576,7 +577,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
msgBox.setDefaultButton(cancelBtn);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == deleteBtn) {
|
||||
if (AccountDAO::deleteAccount(account.id)) {
|
||||
if (MaterialDAO::deleteAccount(account.id)) {
|
||||
reloadContent(tableWidget);
|
||||
tableWidget->resizeColumnsToContents();
|
||||
} else {
|
||||
@ -587,7 +588,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
|
||||
auto *transactionsBtn = new QPushButton("Транзакции", tableWidget);
|
||||
transactionsBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
tableWidget->setCellWidget(k, 7, transactionsBtn);
|
||||
tableWidget->setCellWidget(k, 8, transactionsBtn);
|
||||
|
||||
connect(transactionsBtn, &QPushButton::clicked, this, [this, account]() {
|
||||
auto *txWindow = new TransactionWindow(account.id, this);
|
||||
|
||||
@ -3,19 +3,19 @@
|
||||
#include <QTableWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
class AccountSettingsWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AccountSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app);
|
||||
AccountSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app);
|
||||
|
||||
private:
|
||||
QString m_deviceId;
|
||||
QString m_deviceName;
|
||||
ApplicationInfo m_applicationInfo;
|
||||
BankProfileInfo m_applicationInfo;
|
||||
|
||||
QLabel *m_statusLabel;
|
||||
QLabel *m_commentLabel;
|
||||
@ -33,5 +33,5 @@ private:
|
||||
|
||||
void sendAppStatus(const QString &deviceId, const QString &code, const QString &status);
|
||||
|
||||
void updateAccountData(const AccountInfo &account);
|
||||
void updateAccountData(const MaterialInfo &account);
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include <QScrollArea>
|
||||
|
||||
#include "TransactionWindow.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
|
||||
AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
||||
@ -30,7 +30,7 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
||||
QList<DeviceInfo> devices = DeviceDAO::getAllDevices(false);
|
||||
for (int i = 0; i < devices.size(); ++i) {
|
||||
DeviceInfo device = devices[i];
|
||||
QList<AccountInfo> accounts = AccountDAO::getAccounts(device.id, "ozon");
|
||||
QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, "ozon");
|
||||
// Текст перед первой таблицей
|
||||
QString title = QString("%1 [%2] | %3").arg(device.name, device.id, device.status);
|
||||
QLabel *label = new QLabel(title, this);
|
||||
@ -54,14 +54,14 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
||||
|
||||
|
||||
for (int k = 0; k < accounts.size(); ++k) {
|
||||
AccountInfo account = accounts[k];
|
||||
MaterialInfo account = accounts[k];
|
||||
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(accountStatusToString(account.status)));
|
||||
tableWidget->setItem(k, 4, new QTableWidgetItem(materialStatusToString(account.status)));
|
||||
totalHeight += tableWidget->rowHeight(k);
|
||||
}
|
||||
tableWidget->setFixedHeight(totalHeight + 2);
|
||||
|
||||
@ -15,8 +15,9 @@
|
||||
#include <QJsonObject>
|
||||
#include <QThread>
|
||||
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "common/PaddedItemDelegate.h"
|
||||
#include "device/EditBankDialog.h"
|
||||
@ -29,7 +30,7 @@ QTableWidgetItem *BankSettingsWindow::createTableWidget(const QString &text) {
|
||||
return item;
|
||||
}
|
||||
|
||||
QString parseStatus(const ApplicationInfo &app) {
|
||||
QString parseStatus(const BankProfileInfo &app) {
|
||||
if (!app.install) {
|
||||
return "не установлен";
|
||||
}
|
||||
@ -47,11 +48,22 @@ QString parseStatus(const ApplicationInfo &app) {
|
||||
|
||||
void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||
QThread *thread = new QThread(nullptr);
|
||||
const QString deviceId = m_deviceId;
|
||||
|
||||
auto onResult = [deviceId, appCode](const QString &result) {
|
||||
if (result.isEmpty()) {
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
|
||||
if (app.id >= 0) {
|
||||
BankProfileDAO::updatePinCodeStatus(app.id, "yes", "");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (appCode == "ozon") {
|
||||
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finishedWithResult, worker, onResult);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, thread, &QThread::quit);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
@ -59,6 +71,7 @@ void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||
auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finishedWithResult, worker, onResult);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, thread, &QThread::quit);
|
||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
@ -70,13 +83,45 @@ void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||
thread->start();
|
||||
}
|
||||
|
||||
static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName) {
|
||||
QJsonObject errData;
|
||||
errData["status"] = "error";
|
||||
errData["description"] = "Bank profile disabled";
|
||||
|
||||
QJsonObject obj;
|
||||
obj["type"] = type;
|
||||
obj["material_id"] = materialId;
|
||||
obj["bank_name"] = bankName;
|
||||
obj["data"] = errData;
|
||||
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
net->moveToThread(thread);
|
||||
net->setPayload(QJsonDocument(obj).toJson());
|
||||
QObject::connect(thread, &QThread::started, net, &NetworkService::postTaskResult);
|
||||
QObject::connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void sendAppStatus(const QString &deviceId, const QString &code, const QString &status) {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, code);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, code);
|
||||
if (app.bankProfileId.isEmpty()) {
|
||||
qWarning() << "[BankSettings] sendAppStatus: no bankProfileId for" << code;
|
||||
return;
|
||||
}
|
||||
|
||||
// При выключении — отменяем ожидающие задачи
|
||||
if (status != "active") {
|
||||
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByBank(deviceId, code);
|
||||
for (const EventInfo &e : cancelled) {
|
||||
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName);
|
||||
EventDAO::markSentToServer(e.id);
|
||||
qDebug() << "[BankSettings] cancelled task" << e.id << eventTypeToString(e.type);
|
||||
}
|
||||
}
|
||||
|
||||
const bool enable = (status == "active");
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
@ -91,15 +136,16 @@ void sendAppStatus(const QString &deviceId, const QString &code, const QString &
|
||||
}
|
||||
|
||||
void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
QList<ApplicationInfo> applications = ApplicationDAO::getApplicationsByDeviceId(m_deviceId);
|
||||
QList<BankProfileInfo> applications = BankProfileDAO::getApplicationsByDeviceId(m_deviceId);
|
||||
|
||||
tableWidget->setRowCount(applications.length());
|
||||
tableWidget->setColumnCount(7);
|
||||
tableWidget->setColumnCount(8);
|
||||
tableWidget->setHorizontalHeaderLabels({
|
||||
"",
|
||||
"Имя",
|
||||
"Пин-код",
|
||||
"Статус",
|
||||
"Сервер",
|
||||
"Комментарий",
|
||||
"",
|
||||
""
|
||||
@ -109,17 +155,18 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
tableWidget->setTextElideMode(Qt::ElideNone);
|
||||
|
||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
}
|
||||
for (int i = 0; i < applications.length(); ++i) {
|
||||
tableWidget->verticalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
}
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
|
||||
tableWidget->setColumnWidth(4, 140);
|
||||
|
||||
|
||||
for (int k = 0; k < applications.size(); ++k) {
|
||||
const ApplicationInfo &app = applications[k];
|
||||
const BankProfileInfo &app = applications[k];
|
||||
|
||||
|
||||
auto *icon = new QLabel(tableWidget);
|
||||
@ -151,20 +198,23 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
|
||||
tableWidget->setItem(k, 3, createTableWidget(parseStatus(app)));
|
||||
|
||||
// tableWidget->setItem(k, 4, createTableWidget(app.comment));
|
||||
// Статус синхронизации с сервером
|
||||
auto *syncItem = new QTableWidgetItem(app.bankProfileId.isEmpty() ? "Не синхронизирован" : "Синхронизирован");
|
||||
syncItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
syncItem->setForeground(app.bankProfileId.isEmpty() ? QBrush(Qt::red) : QBrush(Qt::darkGreen));
|
||||
tableWidget->setItem(k, 4, syncItem);
|
||||
|
||||
QTableWidgetItem *item = new QTableWidgetItem(app.comment);
|
||||
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||
// item->setSizeHint(QSize(100, app.comment.length() / 2)); // Минимальная высота ячейки для переноса
|
||||
tableWidget->setItem(k, 4, item);
|
||||
tableWidget->setItem(k, 5, item);
|
||||
|
||||
|
||||
if (app.install) {
|
||||
if (!app.pinCode.isEmpty() && !app.pinCodeChecked) {
|
||||
auto *pinCodeBtn = new QPushButton(" Проверить пин-код ", tableWidget);
|
||||
pinCodeBtn->setProperty("btnClass", "pincode");
|
||||
tableWidget->setCellWidget(k, 5, pinCodeBtn);
|
||||
tableWidget->setCellWidget(k, 6, pinCodeBtn);
|
||||
|
||||
connect(pinCodeBtn, &QPushButton::clicked, this, [this, app]() {
|
||||
QMessageBox msgBox(this);
|
||||
@ -187,7 +237,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
deleteBtn->setProperty("btnClass", "delete");
|
||||
// deleteBtn->setStyleSheet("padding: 4px 8px;");
|
||||
// deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
tableWidget->setCellWidget(k, 5, deleteBtn);
|
||||
tableWidget->setCellWidget(k, 6, deleteBtn);
|
||||
|
||||
connect(deleteBtn, &QPushButton::clicked, this, [this,tableWidget, app]() {
|
||||
QMessageBox msgBox(this);
|
||||
@ -201,7 +251,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == deleteBtn) {
|
||||
if (!ApplicationDAO::update(app.id, app.pinCode, app.comment, "off")) {
|
||||
if (!BankProfileDAO::update(app.id, app.pinCode, app.comment, "off")) {
|
||||
qDebug() << "Cant update bank data";
|
||||
} else {
|
||||
sendAppStatus(app.deviceId, app.code, "off");
|
||||
@ -222,7 +272,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
auto *btn = new QPushButton(" Редактировать ", tableWidget);
|
||||
btn->setProperty("btnClass", "edit");
|
||||
// btn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
tableWidget->setCellWidget(k, 6, btn);
|
||||
tableWidget->setCellWidget(k, 7, btn);
|
||||
connect(btn, &QPushButton::clicked, this, [this, tableWidget, k,app]() {
|
||||
BankEditDialog dlg(app, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
@ -230,7 +280,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
const QString comment = dlg.secondValue();
|
||||
bool on = dlg.toggled();
|
||||
|
||||
if (!ApplicationDAO::update(app.id, pinCode, comment, on ? "active" : "off")) {
|
||||
if (!BankProfileDAO::update(app.id, pinCode, comment, on ? "active" : "off")) {
|
||||
qDebug() << "Cant update bank data";
|
||||
} else {
|
||||
sendAppStatus(app.deviceId, app.code, on ? "active" : "off");
|
||||
@ -246,7 +296,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
}
|
||||
}
|
||||
|
||||
BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app)
|
||||
BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app)
|
||||
: QWidget(parent,
|
||||
Qt::Window
|
||||
| Qt::WindowTitleHint
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
#include <qtablewidget.h>
|
||||
#include <QWidget>
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
class BankSettingsWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app);
|
||||
BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app);
|
||||
|
||||
private:
|
||||
QString m_deviceId;
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
#include <QTableWidget>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/MaterialDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
|
||||
class AmountDelegate : public QStyledItemDelegate {
|
||||
@ -67,7 +67,7 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
||||
scrollArea->setStyleSheet("border: none;"); // Убираем рамку
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
|
||||
AccountInfo account = AccountDAO::getAccountById(accountId);
|
||||
MaterialInfo account = MaterialDAO::getAccountById(accountId);
|
||||
QList<TransactionInfo> transactions = TransactionDAO::getTransactions(accountId);
|
||||
|
||||
QTableWidget *tableWidget = new QTableWidget(this);
|
||||
|
||||
@ -7,11 +7,12 @@
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
|
||||
#include <QSettings>
|
||||
#include "bank/AccountSettingsWindow.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
static QString formatStatus(const ApplicationInfo &app) {
|
||||
static QString formatStatus(const BankProfileInfo &app) {
|
||||
if (!app.install) return "не установлен";
|
||||
if (app.status == "active") {
|
||||
if (!app.pinCodeChecked)
|
||||
@ -63,7 +64,29 @@ DeviceSettingsWindow::DeviceSettingsWindow(QWidget *parent, const QString &devic
|
||||
}
|
||||
|
||||
void DeviceSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(m_deviceId);
|
||||
QList<BankProfileInfo> apps = BankProfileDAO::getAllApplicationsByDeviceId(m_deviceId);
|
||||
|
||||
// Дополняем список банками из config.ini, которых нет в БД
|
||||
const QSettings configSettings("config.ini", QSettings::IniFormat);
|
||||
const QStringList banks = configSettings.value("apps/list").toStringList();
|
||||
QSet<QString> existingCodes;
|
||||
for (const BankProfileInfo &a : apps) {
|
||||
existingCodes.insert(a.code);
|
||||
}
|
||||
for (const QString &rawBank : banks) {
|
||||
const QString bank = rawBank.trimmed();
|
||||
if (bank.isEmpty() || existingCodes.contains(bank)) continue;
|
||||
BankProfileInfo stub;
|
||||
stub.id = -1;
|
||||
stub.deviceId = m_deviceId;
|
||||
stub.code = bank;
|
||||
stub.name = configSettings.value(bank + "/name", bank).toString();
|
||||
stub.package = configSettings.value(bank + "/android_package_name").toString();
|
||||
stub.currency = configSettings.value(bank + "/currency").toString();
|
||||
stub.status = "off";
|
||||
stub.install = false;
|
||||
apps.append(stub);
|
||||
}
|
||||
|
||||
tableWidget->clearContents();
|
||||
tableWidget->setRowCount(apps.size());
|
||||
@ -76,7 +99,7 @@ void DeviceSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
|
||||
|
||||
for (int k = 0; k < apps.size(); ++k) {
|
||||
const ApplicationInfo &app = apps[k];
|
||||
const BankProfileInfo &app = apps[k];
|
||||
|
||||
// Иконка
|
||||
auto *icon = new QLabel(tableWidget);
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <QImage>
|
||||
#include <QPushButton>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPointer>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QGraphicsScene>
|
||||
@ -13,14 +15,15 @@
|
||||
#include "bank/AccountSettingsWindow.h"
|
||||
#include "bank/BankSettingsWindow.h"
|
||||
#include "DeviceSettingsWindow.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/BankProfileDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "dao/EventDAO.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include <QThread>
|
||||
|
||||
|
||||
QString getColorByStatus(const ApplicationInfo &app) {
|
||||
QString getColorByStatus(const BankProfileInfo &app) {
|
||||
if (!app.install) {
|
||||
return "black";
|
||||
}
|
||||
@ -36,13 +39,44 @@ QString getColorByStatus(const ApplicationInfo &app) {
|
||||
}
|
||||
}
|
||||
|
||||
static void sendTaskErrorToServer(const QString &type, const QString &materialId, const QString &bankName) {
|
||||
QJsonObject errData;
|
||||
errData["status"] = "error";
|
||||
errData["description"] = "Bank profile disabled";
|
||||
|
||||
QJsonObject obj;
|
||||
obj["type"] = type;
|
||||
obj["material_id"] = materialId;
|
||||
obj["bank_name"] = bankName;
|
||||
obj["data"] = errData;
|
||||
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
net->moveToThread(thread);
|
||||
net->setPayload(QJsonDocument(obj).toJson());
|
||||
QObject::connect(thread, &QThread::started, net, &NetworkService::postTaskResult);
|
||||
QObject::connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void sendBankStatus(const QString &deviceId, const QString &code, const QString &status) {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, code);
|
||||
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, code);
|
||||
if (app.bankProfileId.isEmpty()) {
|
||||
qWarning() << "[DeviceWidget] sendBankStatus: no bankProfileId for" << code;
|
||||
return;
|
||||
}
|
||||
|
||||
// При выключении — отменяем ожидающие задачи
|
||||
if (status != "active") {
|
||||
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByBank(deviceId, code);
|
||||
for (const EventInfo &e : cancelled) {
|
||||
sendTaskErrorToServer(eventTypeToString(e.type), e.externalId, e.bankName);
|
||||
EventDAO::markSentToServer(e.id);
|
||||
}
|
||||
}
|
||||
|
||||
const bool enable = (status == "active");
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
@ -56,7 +90,7 @@ void sendBankStatus(const QString &deviceId, const QString &code, const QString
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget, QWidget *parent, bool isOffline) {
|
||||
void DeviceWidget::buildBankRow(const BankProfileInfo &app, QWidget *rowWidget, QWidget *parent, bool isOffline) {
|
||||
rowWidget->setContentsMargins(0, 0, 0, 0);
|
||||
rowWidget->setStyleSheet(
|
||||
"background-color: rgba(0, 0, 0, 0.6);" // полупрозрачный фон
|
||||
@ -78,7 +112,16 @@ void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget,
|
||||
);
|
||||
|
||||
|
||||
auto *label = new QLabel(app.name, parent);
|
||||
QString labelText;
|
||||
if (!app.fullName.isEmpty()) {
|
||||
labelText = app.fullName;
|
||||
if (!app.phone.isEmpty()) {
|
||||
labelText += "\n" + app.phone;
|
||||
}
|
||||
} else {
|
||||
labelText = app.name;
|
||||
}
|
||||
auto *label = new QLabel(labelText, parent);
|
||||
label->setStyleSheet(
|
||||
"background-color: transparent;"
|
||||
"margin: 0px;"
|
||||
@ -129,7 +172,7 @@ void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget,
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == confirmBtn) {
|
||||
if (!ApplicationDAO::update(app.id, app.pinCode, app.comment, newStatus)) {
|
||||
if (!BankProfileDAO::update(app.id, app.pinCode, app.comment, newStatus)) {
|
||||
qDebug() << "Cant update bank data";
|
||||
} else {
|
||||
sendBankStatus(app.deviceId, app.code, newStatus);
|
||||
@ -175,7 +218,7 @@ void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget,
|
||||
// hbox->addStretch();
|
||||
}
|
||||
|
||||
void DeviceWidget::setOpenBanksSettings(QPushButton *button, const ApplicationInfo &app) {
|
||||
void DeviceWidget::setOpenBanksSettings(QPushButton *button, const BankProfileInfo &app) {
|
||||
QString deviceId = m_device.id;
|
||||
QString deviceName = m_device.name;
|
||||
connect(button, &QPushButton::clicked, this, [deviceId, deviceName, app]() {
|
||||
@ -219,6 +262,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
statusLabel->setStyleSheet(
|
||||
"background-color: rgba(0, 0, 0, 0.5);"
|
||||
"color: white;"
|
||||
"font-size: 10px;"
|
||||
);
|
||||
layout->addWidget(statusLabel, 0, 0, Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
@ -274,7 +318,15 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == confirmBtn) {
|
||||
DeviceDAO::deleteDevice(deviceId);
|
||||
auto *thread = new QThread;
|
||||
auto *net = new NetworkService;
|
||||
net->setDeviceId(deviceId);
|
||||
net->moveToThread(thread);
|
||||
QObject::connect(thread, &QThread::started, net, &NetworkService::deleteDeviceFromServer);
|
||||
QObject::connect(net, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(net, &NetworkService::finished, net, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
});
|
||||
|
||||
@ -307,8 +359,8 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
||||
|
||||
const bool isOffline = device.status != "device";
|
||||
|
||||
QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(device.id);
|
||||
for (ApplicationInfo &app: apps) {
|
||||
QList<BankProfileInfo> apps = BankProfileDAO::getApplicationsByDeviceId(device.id);
|
||||
for (BankProfileInfo &app: apps) {
|
||||
if (app.install) {
|
||||
auto *rowWidget = new QWidget(appsView);
|
||||
buildBankRow(app, rowWidget, appsView, isOffline);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <qtablewidget.h>
|
||||
#include <QWidget>
|
||||
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
|
||||
class DeviceWidget final : public QWidget {
|
||||
@ -15,9 +15,9 @@ public:
|
||||
private:
|
||||
const DeviceInfo m_device;
|
||||
|
||||
void buildBankRow(const ApplicationInfo &app, QWidget *rowWidget, QWidget *parent, bool isOffline = false);
|
||||
void buildBankRow(const BankProfileInfo &app, QWidget *rowWidget, QWidget *parent, bool isOffline = false);
|
||||
|
||||
void setOpenBanksSettings(QPushButton *button, const ApplicationInfo &app);
|
||||
void setOpenBanksSettings(QPushButton *button, const BankProfileInfo &app);
|
||||
|
||||
signals:
|
||||
void deleteRequested(const QString &deviceId);
|
||||
|
||||
@ -7,13 +7,13 @@
|
||||
#include <QComboBox>
|
||||
#include <QSettings>
|
||||
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/MaterialInfo.h"
|
||||
|
||||
class EditBankAccountDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EditBankAccountDialog(const AccountInfo &account, QWidget *parent = nullptr) : QDialog(parent) {
|
||||
explicit EditBankAccountDialog(const MaterialInfo &account, QWidget *parent = nullptr) : QDialog(parent) {
|
||||
if (account.id > 0) {
|
||||
setWindowTitle("Настройка аккаунта *" + account.lastNumbers);
|
||||
} else {
|
||||
@ -36,7 +36,7 @@ public:
|
||||
currency->setCurrentText(account.currency);
|
||||
|
||||
toggle = new QCheckBox(tr("В работе"), this);
|
||||
toggle->setChecked(account.status == AccountStatus::Active);
|
||||
toggle->setChecked(account.status == MaterialStatus::Active);
|
||||
|
||||
form->addRow(tr("Номер карты:"), cardNumber);
|
||||
form->addRow(tr("Валюта:"), currency);
|
||||
|
||||
@ -5,13 +5,13 @@
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <qtextedit.h>
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/BankProfileInfo.h"
|
||||
|
||||
class BankEditDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BankEditDialog(const ApplicationInfo &app, QWidget *parent = nullptr) : QDialog(parent) {
|
||||
explicit BankEditDialog(const BankProfileInfo &app, QWidget *parent = nullptr) : QDialog(parent) {
|
||||
setWindowTitle("Настройка " + app.name);
|
||||
setModal(true);
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ static QString typeDisplayName(EventType t) {
|
||||
|
||||
EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle("События");
|
||||
resize(1400, 600);
|
||||
resize(1400, 700);
|
||||
|
||||
// ── Таблица ───────────────────────────────────────────────────────────────
|
||||
m_table = new QTableWidget(this);
|
||||
@ -91,6 +91,19 @@ EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
||||
m_table->setColumnWidth(COL_SENT, 45);
|
||||
m_table->setColumnWidth(COL_ACTION, 80);
|
||||
|
||||
// ── Панель скриншота ──────────────────────────────────────────────────────
|
||||
m_screenshotLabel = new QLabel(this);
|
||||
m_screenshotLabel->setAlignment(Qt::AlignCenter);
|
||||
m_screenshotLabel->setMinimumWidth(300);
|
||||
m_screenshotLabel->setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;");
|
||||
m_screenshotLabel->setText("Выберите событие\nдля просмотра скриншота");
|
||||
|
||||
auto *splitter = new QSplitter(Qt::Horizontal, this);
|
||||
splitter->addWidget(m_table);
|
||||
splitter->addWidget(m_screenshotLabel);
|
||||
splitter->setStretchFactor(0, 3);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
// ── Пагинация ─────────────────────────────────────────────────────────────
|
||||
m_prevBtn = new QPushButton("←", this);
|
||||
m_nextBtn = new QPushButton("→", this);
|
||||
@ -108,7 +121,7 @@ EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
||||
paginationLayout->addWidget(refreshBtn);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(m_table, 1);
|
||||
mainLayout->addWidget(splitter, 1);
|
||||
mainLayout->addLayout(paginationLayout);
|
||||
|
||||
// ── Сигналы ───────────────────────────────────────────────────────────────
|
||||
@ -122,6 +135,11 @@ EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
||||
m_page = 0;
|
||||
loadPage();
|
||||
});
|
||||
connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) {
|
||||
if (row >= 0 && row < m_eventIds.size()) {
|
||||
showScreenshot(m_eventIds[row]);
|
||||
}
|
||||
});
|
||||
connect(m_table, &QTableWidget::cellDoubleClicked, this, [this](int row, int col) {
|
||||
if (col != COL_JSON) return;
|
||||
auto *item = m_table->item(row, col);
|
||||
@ -150,9 +168,11 @@ void EventWindow::loadPage() {
|
||||
m_page = qBound(0, m_page, m_totalPages - 1);
|
||||
|
||||
const QList<EventInfo> events = EventDAO::getEvents(m_page, m_pageSize);
|
||||
m_eventIds.clear();
|
||||
|
||||
m_table->setRowCount(static_cast<int>(events.size()));
|
||||
for (int i = 0; i < events.size(); ++i) {
|
||||
m_eventIds.append(events[i].id);
|
||||
const auto &e = events[i];
|
||||
m_table->setItem(i, COL_ID, new QTableWidgetItem(QString::number(e.id)));
|
||||
m_table->setItem(i, COL_TIME, new QTableWidgetItem(
|
||||
@ -204,6 +224,24 @@ void EventWindow::loadPage() {
|
||||
updatePagination();
|
||||
}
|
||||
|
||||
void EventWindow::showScreenshot(int eventId) {
|
||||
const QByteArray data = EventDAO::getScreenshot(eventId);
|
||||
if (data.isEmpty()) {
|
||||
m_screenshotLabel->setText("Нет скриншота");
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap pixmap;
|
||||
pixmap.loadFromData(data);
|
||||
if (pixmap.isNull()) {
|
||||
m_screenshotLabel->setText("Ошибка загрузки");
|
||||
return;
|
||||
}
|
||||
|
||||
m_screenshotLabel->setPixmap(pixmap.scaled(
|
||||
m_screenshotLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
void EventWindow::updatePagination() {
|
||||
m_pageLabel->setText(QString("Страница %1 из %2").arg(m_page + 1).arg(m_totalPages));
|
||||
m_prevBtn->setEnabled(m_page > 0);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
|
||||
class EventWindow final : public QDialog {
|
||||
@ -13,6 +14,7 @@ public:
|
||||
private:
|
||||
QTableWidget *m_table;
|
||||
QLabel *m_pageLabel;
|
||||
QLabel *m_screenshotLabel;
|
||||
QPushButton *m_prevBtn;
|
||||
QPushButton *m_nextBtn;
|
||||
|
||||
@ -20,6 +22,10 @@ private:
|
||||
int m_pageSize = 50;
|
||||
int m_totalPages = 1;
|
||||
|
||||
// Кешируем event id текущей страницы для загрузки скриншота по клику
|
||||
QList<int> m_eventIds;
|
||||
|
||||
void loadPage();
|
||||
void updatePagination();
|
||||
void showScreenshot(int eventId);
|
||||
};
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
#include "FileLogWindow.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryFile>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "dao/EventDAO.h"
|
||||
#include "dao/GeneralLogDAO.h"
|
||||
#include "dao/SettingsDAO.h"
|
||||
|
||||
static constexpr int COL_ID = 0;
|
||||
static constexpr int COL_TIME = 1;
|
||||
@ -38,6 +49,8 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
||||
m_pageLabel->setMinimumWidth(140);
|
||||
|
||||
auto *refreshBtn = new QPushButton("Обновить", this);
|
||||
m_sendBtn = new QPushButton("Отправить", this);
|
||||
m_sendBtn->setStyleSheet("background-color: #0088cc; color: white; border-radius: 4px; padding: 4px 12px;");
|
||||
|
||||
auto *paginationLayout = new QHBoxLayout;
|
||||
paginationLayout->addWidget(m_prevBtn);
|
||||
@ -46,6 +59,7 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
||||
paginationLayout->addStretch();
|
||||
paginationLayout->addWidget(m_showAllCheck);
|
||||
paginationLayout->addWidget(refreshBtn);
|
||||
paginationLayout->addWidget(m_sendBtn);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(m_table, 1);
|
||||
@ -65,6 +79,7 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
||||
m_page = 0;
|
||||
loadPage();
|
||||
});
|
||||
connect(m_sendBtn, &QPushButton::clicked, this, &FileLogWindow::sendLogToTelegram);
|
||||
|
||||
loadPage();
|
||||
}
|
||||
@ -102,3 +117,141 @@ void FileLogWindow::updatePagination() {
|
||||
m_prevBtn->setEnabled(m_page > 0);
|
||||
m_nextBtn->setEnabled(m_page < m_totalPages - 1);
|
||||
}
|
||||
|
||||
void FileLogWindow::sendLogToTelegram() {
|
||||
const QString logPath = QCoreApplication::applicationDirPath() + "/application.log";
|
||||
if (!QFile::exists(logPath)) {
|
||||
QMessageBox::warning(this, "Ошибка", "Файл application.log не найден");
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendBtn->setEnabled(false);
|
||||
m_sendBtn->setText("Подготовка...");
|
||||
|
||||
// Собираем временную папку для архива
|
||||
const QString tmpDir = QDir::tempPath() + "/arc_logs_export";
|
||||
QDir().mkpath(tmpDir);
|
||||
|
||||
// 1. Копируем application.log
|
||||
QFile::remove(tmpDir + "/application.log");
|
||||
QFile::copy(logPath, tmpDir + "/application.log");
|
||||
|
||||
// 2. Экспортируем events с ошибками в errors.txt
|
||||
const QList<EventInfo> errors = EventDAO::getAllEvents(EventStatus::Error);
|
||||
if (!errors.isEmpty()) {
|
||||
QFile errFile(tmpDir + "/errors.txt");
|
||||
if (errFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream out(&errFile);
|
||||
for (const EventInfo &e : errors) {
|
||||
out << "--- Event #" << e.id << " ---\n"
|
||||
<< "Type: " << eventTypeToString(e.type) << "\n"
|
||||
<< "Bank: " << e.bankName << "\n"
|
||||
<< "Device: " << e.deviceId << "\n"
|
||||
<< "Amount: " << e.amount << "\n"
|
||||
<< "Phone: " << e.phone << "\n"
|
||||
<< "Comment: " << e.comment << "\n"
|
||||
<< "Time: " << e.timestamp.toString(Qt::ISODate) << "\n"
|
||||
<< "JSON: " << e.json << "\n\n";
|
||||
}
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Экспортируем скриншоты ошибок из events
|
||||
QDir().mkpath(tmpDir + "/screenshots");
|
||||
int screenshotCount = 0;
|
||||
for (const EventInfo &e : errors) {
|
||||
const QByteArray screenshot = EventDAO::getScreenshot(e.id);
|
||||
if (screenshot.isEmpty()) continue;
|
||||
const QString fileName = QString("screenshots/%1_%2_%3.png")
|
||||
.arg(e.id)
|
||||
.arg(eventTypeToString(e.type))
|
||||
.arg(e.timestamp.toString("yyyyMMdd_HHmmss"));
|
||||
QFile imgFile(tmpDir + "/" + fileName);
|
||||
if (imgFile.open(QIODevice::WriteOnly)) {
|
||||
imgFile.write(screenshot);
|
||||
imgFile.close();
|
||||
++screenshotCount;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Создаём ZIP
|
||||
const QString zipPath = QDir::tempPath() + "/arc_logs.zip";
|
||||
QFile::remove(zipPath);
|
||||
|
||||
QProcess zip;
|
||||
zip.setWorkingDirectory(tmpDir);
|
||||
zip.start("zip", QStringList() << "-r" << zipPath << ".");
|
||||
zip.waitForFinished(30000);
|
||||
|
||||
// Чистим временную папку
|
||||
QDir(tmpDir).removeRecursively();
|
||||
|
||||
if (zip.exitCode() != 0 || !QFile::exists(zipPath)) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось создать архив");
|
||||
m_sendBtn->setEnabled(true);
|
||||
m_sendBtn->setText("Отправить");
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendBtn->setText("Отправка...");
|
||||
|
||||
QFile *zipFile = new QFile(zipPath);
|
||||
if (!zipFile->open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось открыть архив");
|
||||
delete zipFile;
|
||||
m_sendBtn->setEnabled(true);
|
||||
m_sendBtn->setText("Отправить");
|
||||
return;
|
||||
}
|
||||
|
||||
// Telegram Bot API
|
||||
const QString botToken = "8256716069:AAFgZRB_0Y6KTpqFQmCxIlZiWdY5m2dR2D8";
|
||||
const QString chatId = "-5177086220";
|
||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||
const QString caption = "Logs from desktop: " + desktopId
|
||||
+ "\nErrors: " + QString::number(errors.size())
|
||||
+ "\nScreenshots: " + QString::number(screenshotCount);
|
||||
|
||||
const QUrl url("https://api.telegram.org/bot" + botToken + "/sendDocument");
|
||||
|
||||
auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart chatPart;
|
||||
chatPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="chat_id")");
|
||||
chatPart.setBody(chatId.toUtf8());
|
||||
multiPart->append(chatPart);
|
||||
|
||||
QHttpPart captionPart;
|
||||
captionPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="caption")");
|
||||
captionPart.setBody(caption.toUtf8());
|
||||
multiPart->append(captionPart);
|
||||
|
||||
QHttpPart filePart;
|
||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "application/zip");
|
||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
R"(form-data; name="document"; filename="arc_logs.zip")");
|
||||
filePart.setBodyDevice(zipFile);
|
||||
zipFile->setParent(multiPart);
|
||||
multiPart->append(filePart);
|
||||
|
||||
auto *manager = new QNetworkAccessManager(this);
|
||||
QNetworkReply *reply = manager->post(QNetworkRequest(url), multiPart);
|
||||
multiPart->setParent(reply);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, manager, zipPath]() {
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
QFile::remove(zipPath);
|
||||
|
||||
m_sendBtn->setEnabled(true);
|
||||
m_sendBtn->setText("Отправить");
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось отправить: " + reply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::information(this, "Готово", "Логи отправлены в Telegram");
|
||||
});
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ private:
|
||||
QLabel *m_pageLabel;
|
||||
QPushButton *m_prevBtn;
|
||||
QPushButton *m_nextBtn;
|
||||
QPushButton *m_sendBtn;
|
||||
QCheckBox *m_showAllCheck;
|
||||
|
||||
int m_page = 0;
|
||||
@ -25,4 +26,5 @@ private:
|
||||
QStringList currentLevelFilter() const;
|
||||
void loadPage();
|
||||
void updatePagination();
|
||||
void sendLogToTelegram();
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user