Remove AccountDAO, ApplicationDAO, and related models, replacing them with BankProfileDAO and BankProfileInfo.
This commit is contained in:
parent
b395ed41a5
commit
bceceb516b
@ -8,7 +8,8 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
|
|
||||||
namespace Black {
|
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()) {
|
if (accounts.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -117,15 +118,15 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node>> &accou
|
|||||||
auto *worker = new NetworkService;
|
auto *worker = new NetworkService;
|
||||||
|
|
||||||
QJsonArray list;
|
QJsonArray list;
|
||||||
for (const QPair<AccountInfo, Node> &pair : accounts) {
|
for (const QPair<MaterialInfo, Node> &pair : accounts) {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
AccountInfo account = pair.first;
|
MaterialInfo account = pair.first;
|
||||||
obj["appName"] = account.appCode;
|
obj["appName"] = account.appCode;
|
||||||
obj["lastNumbers"] = account.lastNumbers;
|
obj["lastNumbers"] = account.lastNumbers;
|
||||||
obj["amount"] = account.amount;
|
obj["amount"] = account.amount;
|
||||||
obj["description"] = account.description;
|
obj["description"] = account.description;
|
||||||
obj["currency"] = account.currency;
|
obj["currency"] = account.currency;
|
||||||
obj["status"] = accountStatusToString(account.status);
|
obj["status"] = materialStatusToString(account.status);
|
||||||
list.append(obj);
|
list.append(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node>> &accou
|
|||||||
thread->start();
|
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 *thread = new QThread;
|
||||||
auto *worker = new NetworkService;
|
auto *worker = new NetworkService;
|
||||||
|
|
||||||
@ -170,7 +171,7 @@ void CommonScript::postNewTransactionData(const AccountInfo &account, const Tran
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommonScript::updateTransactionData(
|
void CommonScript::updateTransactionData(
|
||||||
const AccountInfo &account,
|
const MaterialInfo &account,
|
||||||
const int transactionId,
|
const int transactionId,
|
||||||
const TransactionStatus status,
|
const TransactionStatus status,
|
||||||
const QString &oldDesc,
|
const QString &oldDesc,
|
||||||
@ -197,7 +198,7 @@ void CommonScript::updateTransactionData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) {
|
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()) {
|
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
|
||||||
qWarning() << "[Black] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
qWarning() << "[Black] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
||||||
@ -224,6 +225,8 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
|||||||
}
|
}
|
||||||
phone.prepend('+');
|
phone.prepend('+');
|
||||||
|
|
||||||
|
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
|
||||||
|
|
||||||
QJsonObject profileBody;
|
QJsonObject profileBody;
|
||||||
profileBody["first_name"] = parts.value(0);
|
profileBody["first_name"] = parts.value(0);
|
||||||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
|
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["phone_number"] = phone;
|
||||||
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
||||||
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
||||||
|
profileBody["device_id"] = device.apiId;
|
||||||
profileBody["currency"] = currencyCode;
|
profileBody["currency"] = currencyCode;
|
||||||
|
|
||||||
NetworkService ns;
|
NetworkService ns;
|
||||||
|
|||||||
@ -34,12 +34,12 @@ protected:
|
|||||||
int &counter, int width, int height
|
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(
|
void updateTransactionData(
|
||||||
const AccountInfo &account,
|
const MaterialInfo &account,
|
||||||
int transactionId,
|
int transactionId,
|
||||||
TransactionStatus status,
|
TransactionStatus status,
|
||||||
const QString &oldDesc,
|
const QString &oldDesc,
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
|
|
||||||
@ -25,8 +25,8 @@ GetCardInfoScript::GetCardInfoScript(
|
|||||||
GetCardInfoScript::~GetCardInfoScript() = default;
|
GetCardInfoScript::~GetCardInfoScript() = default;
|
||||||
|
|
||||||
void GetCardInfoScript::doStart() {
|
void GetCardInfoScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id == -1) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -41,7 +41,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем
|
// Если не на домашнем экране — перезапускаем
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[Black::GetCardInfo] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -55,7 +55,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
// 1. Парсим баланс с домашнего экрана
|
// 1. Парсим баланс с домашнего экрана
|
||||||
const double balance = xmlScreenParser.parseBalanceFromHomeScreen();
|
const double balance = xmlScreenParser.parseBalanceFromHomeScreen();
|
||||||
qDebug() << "[Black::GetCardInfo] balance from home:" << balance;
|
qDebug() << "[Black::GetCardInfo] balance from home:" << balance;
|
||||||
ApplicationDAO::updateAmount(app.id, balance);
|
BankProfileDAO::updateAmount(app.id, balance);
|
||||||
|
|
||||||
// 2. Нажимаем "Tu CVU" на домашнем экране
|
// 2. Нажимаем "Tu CVU" на домашнем экране
|
||||||
Node cvuButton = xmlScreenParser.findButtonNode("Tu CVU", false);
|
Node cvuButton = xmlScreenParser.findButtonNode("Tu CVU", false);
|
||||||
@ -104,24 +104,24 @@ void GetCardInfoScript::doStart() {
|
|||||||
// 5. Сохраняем аккаунт в БД (lastNumbers = последние 4 цифры CVU)
|
// 5. Сохраняем аккаунт в БД (lastNumbers = последние 4 цифры CVU)
|
||||||
const QString lastNumbers = cvuNumber.right(4);
|
const QString lastNumbers = cvuNumber.right(4);
|
||||||
|
|
||||||
AccountInfo account;
|
MaterialInfo account;
|
||||||
account.deviceId = m_deviceId;
|
account.deviceId = device.id;
|
||||||
account.appCode = m_appCode;
|
account.appCode = m_appCode;
|
||||||
account.lastNumbers = lastNumbers;
|
account.lastNumbers = lastNumbers;
|
||||||
account.cardNumber = cvuNumber;
|
account.cardNumber = cvuNumber;
|
||||||
account.amount = balance;
|
account.amount = balance;
|
||||||
account.currency = "USD";
|
account.currency = "USD";
|
||||||
account.description = "CVU";
|
account.description = "CVU";
|
||||||
account.status = AccountStatus::Active;
|
account.status = MaterialStatus::Active;
|
||||||
account.updateTime = QDateTime::currentDateTimeUtc();
|
account.updateTime = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
if (!AccountDAO::upsertAccount(account)) {
|
if (!MaterialDAO::upsertAccount(account)) {
|
||||||
qWarning() << "[Black::GetCardInfo] Failed to upsert account";
|
qWarning() << "[Black::GetCardInfo] Failed to upsert account";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Отправляем материал на сервер (если ещё нет material_id)
|
// 6. Отправляем материал на сервер (если ещё нет material_id)
|
||||||
const AccountInfo savedAccount = AccountDAO::findAppAccount(m_deviceId, m_appCode, lastNumbers);
|
const MaterialInfo savedAccount = MaterialDAO::findAppAccount(device.id, m_appCode, lastNumbers);
|
||||||
const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (savedAccount.id != -1 && !updatedApp.bankProfileId.isEmpty()) {
|
if (savedAccount.id != -1 && !updatedApp.bankProfileId.isEmpty()) {
|
||||||
if (savedAccount.materialId.isEmpty()) {
|
if (savedAccount.materialId.isEmpty()) {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "AppLogger.h"
|
#include "AppLogger.h"
|
||||||
@ -23,8 +23,8 @@ GetLastTransactionsScript::GetLastTransactionsScript(
|
|||||||
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
||||||
|
|
||||||
void GetLastTransactionsScript::doStart() {
|
void GetLastTransactionsScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id == -1) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -38,7 +38,7 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем приложение
|
// Если не на домашнем экране — перезапускаем приложение
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[Black::GetLastTransactions] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -113,7 +113,7 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
|
|
||||||
// Находим accountId для сохранения транзакций
|
// Находим accountId для сохранения транзакций
|
||||||
int accountId = -1;
|
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()) {
|
if (!accounts.isEmpty()) {
|
||||||
accountId = accounts.first().id;
|
accountId = accounts.first().id;
|
||||||
qDebug() << "[Black::GetLastTransactions] Using accountId:" << accountId;
|
qDebug() << "[Black::GetLastTransactions] Using accountId:" << accountId;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
|
|
||||||
@ -24,8 +24,8 @@ GetProfileInfoScript::GetProfileInfoScript(
|
|||||||
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
||||||
|
|
||||||
void GetProfileInfoScript::doStart() {
|
void GetProfileInfoScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id == -1) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -40,7 +40,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем
|
// Если не на домашнем экране — перезапускаем
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[Black::GetProfileInfo] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -67,7 +67,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
qDebug() << "[Black::GetProfileInfo] fullName from home:" << fullName << "balance:" << balance;
|
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());
|
AdbUtils::makeTap(m_deviceId, holaNode.x(), holaNode.y());
|
||||||
QThread::msleep(2000);
|
QThread::msleep(2000);
|
||||||
@ -155,7 +155,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
<< "fullName:" << parsedFullName;
|
<< "fullName:" << parsedFullName;
|
||||||
|
|
||||||
// 7. Сохраняем в БД
|
// 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";
|
qWarning() << "[Black::GetProfileInfo] Failed to save profile info to DB";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +163,8 @@ void GetProfileInfoScript::doStart() {
|
|||||||
createBankProfileIfNeeded(m_deviceId, m_appCode);
|
createBankProfileIfNeeded(m_deviceId, m_appCode);
|
||||||
|
|
||||||
// 8. Отправляем данные аккаунтов на сервер если нет material_id
|
// 8. Отправляем данные аккаунтов на сервер если нет material_id
|
||||||
const QList<AccountInfo> allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||||
QList<QPair<AccountInfo, Node>> accountsToPost;
|
QList<QPair<MaterialInfo, Node>> accountsToPost;
|
||||||
for (const auto &acc : allAccounts) {
|
for (const auto &acc : allAccounts) {
|
||||||
if (acc.materialId.isEmpty()) {
|
if (acc.materialId.isEmpty()) {
|
||||||
accountsToPost.append({acc, Node()});
|
accountsToPost.append({acc, Node()});
|
||||||
|
|||||||
@ -19,7 +19,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "AppLogger.h"
|
#include "AppLogger.h"
|
||||||
#include "GetProfileInfoScript.h"
|
#include "GetProfileInfoScript.h"
|
||||||
@ -22,8 +22,9 @@ LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
|||||||
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
||||||
|
|
||||||
void LoginAndCheckAccountsScript::doStart() {
|
void LoginAndCheckAccountsScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
// m_deviceId = ADB serial, device.id = android_id (для БД)
|
||||||
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
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));
|
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;
|
m_error = "Cant open the home screen: " + m_deviceId + " " + m_appCode;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
AppLogger::log("black/LoginAndCheck", m_deviceId, m_error,
|
AppLogger::log("black/LoginAndCheck", m_deviceId, m_error,
|
||||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||||
"FETCH_PROFILE");
|
"FETCH_PROFILE");
|
||||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "no", "Не прошла проверка [дата]")) {
|
BankProfileDAO::updateComment(app.id,
|
||||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
"Не прошла проверка " + QDateTime::currentDateTime().toString("dd.MM.yyyy HH:mm:ss"));
|
||||||
}
|
|
||||||
|
|
||||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PIN-код прошёл
|
|
||||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) {
|
|
||||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем профиль и аккаунты (приложение уже на home screen)
|
// Проверяем профиль и аккаунты (приложение уже на home screen)
|
||||||
GetProfileInfoScript profileScript(m_deviceId, m_appCode);
|
GetProfileInfoScript profileScript(m_deviceId, m_appCode);
|
||||||
profileScript.start();
|
profileScript.start();
|
||||||
|
|||||||
@ -19,7 +19,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "AppLogger.h"
|
#include "AppLogger.h"
|
||||||
@ -12,7 +12,7 @@
|
|||||||
namespace Black {
|
namespace Black {
|
||||||
|
|
||||||
PayByCardScript::PayByCardScript(
|
PayByCardScript::PayByCardScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString cardNumber,
|
QString cardNumber,
|
||||||
double amount,
|
double amount,
|
||||||
QObject *parent
|
QObject *parent
|
||||||
@ -26,7 +26,7 @@ PayByCardScript::~PayByCardScript() = default;
|
|||||||
|
|
||||||
void PayByCardScript::doStart() {
|
void PayByCardScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_account.deviceId;
|
m_error = "Device or app not found: " + m_account.deviceId;
|
||||||
@ -35,7 +35,7 @@ void PayByCardScript::doStart() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
makePayment(device.adbSerial, app.pinCode, device.screenWidth, device.screenHeight);
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ void PayByCardScript::makePayment(
|
|||||||
// 0. Убеждаемся что на домашнем экране
|
// 0. Убеждаемся что на домашнем экране
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[Black::PayByCard] Not on home screen, restarting app...";
|
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)) {
|
if (!runAppAndGoToHomeScreen(deviceId, app.package, pinCode, width, height)) {
|
||||||
m_error = "Cannot reach home screen: " + deviceId;
|
m_error = "Cannot reach home screen: " + deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class PayByCardScript final : public CommonScript {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
PayByCardScript(
|
PayByCardScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString cardNumber,
|
QString cardNumber,
|
||||||
double amount,
|
double amount,
|
||||||
QObject *parent = nullptr
|
QObject *parent = nullptr
|
||||||
@ -20,7 +20,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const AccountInfo m_account;
|
const MaterialInfo m_account;
|
||||||
const QString m_cardNumber;
|
const QString m_cardNumber;
|
||||||
const double m_amount;
|
const double m_amount;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include "android/xml/Node.h"
|
#include "android/xml/Node.h"
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "db/TransactionInfo.h"
|
#include "db/TransactionInfo.h"
|
||||||
|
|
||||||
namespace Black {
|
namespace Black {
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
|
|
||||||
namespace Ozon {
|
namespace Ozon {
|
||||||
@ -113,11 +114,18 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем на рекламный bottom sheet (ипотека и т.п.)
|
// Проверяем на рекламный bottom sheet (ипотека, открыть счёт и т.п.)
|
||||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||||
qDebug() << "[runAppAndGoToHomeScreen] Ad bottom sheet detected, closing...";
|
qDebug() << "[runAppAndGoToHomeScreen] Ad bottom sheet detected, closing...";
|
||||||
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
|
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
|
||||||
QThread::msleep(1000);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +195,7 @@ bool CommonScript::goToAllProducts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CommonScript::findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers) {
|
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) {
|
for (auto &[account, node]: accounts) {
|
||||||
// FIXME будем считать что числа последние везде уникальные
|
// FIXME будем считать что числа последние везде уникальные
|
||||||
if (accountNumbers == account.lastNumbers) {
|
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()) {
|
if (accounts.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -210,15 +218,15 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &acco
|
|||||||
auto *worker = new NetworkService;
|
auto *worker = new NetworkService;
|
||||||
|
|
||||||
QJsonArray list;
|
QJsonArray list;
|
||||||
for (const QPair<AccountInfo, Node> &pair: accounts) {
|
for (const QPair<MaterialInfo, Node> &pair: accounts) {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
AccountInfo account = pair.first;
|
MaterialInfo account = pair.first;
|
||||||
obj["appName"] = account.appCode;
|
obj["appName"] = account.appCode;
|
||||||
obj["lastNumbers"] = account.lastNumbers;
|
obj["lastNumbers"] = account.lastNumbers;
|
||||||
obj["amount"] = account.amount;
|
obj["amount"] = account.amount;
|
||||||
obj["description"] = account.description;
|
obj["description"] = account.description;
|
||||||
obj["currency"] = account.currency;
|
obj["currency"] = account.currency;
|
||||||
obj["status"] = accountStatusToString(account.status);
|
obj["status"] = materialStatusToString(account.status);
|
||||||
|
|
||||||
list.append(obj);
|
list.append(obj);
|
||||||
}
|
}
|
||||||
@ -233,7 +241,7 @@ void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &acco
|
|||||||
thread->start();
|
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 *thread = new QThread;
|
||||||
auto *worker = new NetworkService;
|
auto *worker = new NetworkService;
|
||||||
|
|
||||||
@ -265,7 +273,7 @@ void CommonScript::postNewTransactionData(const AccountInfo &account, const Tran
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommonScript::updateTransactionData(
|
void CommonScript::updateTransactionData(
|
||||||
const AccountInfo &account,
|
const MaterialInfo &account,
|
||||||
const int transactionId,
|
const int transactionId,
|
||||||
const TransactionStatus status,
|
const TransactionStatus status,
|
||||||
const QString &oldDesc,
|
const QString &oldDesc,
|
||||||
@ -293,7 +301,7 @@ void CommonScript::updateTransactionData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) {
|
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()) {
|
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
|
||||||
qWarning() << "[Ozon] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
qWarning() << "[Ozon] createBankProfileIfNeeded: skipping — fullName or phone is empty";
|
||||||
@ -320,6 +328,8 @@ void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QStr
|
|||||||
}
|
}
|
||||||
phone.prepend('+');
|
phone.prepend('+');
|
||||||
|
|
||||||
|
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
|
||||||
|
|
||||||
QJsonObject profileBody;
|
QJsonObject profileBody;
|
||||||
profileBody["first_name"] = parts.value(0);
|
profileBody["first_name"] = parts.value(0);
|
||||||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
|
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["phone_number"] = phone;
|
||||||
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
profileBody["balance"] = QString::number(qRound64(app.amount * 100));
|
||||||
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
|
||||||
|
profileBody["device_id"] = device.apiId;
|
||||||
profileBody["currency"] = currencyCode;
|
profileBody["currency"] = currencyCode;
|
||||||
|
|
||||||
NetworkService ns;
|
NetworkService ns;
|
||||||
|
|||||||
@ -27,12 +27,12 @@ protected:
|
|||||||
|
|
||||||
bool findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers);
|
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(
|
void updateTransactionData(
|
||||||
const AccountInfo &account,
|
const MaterialInfo &account,
|
||||||
int transactionId,
|
int transactionId,
|
||||||
TransactionStatus status,
|
TransactionStatus status,
|
||||||
const QString &oldDesc,
|
const QString &oldDesc,
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/SettingsDAO.h"
|
#include "dao/SettingsDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
@ -30,8 +30,8 @@ GetCardInfoScript::GetCardInfoScript(
|
|||||||
GetCardInfoScript::~GetCardInfoScript() = default;
|
GetCardInfoScript::~GetCardInfoScript() = default;
|
||||||
|
|
||||||
void GetCardInfoScript::doStart() {
|
void GetCardInfoScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id == -1) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -57,7 +57,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем приложение
|
// Если не на домашнем экране — перезапускаем приложение
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[GetCardInfo] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -97,7 +97,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||||||
const QString phone = xmlScreenParser.parseProfilePhone();
|
const QString phone = xmlScreenParser.parseProfilePhone();
|
||||||
qDebug() << "[GetCardInfo] Profile parsed - fullName:" << fullName << "phone:" << phone;
|
qDebug() << "[GetCardInfo] Profile parsed - fullName:" << fullName << "phone:" << phone;
|
||||||
ApplicationDAO::updateProfileInfo(app.id, fullName, phone);
|
BankProfileDAO::updateProfileInfo(app.id, fullName, phone);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "[GetCardInfo] Profile screen not reached, skipping profile parse";
|
qWarning() << "[GetCardInfo] Profile screen not reached, skipping profile parse";
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[GetCardInfo] Not on home screen before card search, restarting...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen before card search: " + m_deviceId;
|
m_error = "Cannot reach home screen before card search: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -129,7 +129,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ждём пока все карты подгрузятся — до 5 попыток
|
// Ждём пока все карты подгрузятся — до 5 попыток
|
||||||
QList<AccountInfo> cards;
|
QList<MaterialInfo> cards;
|
||||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
cards = xmlScreenParser.parseCardsOnHomeScreen();
|
cards = xmlScreenParser.parseCardsOnHomeScreen();
|
||||||
@ -149,12 +149,12 @@ void GetCardInfoScript::doStart() {
|
|||||||
qDebug() << "[GetCardInfo] Found" << cards.size() << "card(s) on home screen";
|
qDebug() << "[GetCardInfo] Found" << cards.size() << "card(s) on home screen";
|
||||||
|
|
||||||
// Апсёртим аккаунты для всех найденных карт
|
// Апсёртим аккаунты для всех найденных карт
|
||||||
for (AccountInfo &card : cards) {
|
for (MaterialInfo &card : cards) {
|
||||||
card.deviceId = m_deviceId;
|
card.deviceId = device.id;
|
||||||
card.appCode = m_appCode;
|
card.appCode = m_appCode;
|
||||||
card.updateTime = QDateTime::currentDateTimeUtc();
|
card.updateTime = QDateTime::currentDateTimeUtc();
|
||||||
card.status = AccountStatus::Active;
|
card.status = MaterialStatus::Active;
|
||||||
if (!AccountDAO::upsertAccount(card)) {
|
if (!MaterialDAO::upsertAccount(card)) {
|
||||||
qWarning() << "[GetCardInfo] Failed to upsert account for card" << card.lastNumbers;
|
qWarning() << "[GetCardInfo] Failed to upsert account for card" << card.lastNumbers;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "[GetCardInfo] Upserted account:" << card.lastNumbers
|
qDebug() << "[GetCardInfo] Upserted account:" << card.lastNumbers
|
||||||
@ -165,7 +165,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
// Сохраняем баланс домашнего экрана в application
|
// Сохраняем баланс домашнего экрана в application
|
||||||
const double totalAmount = cards.isEmpty() ? 0.0 : cards[0].amount;
|
const double totalAmount = cards.isEmpty() ? 0.0 : cards[0].amount;
|
||||||
if (totalAmount > 0.0) {
|
if (totalAmount > 0.0) {
|
||||||
ApplicationDAO::updateAmount(app.id, totalAmount);
|
BankProfileDAO::updateAmount(app.id, totalAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Синхронная отправка на сервер (NetworkService блокируется через QEventLoop внутри)
|
// Синхронная отправка на сервер (NetworkService блокируется через QEventLoop внутри)
|
||||||
@ -188,7 +188,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
|
|
||||||
// 2. Отправляем банковский профиль (используем свежие данные из БД — имя/телефон уже спарсены)
|
// 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);
|
const QStringList parts = freshApp.fullName.split(' ', Qt::SkipEmptyParts);
|
||||||
|
|
||||||
// ISO 4217 numeric currency codes
|
// ISO 4217 numeric currency codes
|
||||||
@ -207,6 +207,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
profileBody["phone_number"] = phone;
|
profileBody["phone_number"] = phone;
|
||||||
profileBody["balance"] = QString::number(qRound64(totalAmount * 100));
|
profileBody["balance"] = QString::number(qRound64(totalAmount * 100));
|
||||||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||||||
|
profileBody["device_id"] = device.apiId;
|
||||||
profileBody["currency"] = currencyCode;
|
profileBody["currency"] = currencyCode;
|
||||||
ns.setPayload(QJsonDocument(profileBody).toJson());
|
ns.setPayload(QJsonDocument(profileBody).toJson());
|
||||||
|
|
||||||
@ -214,7 +215,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
ns.addExtra("app_id", freshApp.id);
|
ns.addExtra("app_id", freshApp.id);
|
||||||
ns.postBankProfile();
|
ns.postBankProfile();
|
||||||
// Проверяем что bankProfileId сохранился
|
// Проверяем что bankProfileId сохранился
|
||||||
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
if (afterPost.bankProfileId.isEmpty()) {
|
if (afterPost.bankProfileId.isEmpty()) {
|
||||||
const QString err = "postBankProfile failed for " + m_deviceId;
|
const QString err = "postBankProfile failed for " + m_deviceId;
|
||||||
qWarning() << "[GetCardInfo]" << err;
|
qWarning() << "[GetCardInfo]" << err;
|
||||||
@ -233,7 +234,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
|
|
||||||
// Для каждой карты переходим на её экран и получаем полный номер через OCR
|
// Для каждой карты переходим на её экран и получаем полный номер через OCR
|
||||||
for (int i = 0; i < cards.size(); ++i) {
|
for (int i = 0; i < cards.size(); ++i) {
|
||||||
const AccountInfo &card = cards[i];
|
const MaterialInfo &card = cards[i];
|
||||||
|
|
||||||
// Если передан конкретный lastNumbers — обрабатываем только его
|
// Если передан конкретный lastNumbers — обрабатываем только его
|
||||||
if (!m_lastNumbers.isEmpty() && card.lastNumbers != m_lastNumbers) continue;
|
if (!m_lastNumbers.isEmpty() && card.lastNumbers != m_lastNumbers) continue;
|
||||||
@ -241,7 +242,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
qDebug() << "[GetCardInfo] Getting card number for" << card.lastNumbers;
|
qDebug() << "[GetCardInfo] Getting card number for" << card.lastNumbers;
|
||||||
|
|
||||||
// Если номер карты уже есть в БД — сумма уже обновлена через upsert, навигация не нужна
|
// Если номер карты уже есть в БД — сумма уже обновлена через 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()) {
|
if (existing.id != -1 && !existing.cardNumber.isEmpty()) {
|
||||||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "already has number, skipping";
|
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "already has number, skipping";
|
||||||
continue;
|
continue;
|
||||||
@ -339,16 +340,16 @@ void GetCardInfoScript::doStart() {
|
|||||||
if (!cardNumber.isEmpty()) {
|
if (!cardNumber.isEmpty()) {
|
||||||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
|
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
|
||||||
// Получаем id аккаунта из БД и сохраняем номер карты
|
// Получаем 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) {
|
if (saved.id != -1) {
|
||||||
AccountDAO::updateCardNumber(saved.id, cardNumber);
|
MaterialDAO::updateCardNumber(saved.id, cardNumber);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const QString err = "Card number not found for " + card.lastNumbers;
|
const QString err = "Card number not found for " + card.lastNumbers;
|
||||||
qWarning() << "[GetCardInfo]" << err;
|
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) {
|
if (saved.id != -1) {
|
||||||
AccountDAO::updateAccountById(saved.id, accountStatusToString(AccountStatus::Off),
|
MaterialDAO::updateAccountById(saved.id, materialStatusToString(MaterialStatus::Off),
|
||||||
saved.description, saved.currency);
|
saved.description, saved.currency);
|
||||||
}
|
}
|
||||||
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
|
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
|
||||||
@ -365,10 +366,10 @@ void GetCardInfoScript::doStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
|
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
|
||||||
const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
if (!updatedApp.bankProfileId.isEmpty()) {
|
if (!updatedApp.bankProfileId.isEmpty()) {
|
||||||
for (const AccountInfo &card : cards) {
|
for (const MaterialInfo &card : cards) {
|
||||||
const AccountInfo dbCard = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
|
const MaterialInfo dbCard = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||||||
if (dbCard.cardNumber.isEmpty()) continue;
|
if (dbCard.cardNumber.isEmpty()) continue;
|
||||||
if (!dbCard.materialId.isEmpty()) {
|
if (!dbCard.materialId.isEmpty()) {
|
||||||
qDebug() << "[GetCardInfo] material already exists for card" << dbCard.lastNumbers << ", skipping";
|
qDebug() << "[GetCardInfo] material already exists for card" << dbCard.lastNumbers << ", skipping";
|
||||||
@ -379,7 +380,7 @@ void GetCardInfoScript::doStart() {
|
|||||||
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
||||||
materialBody["card_number"] = dbCard.cardNumber;
|
materialBody["card_number"] = dbCard.cardNumber;
|
||||||
materialBody["name"] = updatedApp.fullName;
|
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.setPayload(QJsonDocument(materialBody).toJson());
|
||||||
ns.addExtra("account_id", dbCard.id);
|
ns.addExtra("account_id", dbCard.id);
|
||||||
ns.postMaterial();
|
ns.postMaterial();
|
||||||
|
|||||||
@ -23,7 +23,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
const QString m_lastNumbers;
|
const QString m_lastNumbers;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
#include <leptonica/allheaders.h>
|
#include <leptonica/allheaders.h>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "time/DateUtils.h"
|
#include "time/DateUtils.h"
|
||||||
@ -33,7 +33,6 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to grayscale for better OCR accuracy
|
|
||||||
Pix *gray = pixConvertRGBToGray(pix, 0.0f, 0.0f, 0.0f);
|
Pix *gray = pixConvertRGBToGray(pix, 0.0f, 0.0f, 0.0f);
|
||||||
pixDestroy(&pix);
|
pixDestroy(&pix);
|
||||||
if (!gray) {
|
if (!gray) {
|
||||||
@ -44,8 +43,6 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api->SetImage(gray);
|
api->SetImage(gray);
|
||||||
// adb screencap PNGs lack DPI metadata; set 300 DPI so Tesseract
|
|
||||||
// interprets character sizes correctly
|
|
||||||
api->SetSourceResolution(300);
|
api->SetSourceResolution(300);
|
||||||
char *text = api->GetUTF8Text();
|
char *text = api->GetUTF8Text();
|
||||||
QString result = QString::fromUtf8(text).trimmed();
|
QString result = QString::fromUtf8(text).trimmed();
|
||||||
@ -58,12 +55,9 @@ static QString recognizeTextFromImage(const QByteArray &pngData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static QString extractTransactionId(const QString &ocrText) {
|
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]+))");
|
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);
|
if (auto m = reId.match(ocrText); m.hasMatch()) return m.captured(1);
|
||||||
|
|
||||||
// "№ Ф-2026-25235663"
|
|
||||||
QRegularExpression reRef(R"(№\s*(Ф-[\d-]+))");
|
QRegularExpression reRef(R"(№\s*(Ф-[\d-]+))");
|
||||||
if (auto m = reRef.match(ocrText); m.hasMatch()) return m.captured(1);
|
if (auto m = reRef.match(ocrText); m.hasMatch()) return m.captured(1);
|
||||||
|
|
||||||
@ -76,16 +70,24 @@ GetLastTransactionsScript::GetLastTransactionsScript(
|
|||||||
QString deviceId,
|
QString deviceId,
|
||||||
QString appCode,
|
QString appCode,
|
||||||
int maxCount,
|
int maxCount,
|
||||||
|
double searchAmount,
|
||||||
|
QDateTime searchTime,
|
||||||
QObject *parent
|
QObject *parent
|
||||||
) : CommonScript(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;
|
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
||||||
|
|
||||||
void GetLastTransactionsScript::doStart() {
|
void GetLastTransactionsScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -94,22 +96,49 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
return;
|
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));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
|
|
||||||
// Закрываем рекламный bottom sheet, если открыт
|
// Закрываем рекламный bottom sheet
|
||||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||||
qDebug() << "[GetLastTransactions] Ad banner detected, closing...";
|
|
||||||
AdbUtils::makeTap(m_deviceId, adNode.x(), adNode.y());
|
AdbUtils::makeTap(m_deviceId, adNode.x(), adNode.y());
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
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));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
|
|
||||||
// Если не на домашнем экране — перезапускаем приложение
|
// Если не на домашнем экране — перезапускаем
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[GetLastTransactions] Not on home screen, restarting app...";
|
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
|
||||||
device.screenWidth, device.screenHeight)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId;
|
m_error = "Cannot reach home screen: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -117,8 +146,7 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||||
"FETCH_TRANSACTIONS");
|
"FETCH_TRANSACTIONS");
|
||||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||||
emit finishedWithResult(m_error);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
}
|
}
|
||||||
@ -128,13 +156,10 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
QThread::msleep(500);
|
QThread::msleep(500);
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
|
|
||||||
// Скроллим вниз до кнопки "Все" и тапаем по ней
|
// Скроллим до "Все" и тапаем
|
||||||
// Кнопка "Все" на домашнем экране находится у границы навбара (y ≈ 2190),
|
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
||||||
// поэтому сначала скроллим вниз, чтобы она поднялась выше навигационной панели
|
|
||||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10); // ~90% высоты экрана
|
|
||||||
Node allBtn;
|
Node allBtn;
|
||||||
for (int i = 0; i < 10; ++i) {
|
for (int i = 0; i < 10; ++i) {
|
||||||
// Сначала скроллим, потом ищем
|
|
||||||
const int x = device.screenWidth / 2;
|
const int x = device.screenWidth / 2;
|
||||||
const int offset = device.screenHeight / 4;
|
const int offset = device.screenHeight / 4;
|
||||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset);
|
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));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
|
|
||||||
allBtn = xmlScreenParser.findButtonNode("Все", false);
|
allBtn = xmlScreenParser.findButtonNode("Все", false);
|
||||||
if (!allBtn.isEmpty() && allBtn.y() > 0 && allBtn.y() < navBarTop) {
|
if (!allBtn.isEmpty() && allBtn.y() > 0 && allBtn.y() < navBarTop) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
allBtn = {};
|
allBtn = {};
|
||||||
qDebug() << "[GetLastTransactions] 'Все' not found above navbar, attempt" << i + 1 << "/ 10";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allBtn.isEmpty()) {
|
if (allBtn.isEmpty()) {
|
||||||
m_error = "Button 'Все' not found after scrolling: " + m_deviceId;
|
m_error = "Button 'Все' not found: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
emit finishedWithResult(m_error);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "[GetLastTransactions] Found 'Все' at" << allBtn.x() << allBtn.y();
|
|
||||||
AdbUtils::makeTap(m_deviceId, allBtn.x(), allBtn.y());
|
AdbUtils::makeTap(m_deviceId, allBtn.x(), allBtn.y());
|
||||||
QThread::msleep(2000);
|
QThread::msleep(2000);
|
||||||
|
|
||||||
// Ждём загрузки экрана "Операции"
|
// Ждём экран "Операции"
|
||||||
bool loaded = false;
|
|
||||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
if (xmlScreenParser.isTransactionListScreen()) {
|
if (xmlScreenParser.isTransactionListScreen()) return true;
|
||||||
loaded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
qDebug() << "[GetLastTransactions] Waiting for transaction list screen, attempt" << attempt + 1 << "/ 10";
|
|
||||||
QThread::msleep(1500);
|
QThread::msleep(1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loaded) {
|
m_error = "Transaction list screen not loaded: " + m_deviceId;
|
||||||
m_error = "Transaction list screen not loaded: " + m_deviceId;
|
qWarning() << m_error;
|
||||||
qWarning() << m_error;
|
return false;
|
||||||
emit finishedWithResult(m_error);
|
}
|
||||||
return;
|
|
||||||
|
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;
|
QList<Node> txButtons;
|
||||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||||
if (!txButtons.isEmpty()) break;
|
if (!txButtons.isEmpty()) break;
|
||||||
qDebug() << "[GetLastTransactions] No transactions yet, attempt" << attempt + 1 << "/ 5";
|
|
||||||
QThread::msleep(1500);
|
QThread::msleep(1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "[GetLastTransactions] Found" << txButtons.size() << "transaction buttons";
|
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;
|
QList<TransactionInfo> transactions;
|
||||||
for (int i = 0; i < txButtons.size(); ++i) {
|
for (int i = 0; i < txButtons.size(); ++i) {
|
||||||
const Node &btn = txButtons[i];
|
const Node &btn = txButtons[i];
|
||||||
|
|
||||||
// Если кнопка внизу экрана (за навбаром) — скроллим до неё
|
// Скроллим до кнопки если за экраном
|
||||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
|
||||||
if (btn.y() <= 0 || btn.y() >= navBarTop) {
|
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) {
|
for (int s = 0; s < 5; ++s) {
|
||||||
const int x = device.screenWidth / 2;
|
const int x = device.screenWidth / 2;
|
||||||
const int offset = device.screenHeight / 4;
|
const int offset = device.screenHeight / 4;
|
||||||
@ -217,121 +327,84 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||||
if (i < txButtons.size() && txButtons[i].y() > 0 && txButtons[i].y() < navBarTop) {
|
if (i < txButtons.size() && txButtons[i].y() > 0 && txButtons[i].y() < navBarTop) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i >= txButtons.size()) {
|
|
||||||
qWarning() << "[GetLastTransactions] Transaction" << i + 1 << "not found after scrolling";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (i >= txButtons.size()) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Node &visibleBtn = txButtons[i];
|
const Node &visibleBtn = txButtons[i];
|
||||||
TransactionInfo tx = xmlScreenParser.parseTransactionButtonText(visibleBtn.text);
|
TransactionInfo tx = xmlScreenParser.parseTransactionButtonText(visibleBtn.text);
|
||||||
|
|
||||||
qDebug() << "[GetLastTransactions] Tapping transaction" << i + 1 << ":" << visibleBtn.text.left(50);
|
|
||||||
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
||||||
QThread::msleep(2000);
|
QThread::msleep(2000);
|
||||||
|
|
||||||
// Ждём загрузки деталей — ищем дату в шапке
|
// Ждём загрузки деталей
|
||||||
TransactionInfo detail;
|
TransactionInfo detail;
|
||||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
detail = xmlScreenParser.parseTransactionDetail();
|
detail = xmlScreenParser.parseTransactionDetail();
|
||||||
if (!detail.bankTime.isEmpty()) break;
|
if (!detail.bankTime.isEmpty()) break;
|
||||||
|
|
||||||
// Если всё ещё на экране операций — тап не сработал, повторяем
|
|
||||||
if (xmlScreenParser.isTransactionListScreen()) {
|
if (xmlScreenParser.isTransactionListScreen()) {
|
||||||
qDebug() << "[GetLastTransactions] Still on transaction list, re-tapping" << i + 1;
|
|
||||||
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "[GetLastTransactions] Detail not loaded, attempt" << attempt + 1 << "/ 10";
|
|
||||||
QThread::msleep(1500);
|
QThread::msleep(1500);
|
||||||
}
|
}
|
||||||
tx.bankName = "ozon";
|
|
||||||
|
tx.bankName = m_appCode;
|
||||||
tx.bankTime = detail.bankTime;
|
tx.bankTime = detail.bankTime;
|
||||||
tx.status = TransactionStatus::Complete;
|
tx.status = TransactionStatus::Complete;
|
||||||
tx.timestamp = DateUtils::getUtcNow();
|
tx.timestamp = DateUtils::getUtcNow();
|
||||||
if (!detail.name.isEmpty()) tx.name = detail.name;
|
if (!detail.name.isEmpty()) tx.name = detail.name;
|
||||||
if (!detail.phone.isEmpty()) {
|
if (!detail.phone.isEmpty()) {
|
||||||
// Оставляем только цифры
|
|
||||||
QString cleanPhone;
|
QString cleanPhone;
|
||||||
for (const QChar &ch : detail.phone) {
|
for (const QChar &ch : detail.phone)
|
||||||
if (ch.isDigit()) cleanPhone.append(ch);
|
if (ch.isDigit()) cleanPhone.append(ch);
|
||||||
}
|
|
||||||
tx.phone = cleanPhone;
|
tx.phone = cleanPhone;
|
||||||
tx.type = TransactionType::Phone;
|
tx.type = TransactionType::Phone;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Чистим bankTime от unicode символов: заменяем все не-ASCII пробелы на обычные
|
// Парсим время
|
||||||
if (!tx.bankTime.isEmpty()) {
|
if (!tx.bankTime.isEmpty()) {
|
||||||
QString dtStr = tx.bankTime;
|
QString dtStr = tx.bankTime;
|
||||||
for (int ci = 0; ci < dtStr.size(); ++ci) {
|
for (int ci = 0; ci < dtStr.size(); ++ci) {
|
||||||
const QChar ch = dtStr.at(ci);
|
const QChar ch = dtStr.at(ci);
|
||||||
if (ch != ' ' && ch.category() == QChar::Separator_Space) {
|
if (ch != ' ' && ch.category() == QChar::Separator_Space) dtStr[ci] = ' ';
|
||||||
dtStr[ci] = ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tx.bankTime = dtStr;
|
tx.bankTime = dtStr;
|
||||||
const QTimeZone msk("Europe/Moscow");
|
|
||||||
QDateTime parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
|
QDateTime parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
|
||||||
if (!parsed.isValid()) {
|
if (!parsed.isValid()) parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
||||||
parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
|
||||||
}
|
|
||||||
if (parsed.isValid()) {
|
if (parsed.isValid()) {
|
||||||
parsed.setTimeZone(msk);
|
parsed.setTimeZone(msk);
|
||||||
tx.completeTime = parsed;
|
tx.completeTime = parsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "[GetLastTransactions]"
|
// Фильтр по времени (24ч) если нет maxCount
|
||||||
<< "date:" << tx.bankTime
|
if (m_maxCount <= 0 && tx.completeTime.isValid()) {
|
||||||
<< "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()) {
|
|
||||||
const qint64 ageSecs = tx.completeTime.toUTC().secsTo(QDateTime::currentDateTimeUtc());
|
const qint64 ageSecs = tx.completeTime.toUTC().secsTo(QDateTime::currentDateTimeUtc());
|
||||||
if (ageSecs > 24 * 3600) {
|
if (ageSecs > 24 * 3600) {
|
||||||
qDebug() << "[GetLastTransactions] Transaction older than 24h, stopping";
|
|
||||||
AdbUtils::goBack(m_deviceId);
|
AdbUtils::goBack(m_deviceId);
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем в БД если не существует
|
// Сохраняем
|
||||||
// Берём первый аккаунт для этого устройства и приложения
|
|
||||||
if (accountId != -1) {
|
if (accountId != -1) {
|
||||||
tx.accountId = accountId;
|
tx.accountId = accountId;
|
||||||
if (TransactionDAO::insertTransactionIfNotExists(tx)) {
|
if (TransactionDAO::insertTransactionIfNotExists(tx)) {
|
||||||
qDebug() << "[GetLastTransactions] Saved transaction to DB";
|
// Скриншот чека
|
||||||
|
|
||||||
// Делаем скриншот документа транзакции
|
|
||||||
Node docBtn = xmlScreenParser.findButtonNode("Документы", false);
|
Node docBtn = xmlScreenParser.findButtonNode("Документы", false);
|
||||||
if (!docBtn.isEmpty()) {
|
if (!docBtn.isEmpty()) {
|
||||||
qDebug() << "[GetLastTransactions] Tapping 'Документы'...";
|
|
||||||
AdbUtils::makeTap(m_deviceId, docBtn.x(), docBtn.y());
|
AdbUtils::makeTap(m_deviceId, docBtn.x(), docBtn.y());
|
||||||
QThread::msleep(2000);
|
QThread::msleep(2000);
|
||||||
|
|
||||||
// Ждём появления кнопки "Поделиться"
|
|
||||||
bool docLoaded = false;
|
bool docLoaded = false;
|
||||||
for (int da = 0; da < 20; ++da) {
|
for (int da = 0; da < 20; ++da) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
Node shareBtn = xmlScreenParser.findNodeByContentDesc("Поделиться");
|
if (!xmlScreenParser.findNodeByContentDesc("Поделиться").isEmpty()) {
|
||||||
if (!shareBtn.isEmpty()) {
|
docLoaded = true; break;
|
||||||
docLoaded = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
qDebug() << "[GetLastTransactions] Waiting for 'Поделиться', attempt" << da + 1 << "/ 20";
|
|
||||||
QThread::msleep(1500);
|
QThread::msleep(1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,70 +419,42 @@ void GetLastTransactionsScript::doStart() {
|
|||||||
accountId, tx.bankName, tx.bankTime, tx.amount);
|
accountId, tx.bankName, tx.bankTime, tx.amount);
|
||||||
if (saved.id != -1) {
|
if (saved.id != -1) {
|
||||||
TransactionDAO::updateReceiptImage(saved.id, screenshot);
|
TransactionDAO::updateReceiptImage(saved.id, screenshot);
|
||||||
qDebug() << "[GetLastTransactions] Receipt saved to DB, txId:" << saved.id;
|
|
||||||
|
|
||||||
// OCR: распознаём текст и достаём ID операции
|
|
||||||
const QString ocrText = recognizeTextFromImage(screenshot);
|
const QString ocrText = recognizeTextFromImage(screenshot);
|
||||||
if (!ocrText.isEmpty()) {
|
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);
|
const QString txId = extractTransactionId(ocrText);
|
||||||
if (!txId.isEmpty()) {
|
if (!txId.isEmpty()) {
|
||||||
TransactionDAO::updateBankTrExternalId(saved.id, txId);
|
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);
|
AdbUtils::goBack(m_deviceId);
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
qDebug() << "[GetLastTransactions] Transaction already exists or error";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions.append(tx);
|
transactions.append(tx);
|
||||||
|
|
||||||
// Если достигнут лимит по количеству — выходим
|
|
||||||
if (m_maxCount > 0 && transactions.size() >= m_maxCount) {
|
if (m_maxCount > 0 && transactions.size() >= m_maxCount) {
|
||||||
qDebug() << "[GetLastTransactions] Reached maxCount limit:" << m_maxCount;
|
|
||||||
AdbUtils::goBack(m_deviceId);
|
AdbUtils::goBack(m_deviceId);
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаемся в список
|
|
||||||
AdbUtils::goBack(m_deviceId);
|
AdbUtils::goBack(m_deviceId);
|
||||||
QThread::msleep(1500);
|
QThread::msleep(1500);
|
||||||
|
|
||||||
// Ждём возврата на экран списка
|
|
||||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
if (xmlScreenParser.isTransactionListScreen()) break;
|
if (xmlScreenParser.isTransactionListScreen()) break;
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем кнопки (после возврата координаты могут измениться)
|
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit finishedWithResult(m_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ozon
|
} // namespace Ozon
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "CommonScript.h"
|
#include "CommonScript.h"
|
||||||
|
#include <QDateTime>
|
||||||
|
#include "db/DeviceInfo.h"
|
||||||
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
namespace Ozon {
|
namespace Ozon {
|
||||||
|
|
||||||
@ -11,6 +14,8 @@ public:
|
|||||||
QString deviceId,
|
QString deviceId,
|
||||||
QString appCode,
|
QString appCode,
|
||||||
int maxCount = 0,
|
int maxCount = 0,
|
||||||
|
double searchAmount = 0,
|
||||||
|
QDateTime searchTime = {},
|
||||||
QObject *parent = nullptr
|
QObject *parent = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -20,9 +25,20 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
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 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;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
#include "GetProfileInfoScript.h"
|
#include "GetProfileInfoScript.h"
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "AppLogger.h"
|
#include "AppLogger.h"
|
||||||
@ -26,8 +27,8 @@ GetProfileInfoScript::GetProfileInfoScript(
|
|||||||
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
||||||
|
|
||||||
void GetProfileInfoScript::doStart() {
|
void GetProfileInfoScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id == -1) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
||||||
@ -53,7 +54,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
// Если мы не на домашнем экране — перезапускаем приложение и вводим пин-код
|
// Если мы не на домашнем экране — перезапускаем приложение и вводим пин-код
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[GetProfileInfo] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
@ -107,17 +108,18 @@ void GetProfileInfoScript::doStart() {
|
|||||||
|
|
||||||
// Извлекаем полное имя и телефон
|
// Извлекаем полное имя и телефон
|
||||||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
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;
|
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;
|
m_error = "Failed to save profile info to DB: " + m_deviceId;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем bank_profile на сервер
|
// Отправляем 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 = {
|
const QMap<QString, int> currencyCodes = {
|
||||||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}
|
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}
|
||||||
@ -133,6 +135,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
profileBody["phone_number"] = phone;
|
profileBody["phone_number"] = phone;
|
||||||
profileBody["balance"] = QString::number(qRound64(freshApp.amount * 100));
|
profileBody["balance"] = QString::number(qRound64(freshApp.amount * 100));
|
||||||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||||||
|
profileBody["device_id"] = device.apiId;
|
||||||
profileBody["currency"] = currencyCode;
|
profileBody["currency"] = currencyCode;
|
||||||
|
|
||||||
NetworkService ns;
|
NetworkService ns;
|
||||||
@ -142,7 +145,7 @@ void GetProfileInfoScript::doStart() {
|
|||||||
if (freshApp.bankProfileId.isEmpty()) {
|
if (freshApp.bankProfileId.isEmpty()) {
|
||||||
ns.addExtra("app_id", freshApp.id);
|
ns.addExtra("app_id", freshApp.id);
|
||||||
ns.postBankProfile();
|
ns.postBankProfile();
|
||||||
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
|
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||||
if (afterPost.bankProfileId.isEmpty()) {
|
if (afterPost.bankProfileId.isEmpty()) {
|
||||||
const QString err = "postBankProfile failed for " + m_deviceId;
|
const QString err = "postBankProfile failed for " + m_deviceId;
|
||||||
qWarning() << "[GetProfileInfo]" << err;
|
qWarning() << "[GetProfileInfo]" << err;
|
||||||
@ -159,8 +162,8 @@ void GetProfileInfoScript::doStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем данные аккаунтов на сервер если нет material_id
|
// Отправляем данные аккаунтов на сервер если нет material_id
|
||||||
const QList<AccountInfo> allAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||||||
QList<QPair<AccountInfo, Node>> accountsToPost;
|
QList<QPair<MaterialInfo, Node>> accountsToPost;
|
||||||
for (const auto &acc : allAccounts) {
|
for (const auto &acc : allAccounts) {
|
||||||
if (acc.materialId.isEmpty()) {
|
if (acc.materialId.isEmpty()) {
|
||||||
accountsToPost.append({acc, Node()});
|
accountsToPost.append({acc, Node()});
|
||||||
|
|||||||
@ -19,7 +19,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "AppLogger.h"
|
#include "AppLogger.h"
|
||||||
#include "GetCardInfoScript.h"
|
#include "GetCardInfoScript.h"
|
||||||
@ -14,18 +14,21 @@ namespace Ozon {
|
|||||||
LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
||||||
QString deviceId,
|
QString deviceId,
|
||||||
QString appCode,
|
QString appCode,
|
||||||
QObject *parent
|
QObject *parent,
|
||||||
|
bool fetchProfileOnly
|
||||||
) : CommonScript(parent),
|
) : CommonScript(parent),
|
||||||
m_deviceId(std::move(deviceId)),
|
m_deviceId(std::move(deviceId)),
|
||||||
m_appCode(std::move(appCode)) {
|
m_appCode(std::move(appCode)),
|
||||||
|
m_fetchProfileOnly(fetchProfileOnly) {
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
||||||
|
|
||||||
|
|
||||||
void LoginAndCheckAccountsScript::doStart() {
|
void LoginAndCheckAccountsScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
|
// m_deviceId = ADB serial, device.id = android_id (для БД)
|
||||||
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
|
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));
|
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;
|
m_error = "Cant open the home screen: " + m_deviceId + " " + m_appCode;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
AppLogger::log("ozon/LoginAndCheck", m_deviceId, m_error,
|
AppLogger::log("ozon/LoginAndCheck", m_deviceId, m_error,
|
||||||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||||||
"FETCH_PROFILE");
|
"FETCH_PROFILE");
|
||||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "no", "Не прошла проверка [дата]")) {
|
BankProfileDAO::updateComment(app.id,
|
||||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
"Не прошла проверка " + QDateTime::currentDateTime().toString("dd.MM.yyyy HH:mm:ss"));
|
||||||
}
|
|
||||||
|
|
||||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) {
|
|
||||||
qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаём bank profile если ещё не создан
|
// Создаём bank profile если ещё не создан
|
||||||
createBankProfileIfNeeded(m_deviceId, m_appCode);
|
createBankProfileIfNeeded(device.id, m_appCode);
|
||||||
|
|
||||||
if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) {
|
if (goToAllProducts(m_deviceId, device.screenWidth, device.screenHeight)) {
|
||||||
QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
QList<QPair<MaterialInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||||
for (auto &[account, node]: accounts) {
|
for (auto &[account, node]: accounts) {
|
||||||
account.deviceId = m_deviceId;
|
account.deviceId = device.id;
|
||||||
account.appCode = m_appCode;
|
account.appCode = m_appCode;
|
||||||
if (!AccountDAO::upsertAccount(account)) {
|
if (!MaterialDAO::upsertAccount(account)) {
|
||||||
m_error = "Not updated: " + account.lastNumbers;
|
m_error = "Not updated: " + account.lastNumbers;
|
||||||
qCritical() << m_error;
|
qCritical() << m_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postAccountsData(accounts);
|
if (!m_fetchProfileOnly) {
|
||||||
|
postAccountsData(accounts);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Cant go to all products: " << m_deviceId << " " << m_appCode;
|
qWarning() << "Cant go to all products: " << m_deviceId << " " << m_appCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запускаем сканирование баланса, данных карт и отправку на сервер (синхронно)
|
if (!m_fetchProfileOnly) {
|
||||||
GetCardInfoScript cardScript(m_deviceId, m_appCode, "");
|
// Запускаем сканирование баланса, данных карт и отправку на сервер (синхронно)
|
||||||
cardScript.start();
|
GetCardInfoScript cardScript(m_deviceId, m_appCode, "");
|
||||||
|
cardScript.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
|
|||||||
@ -12,15 +12,17 @@ public:
|
|||||||
explicit LoginAndCheckAccountsScript(
|
explicit LoginAndCheckAccountsScript(
|
||||||
QString deviceId,
|
QString deviceId,
|
||||||
QString appCode,
|
QString appCode,
|
||||||
QObject *parent = nullptr
|
QObject *parent = nullptr,
|
||||||
|
bool fetchProfileOnly = false
|
||||||
);
|
);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString m_deviceId;
|
QString m_deviceId;
|
||||||
const QString m_appCode;
|
const QString m_appCode;
|
||||||
|
const bool m_fetchProfileOnly;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
#include <leptonica/allheaders.h>
|
#include <leptonica/allheaders.h>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "time/DateUtils.h"
|
#include "time/DateUtils.h"
|
||||||
@ -59,7 +59,7 @@ static QString extractTransactionId(const QString &ocrText) {
|
|||||||
namespace Ozon {
|
namespace Ozon {
|
||||||
|
|
||||||
PayByCardScript::PayByCardScript(
|
PayByCardScript::PayByCardScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString cardNumber,
|
QString cardNumber,
|
||||||
const double amount,
|
const double amount,
|
||||||
QObject *parent
|
QObject *parent
|
||||||
@ -72,7 +72,7 @@ PayByCardScript::~PayByCardScript() = default;
|
|||||||
|
|
||||||
void PayByCardScript::doStart() {
|
void PayByCardScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_account.deviceId;
|
m_error = "Device or app not found: " + m_account.deviceId;
|
||||||
@ -94,11 +94,11 @@ void PayByCardScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем
|
// Если не на домашнем экране — перезапускаем
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[PayByCard] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + device.id;
|
m_error = "Cannot reach home screen: " + device.id;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
AdbUtils::tryToKillApplication(device.adbSerial, app.package);
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ void PayByCardScript::doStart() {
|
|||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
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);
|
emit finishedWithResult(m_error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ public:
|
|||||||
~PayByCardScript() override;
|
~PayByCardScript() override;
|
||||||
|
|
||||||
PayByCardScript(
|
PayByCardScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString cardNumber,
|
QString cardNumber,
|
||||||
double amount,
|
double amount,
|
||||||
QObject *parent = nullptr
|
QObject *parent = nullptr
|
||||||
@ -20,7 +20,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const AccountInfo m_account;
|
const MaterialInfo m_account;
|
||||||
const QString m_cardNumber;
|
const QString m_cardNumber;
|
||||||
const double m_amount;
|
const double m_amount;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
#include <leptonica/allheaders.h>
|
#include <leptonica/allheaders.h>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "time/DateUtils.h"
|
#include "time/DateUtils.h"
|
||||||
@ -59,7 +59,7 @@ static QString extractTransactionId(const QString &ocrText) {
|
|||||||
namespace Ozon {
|
namespace Ozon {
|
||||||
|
|
||||||
PayByPhoneScript::PayByPhoneScript(
|
PayByPhoneScript::PayByPhoneScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString phone,
|
QString phone,
|
||||||
QString bankName,
|
QString bankName,
|
||||||
const double amount,
|
const double amount,
|
||||||
@ -73,7 +73,7 @@ PayByPhoneScript::~PayByPhoneScript() = default;
|
|||||||
|
|
||||||
void PayByPhoneScript::doStart() {
|
void PayByPhoneScript::doStart() {
|
||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
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) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found: " + m_account.deviceId;
|
m_error = "Device or app not found: " + m_account.deviceId;
|
||||||
@ -95,11 +95,11 @@ void PayByPhoneScript::doStart() {
|
|||||||
// Если не на домашнем экране — перезапускаем
|
// Если не на домашнем экране — перезапускаем
|
||||||
if (!xmlScreenParser.isHomeScreen()) {
|
if (!xmlScreenParser.isHomeScreen()) {
|
||||||
qDebug() << "[PayByPhone] Not on home screen, restarting app...";
|
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)) {
|
device.screenWidth, device.screenHeight)) {
|
||||||
m_error = "Cannot reach home screen: " + device.id;
|
m_error = "Cannot reach home screen: " + device.id;
|
||||||
qWarning() << m_error;
|
qWarning() << m_error;
|
||||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
AdbUtils::tryToKillApplication(device.adbSerial, app.package);
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ void PayByPhoneScript::doStart() {
|
|||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
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);
|
emit finishedWithResult(m_error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ public:
|
|||||||
~PayByPhoneScript() override;
|
~PayByPhoneScript() override;
|
||||||
|
|
||||||
PayByPhoneScript(
|
PayByPhoneScript(
|
||||||
AccountInfo account,
|
MaterialInfo account,
|
||||||
QString phone,
|
QString phone,
|
||||||
QString bankName,
|
QString bankName,
|
||||||
double amount,
|
double amount,
|
||||||
@ -21,7 +21,7 @@ protected:
|
|||||||
void doStart() override;
|
void doStart() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const AccountInfo m_account;
|
const MaterialInfo m_account;
|
||||||
const QString m_phone;
|
const QString m_phone;
|
||||||
const QString m_bankName;
|
const QString m_bankName;
|
||||||
const double m_amount;
|
const double m_amount;
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "android/xml/Node.h"
|
#include "android/xml/Node.h"
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "db/TransactionInfo.h"
|
#include "db/TransactionInfo.h"
|
||||||
#include "time/DateUtils.h"
|
#include "time/DateUtils.h"
|
||||||
|
|
||||||
@ -358,7 +358,7 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<AccountInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
QList<MaterialInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
||||||
// "Основной счёт X ₽" — общая кнопка с балансом для всех карт.
|
// "Основной счёт X ₽" — общая кнопка с балансом для всех карт.
|
||||||
// Карты — отдельные кнопки с текстом из 4 цифр (могут быть любые non-breaking spaces).
|
// Карты — отдельные кнопки с текстом из 4 цифр (могут быть любые non-breaking spaces).
|
||||||
// Сумма из "Основной счёт" проставляется на все карты.
|
// Сумма из "Основной счёт" проставляется на все карты.
|
||||||
@ -388,13 +388,13 @@ QList<AccountInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
|||||||
qWarning() << "[parseCards] Balance button not found or amount=0";
|
qWarning() << "[parseCards] Balance button not found or amount=0";
|
||||||
|
|
||||||
// 2. Собираем все кнопки карт (текст заканчивается на 4 цифры)
|
// 2. Собираем все кнопки карт (текст заканчивается на 4 цифры)
|
||||||
QList<AccountInfo> cards;
|
QList<MaterialInfo> cards;
|
||||||
for (const Node &node : m_nodes) {
|
for (const Node &node : m_nodes) {
|
||||||
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
||||||
QString t = node.text.trimmed();
|
QString t = node.text.trimmed();
|
||||||
if (!endsWithFourDigits.match(t).hasMatch()) continue;
|
if (!endsWithFourDigits.match(t).hasMatch()) continue;
|
||||||
|
|
||||||
AccountInfo acc;
|
MaterialInfo acc;
|
||||||
acc.lastNumbers = t.right(4);
|
acc.lastNumbers = t.right(4);
|
||||||
acc.amount = commonAmount;
|
acc.amount = commonAmount;
|
||||||
acc.currency = "RUB";
|
acc.currency = "RUB";
|
||||||
@ -1329,8 +1329,8 @@ TransactionInfo ScreenXmlParser::parseTransactionInfo(int screenWidth, int scree
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
QList<QPair<MaterialInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
||||||
QList<QPair<AccountInfo, Node> > accounts;
|
QList<QPair<MaterialInfo, Node> > accounts;
|
||||||
for (const Node &node: m_nodes) {
|
for (const Node &node: m_nodes) {
|
||||||
const QString text = node.text;
|
const QString text = node.text;
|
||||||
|
|
||||||
@ -1343,7 +1343,7 @@ QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountInfo info;
|
MaterialInfo info;
|
||||||
info.lastNumbers = match.captured(1).remove(" ");
|
info.lastNumbers = match.captured(1).remove(" ");
|
||||||
info.amount = match.captured(2).remove(" ").toFloat();
|
info.amount = match.captured(2).remove(" ").toFloat();
|
||||||
info.description = text.mid(0, text.indexOf("Номер карты"));
|
info.description = text.mid(0, text.indexOf("Номер карты"));
|
||||||
@ -1495,7 +1495,7 @@ void ScreenXmlParser::test() {
|
|||||||
// Номер карты с последними цифрами
|
// Номер карты с последними цифрами
|
||||||
QDomElement root = doc.documentElement();
|
QDomElement root = doc.documentElement();
|
||||||
parseAndSaveXml(doc.toString());
|
parseAndSaveXml(doc.toString());
|
||||||
// for (const AccountInfo &info: parseAccountsInfo()) {
|
// for (const MaterialInfo &info: parseAccountsInfo()) {
|
||||||
// qDebug().noquote().nospace() << convertAccountToString(info);
|
// qDebug().noquote().nospace() << convertAccountToString(info);
|
||||||
// }
|
// }
|
||||||
// TransactionInfo info = parseExpandedInfo(root);
|
// TransactionInfo info = parseExpandedInfo(root);
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include "android/xml/Node.h"
|
#include "android/xml/Node.h"
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "db/TransactionInfo.h"
|
#include "db/TransactionInfo.h"
|
||||||
|
|
||||||
namespace Ozon {
|
namespace Ozon {
|
||||||
@ -50,9 +50,9 @@ public:
|
|||||||
// Finds a card button whose text ends with the given last 4 digits
|
// Finds a card button whose text ends with the given last 4 digits
|
||||||
Node findCardButtonByLastNumbers(const QString &lastNumbers);
|
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)
|
// (lastNumbers, description, amount filled; id=-1, cardNumber empty)
|
||||||
QList<AccountInfo> parseCardsOnHomeScreen();
|
QList<MaterialInfo> parseCardsOnHomeScreen();
|
||||||
|
|
||||||
bool isProfileScreen();
|
bool isProfileScreen();
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ public:
|
|||||||
QList<TransactionInfo> parseTodayAndYesterdayHistory();
|
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")
|
// Finds a TextView with full card number (format "XXXX XXXX XXXX XXXX")
|
||||||
QString findFullCardNumber();
|
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() {
|
void DatabaseManager::initSchema() {
|
||||||
QSqlDatabase db = database();
|
QSqlDatabase db = database();
|
||||||
QSqlQuery q(db);
|
QSqlQuery q(db);
|
||||||
|
|
||||||
// Читаем текущую версию схемы
|
db.transaction();
|
||||||
q.exec("PRAGMA user_version");
|
|
||||||
const int version = q.next() ? q.value(0).toInt() : 0;
|
|
||||||
|
|
||||||
// ── Версия 1: полная схема ─────────────────────────────────────────────────
|
q.exec(R"(CREATE TABLE IF NOT EXISTS devices (
|
||||||
if (version < 1) {
|
id TEXT PRIMARY KEY,
|
||||||
db.transaction();
|
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 (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS bank_profiles (
|
||||||
id TEXT PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
api_id TEXT,
|
device_id TEXT,
|
||||||
status TEXT,
|
pin_code TEXT,
|
||||||
name TEXT,
|
pin_code_checked TEXT DEFAULT 'no',
|
||||||
android TEXT,
|
name TEXT,
|
||||||
screen_width INTEGER,
|
code TEXT,
|
||||||
screen_height INTEGER,
|
package TEXT,
|
||||||
battery INTEGER,
|
status TEXT DEFAULT 'off',
|
||||||
update_time DATETIME,
|
install TEXT DEFAULT 'no',
|
||||||
timestamp DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
comment TEXT,
|
||||||
image BLOB
|
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 (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS materials (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
pin_code TEXT,
|
app_code TEXT,
|
||||||
pin_code_checked TEXT DEFAULT 'no',
|
last_numbers TEXT,
|
||||||
name TEXT,
|
description TEXT,
|
||||||
code TEXT,
|
amount REAL,
|
||||||
package TEXT,
|
currency TEXT DEFAULT '',
|
||||||
status TEXT DEFAULT 'off',
|
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||||
install TEXT DEFAULT 'no',
|
status TEXT DEFAULT 'active',
|
||||||
comment TEXT,
|
card_number TEXT DEFAULT '',
|
||||||
phone TEXT DEFAULT '',
|
material_id TEXT DEFAULT '',
|
||||||
email TEXT DEFAULT '',
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
full_name TEXT DEFAULT '',
|
UNIQUE (device_id, app_code, last_numbers)
|
||||||
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 accounts (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
account_id INTEGER,
|
||||||
app_code TEXT,
|
external_id INTEGER,
|
||||||
last_numbers TEXT,
|
amount REAL,
|
||||||
description TEXT,
|
fee REAL,
|
||||||
amount REAL,
|
status TEXT,
|
||||||
currency TEXT DEFAULT '',
|
type TEXT,
|
||||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
phone TEXT,
|
||||||
status TEXT DEFAULT 'active',
|
name TEXT,
|
||||||
card_number TEXT DEFAULT '',
|
short_description TEXT,
|
||||||
material_id TEXT DEFAULT '',
|
updated_short_description TEXT,
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
description TEXT,
|
||||||
UNIQUE (device_id, app_code, last_numbers)
|
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 (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
account_id INTEGER,
|
account_id INTEGER,
|
||||||
external_id INTEGER,
|
external_id TEXT,
|
||||||
amount REAL,
|
device_id TEXT,
|
||||||
fee REAL,
|
amount REAL,
|
||||||
status TEXT,
|
phone TEXT,
|
||||||
type TEXT,
|
bank_name TEXT,
|
||||||
phone TEXT,
|
comment TEXT,
|
||||||
name TEXT,
|
status TEXT,
|
||||||
short_description TEXT,
|
type TEXT,
|
||||||
updated_short_description TEXT,
|
update_time DATETIME,
|
||||||
description TEXT,
|
complete_time DATETIME,
|
||||||
updated_description TEXT,
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
bank_name TEXT,
|
json TEXT DEFAULT '',
|
||||||
bank_time TEXT,
|
sent_to_server INTEGER DEFAULT 0,
|
||||||
bank_tr_external_id TEXT,
|
screenshot BLOB,
|
||||||
update_time DATETIME,
|
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE,
|
||||||
complete_time DATETIME,
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||||
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 (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
account_id INTEGER,
|
alias TEXT UNIQUE,
|
||||||
external_id TEXT,
|
name TEXT,
|
||||||
device_id TEXT,
|
nspk_name 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 (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
alias TEXT UNIQUE,
|
source TEXT NOT NULL DEFAULT '',
|
||||||
name TEXT,
|
device_id TEXT NOT NULL DEFAULT '',
|
||||||
nspk_name TEXT
|
message TEXT NOT NULL,
|
||||||
))");
|
screenshot BLOB,
|
||||||
|
timestamp TEXT NOT NULL
|
||||||
|
))");
|
||||||
|
|
||||||
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
|
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
|
||||||
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(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 (
|
q.exec("CREATE INDEX IF NOT EXISTS idx_general_logs_id ON general_logs (id DESC)");
|
||||||
key TEXT PRIMARY KEY,
|
|
||||||
value TEXT
|
|
||||||
))");
|
|
||||||
|
|
||||||
db.commit();
|
q.exec(R"(CREATE TABLE IF NOT EXISTS settings (
|
||||||
q.exec("PRAGMA user_version = 1");
|
key TEXT PRIMARY KEY,
|
||||||
qDebug() << "[DB] Migration 1 applied";
|
value TEXT
|
||||||
}
|
))");
|
||||||
|
|
||||||
// ── Версия 2: добавляем email в applications ─────────────────────────────
|
db.commit();
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseManager::closeConnection() {
|
void DatabaseManager::closeConnection() {
|
||||||
|
|||||||
@ -68,3 +68,30 @@ QByteArray AppLogDAO::getScreenshot(int id) {
|
|||||||
if (!q.exec() || !q.next()) return {};
|
if (!q.exec() || !q.next()) return {};
|
||||||
return q.value(0).toByteArray();
|
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)
|
// Загружает скриншот отдельно по id (по клику в UI)
|
||||||
[[nodiscard]] static QByteArray getScreenshot(int id);
|
[[nodiscard]] static QByteArray getScreenshot(int id);
|
||||||
|
|
||||||
|
// Все записи со скриншотами (для экспорта)
|
||||||
|
[[nodiscard]] static QList<AppLogEntry> getLogsWithScreenshots(int limit = 100);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include "ApplicationDAO.h"
|
#include "BankProfileDAO.h"
|
||||||
#include "DatabaseManager.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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
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)
|
VALUES (:id, :device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked)
|
||||||
ON CONFLICT(device_id, code)
|
ON CONFLICT(device_id, code)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
@ -44,11 +44,11 @@ bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
|||||||
return true;
|
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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE applications
|
UPDATE bank_profiles
|
||||||
SET pin_code = :pin_code
|
SET pin_code = :pin_code
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
)");
|
)");
|
||||||
@ -63,7 +63,7 @@ bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApplicationDAO::update(
|
bool BankProfileDAO::update(
|
||||||
const int &appId,
|
const int &appId,
|
||||||
const QString &pinCode,
|
const QString &pinCode,
|
||||||
const QString &comment,
|
const QString &comment,
|
||||||
@ -72,7 +72,7 @@ bool ApplicationDAO::update(
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE applications
|
UPDATE bank_profiles
|
||||||
SET pin_code = :pin_code, comment = :comment, status = :status
|
SET pin_code = :pin_code, comment = :comment, status = :status
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
)");
|
)");
|
||||||
@ -89,7 +89,7 @@ bool ApplicationDAO::update(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApplicationDAO::updatePinCodeStatus(
|
bool BankProfileDAO::updatePinCodeStatus(
|
||||||
const int &appId,
|
const int &appId,
|
||||||
const QString &pinCodeStatus,
|
const QString &pinCodeStatus,
|
||||||
const QString &comment
|
const QString &comment
|
||||||
@ -97,15 +97,14 @@ bool ApplicationDAO::updatePinCodeStatus(
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE applications
|
UPDATE bank_profiles
|
||||||
SET pin_code_checked = :pin_code_checked, comment = :comment, status = :status
|
SET pin_code_checked = :pin_code_checked, comment = :comment
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
)");
|
)");
|
||||||
|
|
||||||
query.bindValue(":id", appId);
|
query.bindValue(":id", appId);
|
||||||
query.bindValue(":comment", comment);
|
query.bindValue(":comment", comment);
|
||||||
query.bindValue(":pin_code_checked", pinCodeStatus);
|
query.bindValue(":pin_code_checked", pinCodeStatus);
|
||||||
query.bindValue(":status", pinCodeStatus == "yes" ? "active" : "off");
|
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||||
@ -115,8 +114,20 @@ bool ApplicationDAO::updatePinCodeStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ApplicationInfo parseApplication(const QSqlQuery &query) {
|
bool BankProfileDAO::updateComment(const int appId, const QString &comment) {
|
||||||
ApplicationInfo app;
|
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.id = query.value("id").toInt();
|
||||||
app.deviceId = query.value("device_id").toString();
|
app.deviceId = query.value("device_id").toString();
|
||||||
app.pinCode = query.value("pin_code").toString();
|
app.pinCode = query.value("pin_code").toString();
|
||||||
@ -136,12 +147,12 @@ ApplicationInfo parseApplication(const QSqlQuery &query) {
|
|||||||
return app;
|
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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
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
|
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
|
WHERE device_id = :device_id AND code = :code
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
@ -160,13 +171,13 @@ ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QS
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &deviceId) {
|
QList<BankProfileInfo> BankProfileDAO::getApplicationsByDeviceId(const QString &deviceId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
QList<ApplicationInfo> list;
|
QList<BankProfileInfo> list;
|
||||||
|
|
||||||
query.prepare(R"(
|
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
|
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'
|
WHERE device_id = :device_id AND install = 'yes'
|
||||||
)");
|
)");
|
||||||
query.bindValue(":device_id", deviceId);
|
query.bindValue(":device_id", deviceId);
|
||||||
@ -183,7 +194,30 @@ QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &
|
|||||||
return list;
|
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 int &appId,
|
||||||
const QString &fullName,
|
const QString &fullName,
|
||||||
const QString &phone,
|
const QString &phone,
|
||||||
@ -192,7 +226,7 @@ bool ApplicationDAO::updateProfileInfo(
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE applications
|
UPDATE bank_profiles
|
||||||
SET full_name = :full_name, phone = :phone, email = :email
|
SET full_name = :full_name, phone = :phone, email = :email
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
)");
|
)");
|
||||||
@ -209,9 +243,9 @@ bool ApplicationDAO::updateProfileInfo(
|
|||||||
return true;
|
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());
|
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(":amount", amount);
|
||||||
query.bindValue(":id", appId);
|
query.bindValue(":id", appId);
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
@ -221,9 +255,9 @@ bool ApplicationDAO::updateAmount(const int appId, const double amount) {
|
|||||||
return true;
|
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());
|
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(":bank_profile_id", bankProfileId);
|
||||||
query.bindValue(":id", appId);
|
query.bindValue(":id", appId);
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
@ -233,17 +267,17 @@ bool ApplicationDAO::updateBankProfileId(const int appId, const QString &bankPro
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ApplicationInfo> ApplicationDAO::findAllByBankProfileId(const QString &bankProfileId) {
|
QList<BankProfileInfo> BankProfileDAO::findAllByBankProfileId(const QString &bankProfileId) {
|
||||||
QList<ApplicationInfo> list;
|
QList<BankProfileInfo> list;
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
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
|
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
|
WHERE bank_profile_id = :bank_profile_id
|
||||||
)");
|
)");
|
||||||
query.bindValue(":bank_profile_id", bankProfileId);
|
query.bindValue(":bank_profile_id", bankProfileId);
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при поиске applications по bank_profile_id:" << query.lastError().text();
|
qWarning() << "Ошибка при поиске bank_profiles по bank_profile_id:" << query.lastError().text();
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
while (query.next()) {
|
while (query.next()) {
|
||||||
@ -252,10 +286,10 @@ QList<ApplicationInfo> ApplicationDAO::findAllByBankProfileId(const QString &ban
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApplicationDAO::activateAppsForDevice(const QString &deviceId) {
|
bool BankProfileDAO::activateAppsForDevice(const QString &deviceId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
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'
|
WHERE device_id = :device_id AND install = 'yes' AND pin_code_checked = 'yes'
|
||||||
)");
|
)");
|
||||||
query.bindValue(":device_id", deviceId);
|
query.bindValue(":device_id", deviceId);
|
||||||
@ -266,10 +300,10 @@ bool ApplicationDAO::activateAppsForDevice(const QString &deviceId) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApplicationDAO::deactivateAppsForDevice(const QString &deviceId) {
|
bool BankProfileDAO::deactivateAppsForDevice(const QString &deviceId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE applications SET status = 'off'
|
UPDATE bank_profiles SET status = 'off'
|
||||||
WHERE device_id = :device_id AND install = 'yes'
|
WHERE device_id = :device_id AND install = 'yes'
|
||||||
)");
|
)");
|
||||||
query.bindValue(":device_id", deviceId);
|
query.bindValue(":device_id", deviceId);
|
||||||
@ -280,7 +314,7 @@ bool ApplicationDAO::deactivateAppsForDevice(const QString &deviceId) {
|
|||||||
return true;
|
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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||||
QStringList banks = settings.value("apps/list", QStringList()).toStringList();
|
QStringList banks = settings.value("apps/list", QStringList()).toStringList();
|
||||||
@ -290,7 +324,7 @@ bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QStr
|
|||||||
QStringList placeholders;
|
QStringList placeholders;
|
||||||
for (int i = 0; i < banks.size(); ++i) placeholders << "?";
|
for (int i = 0; i < banks.size(); ++i) placeholders << "?";
|
||||||
query.prepare(QString(R"(
|
query.prepare(QString(R"(
|
||||||
UPDATE applications SET install = 'no'
|
UPDATE bank_profiles SET install = 'no'
|
||||||
WHERE device_id = ? AND code NOT IN (%1)
|
WHERE device_id = ? AND code NOT IN (%1)
|
||||||
)").arg(placeholders.join(",")));
|
)").arg(placeholders.join(",")));
|
||||||
query.addBindValue(deviceId);
|
query.addBindValue(deviceId);
|
||||||
@ -307,7 +341,7 @@ bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QStr
|
|||||||
const QString currency = settings.value(bank + "/currency").toString();
|
const QString currency = settings.value(bank + "/currency").toString();
|
||||||
|
|
||||||
query.prepare(R"(
|
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)
|
VALUES (:device_id, :name, :code, :package, :status, :install, :pin_code_checked, :currency)
|
||||||
ON CONFLICT(device_id, code)
|
ON CONFLICT(device_id, code)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
class ApplicationDAO {
|
class BankProfileDAO {
|
||||||
public:
|
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);
|
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ public:
|
|||||||
const QString &comment
|
const QString &comment
|
||||||
);
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] static bool updateComment(int appId, const QString &comment);
|
||||||
|
|
||||||
[[nodiscard]] static bool updateProfileInfo(
|
[[nodiscard]] static bool updateProfileInfo(
|
||||||
const int &appId,
|
const int &appId,
|
||||||
const QString &fullName,
|
const QString &fullName,
|
||||||
@ -30,13 +32,15 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] static bool updateBankProfileId(int appId, const QString &bankProfileId);
|
[[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 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);
|
[[nodiscard]] static bool checkInstalledApps(const QString &deviceId, const QSet<QString> &apps);
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ DeviceInfo parseDevice(const QSqlQuery &query) {
|
|||||||
DeviceInfo device;
|
DeviceInfo device;
|
||||||
device.id = query.value("id").toString();
|
device.id = query.value("id").toString();
|
||||||
device.apiId = query.value("api_id").toString();
|
device.apiId = query.value("api_id").toString();
|
||||||
|
device.adbSerial = query.value("adb_serial").toString();
|
||||||
device.status = query.value("status").toString();
|
device.status = query.value("status").toString();
|
||||||
device.name = query.value("name").toString();
|
device.name = query.value("name").toString();
|
||||||
device.android = query.value("android").toString();
|
device.android = query.value("android").toString();
|
||||||
@ -25,7 +26,7 @@ QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
|||||||
|
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
QString sql = R"(
|
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
|
FROM devices
|
||||||
)";
|
)";
|
||||||
if (online) {
|
if (online) {
|
||||||
@ -71,7 +72,7 @@ DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
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
|
FROM devices
|
||||||
WHERE id = :deviceId
|
WHERE id = :deviceId
|
||||||
)");
|
)");
|
||||||
@ -89,6 +90,69 @@ DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
|||||||
return {};
|
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) {
|
bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
@ -107,6 +171,8 @@ bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
|||||||
// Обновление
|
// Обновление
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE devices SET
|
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,
|
status = :status,
|
||||||
name = :name,
|
name = :name,
|
||||||
android = :android,
|
android = :android,
|
||||||
@ -120,14 +186,16 @@ bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
|||||||
// Вставка
|
// Вставка
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
INSERT INTO devices (
|
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 (
|
) 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(":id", device.id);
|
||||||
|
query.bindValue(":api_id", device.apiId);
|
||||||
|
query.bindValue(":adb_serial", device.adbSerial);
|
||||||
query.bindValue(":status", device.status);
|
query.bindValue(":status", device.status);
|
||||||
query.bindValue(":name", device.name);
|
query.bindValue(":name", device.name);
|
||||||
query.bindValue(":android", device.android);
|
query.bindValue(":android", device.android);
|
||||||
|
|||||||
@ -6,6 +6,12 @@ class DeviceDAO {
|
|||||||
public:
|
public:
|
||||||
[[nodiscard]] static DeviceInfo getDeviceById(const QString &deviceId);
|
[[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 upsertDevice(const DeviceInfo &device);
|
||||||
|
|
||||||
[[nodiscard]] static bool updateApiId(const QString &deviceId, const QString &apiId);
|
[[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
|
status, type, comment, json, sent_to_server, update_time, complete_time, timestamp
|
||||||
FROM events
|
FROM events
|
||||||
WHERE status = :status AND device_id = :device_id
|
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
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
query.bindValue(":status", eventStatusToString(EventStatus::Wait));
|
query.bindValue(":status", eventStatusToString(EventStatus::Wait));
|
||||||
@ -258,3 +265,78 @@ bool EventDAO::existsActiveByExternalId(const QString &externalId) {
|
|||||||
}
|
}
|
||||||
return query.next();
|
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 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 updateEventAmount(int id, double amount);
|
||||||
|
|
||||||
[[nodiscard]] static bool markSentToServer(int id);
|
[[nodiscard]] static bool markSentToServer(int id);
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include "AccountDAO.h"
|
#include "MaterialDAO.h"
|
||||||
|
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "DatabaseManager.h"
|
#include "DatabaseManager.h"
|
||||||
#include "time/DateUtils.h"
|
#include "time/DateUtils.h"
|
||||||
|
|
||||||
bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
bool MaterialDAO::upsertAccount(const MaterialInfo &info) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
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)
|
VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency)
|
||||||
ON CONFLICT(device_id, app_code, last_numbers)
|
ON CONFLICT(device_id, app_code, last_numbers)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
@ -31,7 +31,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
|||||||
query.bindValue(":description", info.description);
|
query.bindValue(":description", info.description);
|
||||||
query.bindValue(":amount", info.amount);
|
query.bindValue(":amount", info.amount);
|
||||||
query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime));
|
query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime));
|
||||||
query.bindValue(":status", accountStatusToString(info.status));
|
query.bindValue(":status", materialStatusToString(info.status));
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при вставке/обновлении account:" << query.lastError().text();
|
qWarning() << "Ошибка при вставке/обновлении account:" << query.lastError().text();
|
||||||
@ -41,7 +41,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AccountDAO::updateAccountById(
|
bool MaterialDAO::updateAccountById(
|
||||||
const int id,
|
const int id,
|
||||||
const QString &status,
|
const QString &status,
|
||||||
const QString &description,
|
const QString &description,
|
||||||
@ -50,7 +50,7 @@ bool AccountDAO::updateAccountById(
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
UPDATE accounts
|
UPDATE materials
|
||||||
SET status = :status,
|
SET status = :status,
|
||||||
description = :description,
|
description = :description,
|
||||||
last_numbers = :last_numbers,
|
last_numbers = :last_numbers,
|
||||||
@ -72,17 +72,17 @@ bool AccountDAO::updateAccountById(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AccountDAO::deleteAccount(const int id) {
|
bool MaterialDAO::deleteAccount(const int id) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
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);
|
query.bindValue(":id", id);
|
||||||
return query.exec();
|
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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
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(":card_number", cardNumber);
|
||||||
query.bindValue(":id", accountId);
|
query.bindValue(":id", accountId);
|
||||||
@ -93,9 +93,9 @@ bool AccountDAO::updateCardNumber(const int accountId, const QString &cardNumber
|
|||||||
return true;
|
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());
|
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(":material_id", materialId);
|
||||||
query.bindValue(":id", accountId);
|
query.bindValue(":id", accountId);
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
@ -105,8 +105,8 @@ bool AccountDAO::updateMaterialId(const int accountId, const QString &materialId
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountInfo parseAccount(const QSqlQuery &query) {
|
MaterialInfo parseAccount(const QSqlQuery &query) {
|
||||||
AccountInfo acc;
|
MaterialInfo acc;
|
||||||
acc.id = query.value("id").toInt();
|
acc.id = query.value("id").toInt();
|
||||||
acc.deviceId = query.value("device_id").toString();
|
acc.deviceId = query.value("device_id").toString();
|
||||||
acc.appCode = query.value("app_code").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.materialId = query.value("material_id").toString();
|
||||||
acc.amount = query.value("amount").toDouble();
|
acc.amount = query.value("amount").toDouble();
|
||||||
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
|
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.description = query.value("description").toString();
|
||||||
acc.currency = query.value("currency").toString();
|
acc.currency = query.value("currency").toString();
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QString &appName) {
|
QList<MaterialInfo> MaterialDAO::getAccounts(const QString &deviceId, const QString &appName) {
|
||||||
QList<AccountInfo> accounts;
|
QList<MaterialInfo> accounts;
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
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
|
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);
|
query.bindValue(":app_code", appName);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при получении accounts:" << query.lastError().text();
|
qWarning() << "Ошибка при получении materials:" << query.lastError().text();
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,12 +146,12 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
|||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
MaterialInfo MaterialDAO::getAccountById(const int accountId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
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
|
WHERE id = :id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
@ -169,20 +169,20 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<AccountInfo> AccountDAO::getAccountsByAppCode(const QString &appCode) {
|
QList<MaterialInfo> MaterialDAO::getAccountsByAppCode(const QString &appCode) {
|
||||||
QList<AccountInfo> accounts;
|
QList<MaterialInfo> accounts;
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
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
|
WHERE app_code = :app_code
|
||||||
)");
|
)");
|
||||||
|
|
||||||
query.bindValue(":app_code", appCode);
|
query.bindValue(":app_code", appCode);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при получении accounts по app_code:" << query.lastError().text();
|
qWarning() << "Ошибка при получении materials по app_code:" << query.lastError().text();
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,12 +193,12 @@ QList<AccountInfo> AccountDAO::getAccountsByAppCode(const QString &appCode) {
|
|||||||
return accounts;
|
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());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
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
|
WHERE app_code = :appName AND last_numbers = :lastNumber AND device_id = :deviceId
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
@ -219,13 +219,13 @@ AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &a
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList AccountDAO::getActiveMaterialIds() {
|
QStringList MaterialDAO::getActiveMaterialIds() {
|
||||||
QStringList ids;
|
QStringList ids;
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT a.material_id
|
SELECT a.material_id
|
||||||
FROM accounts a
|
FROM materials a
|
||||||
JOIN applications app ON a.device_id = app.device_id AND a.app_code = app.code
|
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
|
JOIN devices d ON a.device_id = d.id
|
||||||
WHERE app.status = 'active'
|
WHERE app.status = 'active'
|
||||||
AND a.material_id IS NOT NULL AND a.material_id != ''
|
AND a.material_id IS NOT NULL AND a.material_id != ''
|
||||||
@ -243,13 +243,13 @@ QStringList AccountDAO::getActiveMaterialIds() {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList AccountDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString> &excludeDevices) {
|
QStringList MaterialDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString> &excludeDevices) {
|
||||||
const QStringList all = getActiveMaterialIds();
|
const QStringList all = getActiveMaterialIds();
|
||||||
if (excludeDevices.isEmpty()) return all;
|
if (excludeDevices.isEmpty()) return all;
|
||||||
|
|
||||||
QStringList filtered;
|
QStringList filtered;
|
||||||
for (const QString &mid : all) {
|
for (const QString &mid : all) {
|
||||||
const AccountInfo acc = findByMaterialId(mid);
|
const MaterialInfo acc = findByMaterialId(mid);
|
||||||
if (!excludeDevices.contains(acc.deviceId)) {
|
if (!excludeDevices.contains(acc.deviceId)) {
|
||||||
filtered << mid;
|
filtered << mid;
|
||||||
}
|
}
|
||||||
@ -257,12 +257,12 @@ QStringList AccountDAO::getActiveMaterialIdsExcludingDevices(const QSet<QString>
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountInfo AccountDAO::findByMaterialId(const QString &materialId) {
|
MaterialInfo MaterialDAO::findByMaterialId(const QString &materialId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
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
|
WHERE material_id = :material_id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
|
|
||||||
class AccountDAO {
|
class MaterialDAO {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static bool upsertAccount(const AccountInfo &info);
|
[[nodiscard]] static bool upsertAccount(const MaterialInfo &info);
|
||||||
|
|
||||||
[[nodiscard]] static bool updateAccountById(
|
[[nodiscard]] static bool updateAccountById(
|
||||||
int id,
|
int id,
|
||||||
@ -19,19 +19,19 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] static bool updateMaterialId(int accountId, const QString &materialId);
|
[[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 &deviceId,
|
||||||
const QString &appName,
|
const QString &appName,
|
||||||
const QString &lastNumber
|
const QString &lastNumber
|
||||||
);
|
);
|
||||||
|
|
||||||
[[nodiscard]] static AccountInfo findByMaterialId(const QString &materialId);
|
[[nodiscard]] static MaterialInfo findByMaterialId(const QString &materialId);
|
||||||
|
|
||||||
[[nodiscard]] static QStringList getActiveMaterialIds();
|
[[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 <QApplication>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "views/MainWindow.h"
|
#include "views/MainWindow.h"
|
||||||
|
|
||||||
#include "database/DatabaseManager.h"
|
#include "database/DatabaseManager.h"
|
||||||
@ -27,6 +27,7 @@
|
|||||||
#include "black/PayByCardScript.h"
|
#include "black/PayByCardScript.h"
|
||||||
#include "ozon/PayByPhoneScript.h"
|
#include "ozon/PayByPhoneScript.h"
|
||||||
#include "widget/loader/ErrorWindow.h"
|
#include "widget/loader/ErrorWindow.h"
|
||||||
|
#include "widget/loader/LoaderDialog.h"
|
||||||
#include "widget/loader/LoaderWindow.h"
|
#include "widget/loader/LoaderWindow.h"
|
||||||
#include "widget/loader/RegistrationWindow.h"
|
#include "widget/loader/RegistrationWindow.h"
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
@ -79,12 +80,12 @@ void startEventHandler(const QCoreApplication &app) {
|
|||||||
|
|
||||||
void startPayByPhoneTest() {
|
void startPayByPhoneTest() {
|
||||||
// Берём первый девайс с ozon аккаунтом
|
// Берём первый девайс с ozon аккаунтом
|
||||||
QList<AccountInfo> accounts = AccountDAO::getAccountsByAppCode("ozon");
|
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("ozon");
|
||||||
if (accounts.isEmpty()) {
|
if (accounts.isEmpty()) {
|
||||||
qWarning() << "[PayByPhoneTest] No ozon accounts found";
|
qWarning() << "[PayByPhoneTest] No ozon accounts found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountInfo account = accounts.first();
|
MaterialInfo account = accounts.first();
|
||||||
qDebug() << "[PayByPhoneTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
qDebug() << "[PayByPhoneTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
||||||
|
|
||||||
auto *thread = new QThread(nullptr);
|
auto *thread = new QThread(nullptr);
|
||||||
@ -107,12 +108,12 @@ void startPayByPhoneTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startBlackPayByCardTest() {
|
void startBlackPayByCardTest() {
|
||||||
QList<AccountInfo> accounts = AccountDAO::getAccountsByAppCode("black");
|
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("black");
|
||||||
if (accounts.isEmpty()) {
|
if (accounts.isEmpty()) {
|
||||||
qWarning() << "[BlackPayByCardTest] No black accounts found";
|
qWarning() << "[BlackPayByCardTest] No black accounts found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountInfo account = accounts.first();
|
MaterialInfo account = accounts.first();
|
||||||
qDebug() << "[BlackPayByCardTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
qDebug() << "[BlackPayByCardTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
||||||
|
|
||||||
auto *thread = new QThread(nullptr);
|
auto *thread = new QThread(nullptr);
|
||||||
@ -143,7 +144,22 @@ void startNetworkService(const QCoreApplication &app) {
|
|||||||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||||||
QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
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();
|
networkService->stop();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
loader->close();
|
||||||
|
delete loader;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Сигнал эмитируется из фонового потока → Qt::QueuedConnection доставит его в главный поток
|
// Сигнал эмитируется из фонового потока → Qt::QueuedConnection доставит его в главный поток
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Версия 1 (user_version = 1): начальная схема
|
-- Схема БД ARCDeskProject
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS devices
|
CREATE TABLE IF NOT EXISTS devices
|
||||||
(
|
(
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
api_id TEXT,
|
api_id TEXT,
|
||||||
|
adb_serial TEXT,
|
||||||
status TEXT,
|
status TEXT,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
android TEXT,
|
android TEXT,
|
||||||
@ -17,7 +18,7 @@ CREATE TABLE IF NOT EXISTS devices
|
|||||||
image BLOB
|
image BLOB
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS applications
|
CREATE TABLE IF NOT EXISTS bank_profiles
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
@ -29,12 +30,18 @@ CREATE TABLE IF NOT EXISTS applications
|
|||||||
status TEXT DEFAULT 'off',
|
status TEXT DEFAULT 'off',
|
||||||
install TEXT DEFAULT 'no',
|
install TEXT DEFAULT 'no',
|
||||||
comment TEXT,
|
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,
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
UNIQUE (device_id, code)
|
UNIQUE (device_id, code)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- app_code: код банка ("ozon", "black") — не путать с человекочитаемым name
|
-- app_code: код банка ("ozon", "black") — не путать с человекочитаемым name
|
||||||
CREATE TABLE IF NOT EXISTS accounts
|
CREATE TABLE IF NOT EXISTS materials
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
@ -45,6 +52,8 @@ CREATE TABLE IF NOT EXISTS accounts
|
|||||||
currency TEXT DEFAULT '',
|
currency TEXT DEFAULT '',
|
||||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||||
status TEXT DEFAULT 'active',
|
status TEXT DEFAULT 'active',
|
||||||
|
card_number TEXT DEFAULT '',
|
||||||
|
material_id TEXT DEFAULT '',
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
UNIQUE (device_id, app_code, last_numbers)
|
UNIQUE (device_id, app_code, last_numbers)
|
||||||
);
|
);
|
||||||
@ -70,14 +79,15 @@ CREATE TABLE IF NOT EXISTS transactions
|
|||||||
update_time DATETIME,
|
update_time DATETIME,
|
||||||
complete_time DATETIME,
|
complete_time DATETIME,
|
||||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
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
|
CREATE TABLE IF NOT EXISTS events
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
account_id INTEGER,
|
account_id INTEGER,
|
||||||
external_id INTEGER,
|
external_id TEXT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
amount REAL,
|
amount REAL,
|
||||||
phone TEXT,
|
phone TEXT,
|
||||||
@ -88,15 +98,46 @@ CREATE TABLE IF NOT EXISTS events
|
|||||||
update_time DATETIME,
|
update_time DATETIME,
|
||||||
complete_time DATETIME,
|
complete_time DATETIME,
|
||||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
|
json TEXT DEFAULT '',
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
sent_to_server INTEGER DEFAULT 0,
|
||||||
UNIQUE (external_id)
|
screenshot BLOB,
|
||||||
|
FOREIGN KEY (account_id) REFERENCES materials (id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================================
|
CREATE TABLE IF NOT EXISTS banks
|
||||||
-- Версия 2 (user_version = 2): currency + rename app_name→app_code
|
(
|
||||||
-- ============================================================
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
alias TEXT UNIQUE,
|
||||||
|
name TEXT,
|
||||||
|
nspk_name TEXT
|
||||||
|
);
|
||||||
|
|
||||||
-- Для существующих БД (применяется автоматически при старте):
|
CREATE TABLE IF NOT EXISTS app_logs
|
||||||
-- ALTER TABLE accounts ADD COLUMN currency TEXT DEFAULT '';
|
(
|
||||||
-- ALTER TABLE accounts RENAME COLUMN app_name TO app_code;
|
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 <QByteArray>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
struct ApplicationInfo {
|
struct BankProfileInfo {
|
||||||
int id = -1;
|
int id = -1;
|
||||||
bool install = false;
|
bool install = false;
|
||||||
bool pinCodeChecked = false;
|
bool pinCodeChecked = false;
|
||||||
@ -3,8 +3,9 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
struct DeviceInfo {
|
struct DeviceInfo {
|
||||||
QString id; // ADB device id (e.g. "emulator-5554")
|
QString id; // android_id (unique device identifier)
|
||||||
QString apiId; // UUID from backend API (DeviceResponseModel.id)
|
QString apiId; // UUID from backend API (DeviceResponseModel.id)
|
||||||
|
QString adbSerial; // ADB serial (e.g. "emulator-5554", "7dfc444d43fbeab7")
|
||||||
QString status;
|
QString status;
|
||||||
QString name;
|
QString name;
|
||||||
QString android;
|
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 <db/DeviceInfo.h>
|
||||||
|
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/SettingsDAO.h"
|
#include "dao/SettingsDAO.h"
|
||||||
|
|
||||||
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
||||||
@ -181,12 +182,13 @@ QString DeviceScreener::createDeviceOnApi(const DeviceInfo &device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["desktop_id"] = desktopId;
|
body["desktop_id"] = desktopId;
|
||||||
body["status"] = "ONLINE";
|
body["bank_profile_id"] = QJsonValue::Null;
|
||||||
body["name"] = device.name;
|
body["status"] = "ONLINE";
|
||||||
body["screen_width"] = device.screenWidth;
|
body["name"] = device.name;
|
||||||
body["screen_height"] = device.screenHeight;
|
body["screen_width"] = device.screenWidth;
|
||||||
body["battery"] = device.battery;
|
body["screen_height"] = device.screenHeight;
|
||||||
|
body["battery"] = device.battery;
|
||||||
|
|
||||||
const QJsonValue result = apiPost("/api/v1/device/", body);
|
const QJsonValue result = apiPost("/api/v1/device/", body);
|
||||||
if (!result.isNull() && !result.isUndefined()) {
|
if (!result.isNull() && !result.isUndefined()) {
|
||||||
@ -239,25 +241,42 @@ void DeviceScreener::postScreenshotToApi(const QString &apiId, const QByteArray
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeviceScreener::updateDeviceOnApi(const DeviceInfo &device) {
|
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;
|
QJsonObject body;
|
||||||
body["status"] = (device.status == "device") ? "ONLINE" : "OFFLINE";
|
body["status"] = (device.status == "device") ? "ONLINE" : "OFFLINE";
|
||||||
body["battery"] = device.battery;
|
body["bank_profile_id"] = bankProfileId;
|
||||||
body["update_time"] = QDateTime::currentSecsSinceEpoch();
|
body["battery"] = device.battery;
|
||||||
|
body["update_time"] = QDateTime::currentSecsSinceEpoch();
|
||||||
apiPatch("/api/v1/device/" + device.apiId, body);
|
apiPatch("/api/v1/device/" + device.apiId, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceScreener::toggleBankProfilesOnApi(const QString &deviceId, bool enable) {
|
void DeviceScreener::toggleBankProfilesOnApi(const QString &deviceId, bool enable) {
|
||||||
const QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(deviceId);
|
if (enable) return; // не включаем автоматически при появлении устройства
|
||||||
for (const ApplicationInfo &app : apps) {
|
|
||||||
|
const QList<BankProfileInfo> apps = BankProfileDAO::getAllApplicationsByDeviceId(deviceId);
|
||||||
|
for (const BankProfileInfo &app : apps) {
|
||||||
if (app.bankProfileId.isEmpty()) continue;
|
if (app.bankProfileId.isEmpty()) continue;
|
||||||
|
if (app.status != "active") continue;
|
||||||
|
|
||||||
// При включении — только если приложение активно в системе
|
apiPatch("/api/v1/profile/bank_profile/" + app.bankProfileId + "/false", QJsonObject());
|
||||||
if (enable && app.status != "active") continue;
|
|
||||||
|
|
||||||
const QString enableStr = enable ? "true" : "false";
|
const QList<MaterialInfo> materials = MaterialDAO::getAccounts(deviceId, app.code);
|
||||||
const QString path = "/api/v1/profile/bank_profile/" + app.bankProfileId + "/" + enableStr;
|
for (const MaterialInfo &mat : materials) {
|
||||||
apiPatch(path, QJsonObject());
|
if (mat.materialId.isEmpty()) continue;
|
||||||
|
apiPatch("/api/v1/profile/material/" + mat.materialId + "/false", QJsonObject());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BankProfileDAO::deactivateAppsForDevice(deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue DeviceScreener::apiGet(const QString &path) {
|
QJsonValue DeviceScreener::apiGet(const QString &path) {
|
||||||
@ -304,24 +323,39 @@ void DeviceScreener::start() {
|
|||||||
QList<QPair<QString, QString> > devices = readAdbDevices();
|
QList<QPair<QString, QString> > devices = readAdbDevices();
|
||||||
QStringList onlineIds;
|
QStringList onlineIds;
|
||||||
QList<DeviceInfo> deviceInfos;
|
QList<DeviceInfo> deviceInfos;
|
||||||
for (const auto &[id, status]: devices) {
|
for (const auto &[adbSerial, status]: devices) {
|
||||||
DeviceInfo device;
|
DeviceInfo device;
|
||||||
device.id = id;
|
bool isChecked = true;
|
||||||
|
|
||||||
if (status == "device") {
|
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;
|
onlineIds << device.id;
|
||||||
const bool isChecked = prevOnline.contains(id);
|
isChecked = prevOnline.contains(device.id);
|
||||||
device = readDeviceInfo(id, isChecked);
|
|
||||||
device.image = takeScreenshot(id);
|
device.image = takeScreenshot(adbSerial);
|
||||||
if (!device.image.isEmpty()) {
|
if (!device.image.isEmpty()) {
|
||||||
if (!DeviceDAO::updateImage(id, device.image)) {
|
if (!DeviceDAO::updateImage(device.id, device.image)) {
|
||||||
qDebug() << "Cant update screenshot";
|
qDebug() << "Cant update screenshot";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isChecked) {
|
} else {
|
||||||
// Новое устройство — включаем active профили
|
device.id = adbSerial;
|
||||||
toggleBankProfilesOnApi(id, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
device.status = status;
|
device.status = status;
|
||||||
device.updateTime = QDateTime::currentDateTime();
|
device.updateTime = QDateTime::currentDateTime();
|
||||||
@ -330,6 +364,16 @@ void DeviceScreener::start() {
|
|||||||
if (!DeviceDAO::upsertDevice(device)) {
|
if (!DeviceDAO::upsertDevice(device)) {
|
||||||
qDebug() << "Cant update 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
|
// Синхронизация с API
|
||||||
@ -361,10 +405,17 @@ void DeviceScreener::start() {
|
|||||||
qDebug() << "Cant mark devices offline";
|
qDebug() << "Cant mark devices offline";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Было онлайн в БД, сейчас офлайн — выключаем профили на сервере
|
// Было онлайн в БД, сейчас офлайн — выключаем профили и обновляем статус на сервере
|
||||||
for (const QString &id : prevOnline) {
|
for (const QString &id : prevOnline) {
|
||||||
if (!onlineIds.contains(id)) {
|
if (!onlineIds.contains(id)) {
|
||||||
toggleBankProfilesOnApi(id, false);
|
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 screenRegex(R"(Physical size: (\d+)x(\d+))");
|
||||||
static const QRegularExpression batteryRegex(R"(level:\s*(\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;
|
DeviceInfo info;
|
||||||
info.id = deviceId;
|
|
||||||
info.screenWidth = 0;
|
info.screenWidth = 0;
|
||||||
info.screenHeight = 0;
|
info.screenHeight = 0;
|
||||||
info.battery = 0;
|
info.battery = 0;
|
||||||
@ -462,12 +512,17 @@ DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool is
|
|||||||
auto runAdbCommand = [&](const QStringList &args) -> QString {
|
auto runAdbCommand = [&](const QStringList &args) -> QString {
|
||||||
QProcess process;
|
QProcess process;
|
||||||
process.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
process.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
||||||
process.start(AdbUtils::adbPath(), QStringList{"-s", deviceId} + args);
|
process.start(AdbUtils::adbPath(), QStringList{"-s", adbSerial} + args);
|
||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
return QString::fromUtf8(process.readAllStandardOutput()).trimmed();
|
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"});
|
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();
|
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();
|
info.updateTime = QDateTime::currentDateTime();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ private:
|
|||||||
|
|
||||||
static QByteArray takeScreenshot(const QString &deviceId);
|
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);
|
void postDevicesData(const QList<DeviceInfo> &devices);
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,13 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/BankDAO.h"
|
#include "dao/BankDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/EventDAO.h"
|
#include "dao/EventDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
#include "adb/AdbUtils.h"
|
#include "adb/AdbUtils.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "black/GetLastTransactionsScript.h"
|
#include "black/GetLastTransactionsScript.h"
|
||||||
@ -50,7 +50,7 @@ void EventHandler::start() {
|
|||||||
|
|
||||||
// Запрашиваем новые задачи только для устройств без pending events
|
// Запрашиваем новые задачи только для устройств без pending events
|
||||||
const QSet<QString> busyDevices = EventDAO::getDevicesWithPendingEvents();
|
const QSet<QString> busyDevices = EventDAO::getDevicesWithPendingEvents();
|
||||||
const QStringList materialIds = AccountDAO::getActiveMaterialIdsExcludingDevices(busyDevices);
|
const QStringList materialIds = MaterialDAO::getActiveMaterialIdsExcludingDevices(busyDevices);
|
||||||
if (!materialIds.isEmpty()) {
|
if (!materialIds.isEmpty()) {
|
||||||
m_network->getTasks(materialIds);
|
m_network->getTasks(materialIds);
|
||||||
}
|
}
|
||||||
@ -99,13 +99,18 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ищем аккаунт по material_id напрямую
|
// Ищем аккаунт по material_id напрямую
|
||||||
const AccountInfo account = AccountDAO::findByMaterialId(materialId);
|
const MaterialInfo account = MaterialDAO::findByMaterialId(materialId);
|
||||||
if (account.id == -1) {
|
if (account.id == -1) {
|
||||||
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
||||||
sendTaskError(typeStr, materialId, bankName, "Account not found for material_id: " + materialId);
|
sendTaskError(typeStr, materialId, bankName, "Account not found for material_id: " + materialId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FETCH_PROFILE: отменяем старые ожидающие задачи такого же типа на этом устройстве
|
||||||
|
if (eventType == EventType::FetchProfile) {
|
||||||
|
EventDAO::cancelWaitingByType(account.deviceId, EventType::FetchProfile);
|
||||||
|
}
|
||||||
|
|
||||||
EventInfo event;
|
EventInfo event;
|
||||||
event.status = EventStatus::Wait;
|
event.status = EventStatus::Wait;
|
||||||
event.type = eventType;
|
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) {
|
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;
|
const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
|
||||||
|
|
||||||
if (!EventDAO::updateEvent(event.id, newStatus, result)) {
|
if (!EventDAO::updateEvent(event.id, newStatus, result)) {
|
||||||
qCritical() << "[EventHandler] Failed to update event:" << event.id;
|
qCritical() << "[EventHandler] Failed to update event:" << event.id;
|
||||||
} else {
|
} else {
|
||||||
if (newStatus == EventStatus::Error) {
|
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);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result);
|
||||||
EventDAO::markSentToServer(event.id);
|
EventDAO::markSentToServer(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newStatus != EventStatus::Error) {
|
if (newStatus != EventStatus::Error) {
|
||||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
||||||
const QMap<QString, int> currencyCodes = {
|
const QMap<QString, int> currencyCodes = {
|
||||||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}
|
{"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);
|
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 auto thr = new QThread(this);
|
||||||
const QString key = account.deviceId;
|
const QString key = account.deviceId;
|
||||||
|
|
||||||
@ -373,17 +404,27 @@ void EventHandler::startThreadForTask(const AccountInfo &account, const EventInf
|
|||||||
};
|
};
|
||||||
|
|
||||||
const QString &appCode = account.appCode;
|
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 (event.type == EventType::FetchProfile) {
|
||||||
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(deviceId, appCode));
|
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(adbSerial, appCode, nullptr, true));
|
||||||
else if (appCode == "black") setup(new Black::LoginAndCheckAccountsScript(deviceId, appCode));
|
else if (appCode == "black") setup(new Black::LoginAndCheckAccountsScript(adbSerial, appCode));
|
||||||
else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode;
|
else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == EventType::FetchTransactions) {
|
if (event.type == EventType::FetchTransactions) {
|
||||||
if (appCode == "ozon") setup(new Ozon::GetLastTransactionsScript(deviceId, appCode));
|
// Парсим amount и created_at из body задачи
|
||||||
else if (appCode == "black") setup(new Black::GetLastTransactionsScript(deviceId, appCode));
|
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;
|
else qWarning() << "[EventHandler] FETCH_TRANSACTIONS: unknown appCode:" << appCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,8 +489,8 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, account.appCode);
|
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, account.appCode);
|
||||||
if (app.status != "active") {
|
if (app.status != "active") {
|
||||||
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
||||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "db/EventInfo.h"
|
#include "db/EventInfo.h"
|
||||||
|
|
||||||
class NetworkService;
|
class NetworkService;
|
||||||
@ -39,7 +39,7 @@ private:
|
|||||||
|
|
||||||
void updateEventThread(const QString &key, const EventInfo &event, const QString &result);
|
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);
|
void processNextTask(const QString &deviceId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,8 +11,9 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/BankDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "dao/EventDAO.h"
|
#include "dao/EventDAO.h"
|
||||||
#include "dao/SettingsDAO.h"
|
#include "dao/SettingsDAO.h"
|
||||||
@ -181,7 +182,7 @@ void NetworkService::getPayments() {
|
|||||||
QString deviceId = obj["deviceId"].toString();
|
QString deviceId = obj["deviceId"].toString();
|
||||||
QString appName = obj["appName"].toString();
|
QString appName = obj["appName"].toString();
|
||||||
QString lastNumbers = obj["lastNumbers"].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.accountId = account.id;
|
||||||
eventInfo.deviceId = account.deviceId;
|
eventInfo.deviceId = account.deviceId;
|
||||||
@ -291,10 +292,37 @@ void NetworkService::loginAndSetup() {
|
|||||||
const qint64 expiresIn = static_cast<qint64>(loginData["expires_in"].toDouble());
|
const qint64 expiresIn = static_cast<qint64>(loginData["expires_in"].toDouble());
|
||||||
SettingsDAO::setLong("token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
SettingsDAO::setLong("token_expires_at", QDateTime::currentSecsSinceEpoch() + expiresIn);
|
||||||
|
|
||||||
// Шаг 2: проверяем есть ли desktop_id
|
// Шаг 2: получаем актуальный desktop с сервера
|
||||||
const QString desktopId = SettingsDAO::get("desktop_id");
|
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()) {
|
if (desktopId.isEmpty()) {
|
||||||
// Шаг 3: создаём desktop
|
// Десктопов на сервере нет — создаём новый
|
||||||
QJsonObject desktopBody;
|
QJsonObject desktopBody;
|
||||||
desktopBody["blue_token"] = QJsonValue::Null;
|
desktopBody["blue_token"] = QJsonValue::Null;
|
||||||
desktopBody["name"] = QJsonValue::Null;
|
desktopBody["name"] = QJsonValue::Null;
|
||||||
@ -310,13 +338,38 @@ void NetworkService::loginAndSetup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const QJsonObject desktopData = desktopResult.toObject();
|
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());
|
SettingsDAO::set("profile_id", desktopData["profile_id"].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Шаг 4: синхронизация устройств и профилей
|
// Шаг 3: синхронизация устройств с сервера
|
||||||
|
syncDevicesFromServer(desktopId);
|
||||||
|
|
||||||
|
// Шаг 4: синхронизация банковских профилей и материалов с сервера
|
||||||
|
syncBankProfilesFromServer();
|
||||||
|
|
||||||
|
// Шаг 5: загрузка справочника банков если пустой
|
||||||
|
syncBanksIfEmpty();
|
||||||
|
|
||||||
|
// Шаг 6: синхронизация статусов профилей/материалов с учётом онлайн-устройств
|
||||||
syncCurrentProfile();
|
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 finishedWithResult(1);
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
@ -412,6 +465,49 @@ QJsonValue NetworkService::patchJson(const QString &path, const QJsonObject &bod
|
|||||||
return result;
|
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) {
|
QJsonValue NetworkService::getJson(const QString &path, bool withAuth) {
|
||||||
const QUrl url(path);
|
const QUrl url(path);
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
@ -457,6 +553,255 @@ QJsonValue NetworkService::getJson(const QString &path, bool withAuth) {
|
|||||||
return result;
|
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() {
|
void NetworkService::syncCurrentProfile() {
|
||||||
// 1. Определяем онлайн-устройства по БД (DeviceScreener обновит позже)
|
// 1. Определяем онлайн-устройства по БД (DeviceScreener обновит позже)
|
||||||
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
|
const QList<DeviceInfo> allDevices = DeviceDAO::getAllDevices(false);
|
||||||
@ -492,8 +837,8 @@ void NetworkService::syncCurrentProfile() {
|
|||||||
const QString serverBpState = bp["state"].toString(); // "enabled" / "disabled"
|
const QString serverBpState = bp["state"].toString(); // "enabled" / "disabled"
|
||||||
|
|
||||||
// Находим applications с этим bank_profile_id
|
// Находим applications с этим bank_profile_id
|
||||||
const QList<ApplicationInfo> apps = ApplicationDAO::findAllByBankProfileId(bpId);
|
const QList<BankProfileInfo> apps = BankProfileDAO::findAllByBankProfileId(bpId);
|
||||||
for (const ApplicationInfo &app : apps) {
|
for (const BankProfileInfo &app : apps) {
|
||||||
const bool deviceOnline = onlineDeviceIds.contains(app.deviceId);
|
const bool deviceOnline = onlineDeviceIds.contains(app.deviceId);
|
||||||
const QString localStatus = app.status; // "active" / "off"
|
const QString localStatus = app.status; // "active" / "off"
|
||||||
const QString serverStatus = (serverBpState == "disabled") ? "off" : "active";
|
const QString serverStatus = (serverBpState == "disabled") ? "off" : "active";
|
||||||
@ -521,11 +866,11 @@ void NetworkService::syncCurrentProfile() {
|
|||||||
const QString matId = mat["id"].toString();
|
const QString matId = mat["id"].toString();
|
||||||
const QString serverMatState = mat["state"].toString();
|
const QString serverMatState = mat["state"].toString();
|
||||||
|
|
||||||
const AccountInfo account = AccountDAO::findByMaterialId(matId);
|
const MaterialInfo account = MaterialDAO::findByMaterialId(matId);
|
||||||
if (account.id < 0) continue; // не найден в локальной БД
|
if (account.id < 0) continue; // не найден в локальной БД
|
||||||
|
|
||||||
const bool deviceOnline = onlineDeviceIds.contains(account.deviceId);
|
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";
|
const QString serverStatus = (serverMatState == "disabled") ? "off" : "active";
|
||||||
|
|
||||||
if (!deviceOnline) {
|
if (!deviceOnline) {
|
||||||
@ -559,7 +904,7 @@ void NetworkService::postBankProfile() {
|
|||||||
const QString bankProfileId = data["id"].toString();
|
const QString bankProfileId = data["id"].toString();
|
||||||
const int appId = m_extraData.value("app_id").toInt();
|
const int appId = m_extraData.value("app_id").toInt();
|
||||||
if (!bankProfileId.isEmpty() && appId > 0) {
|
if (!bankProfileId.isEmpty() && appId > 0) {
|
||||||
ApplicationDAO::updateBankProfileId(appId, bankProfileId);
|
BankProfileDAO::updateBankProfileId(appId, bankProfileId);
|
||||||
qDebug() << "[NetworkService] postBankProfile saved bank_profile_id:" << bankProfileId;
|
qDebug() << "[NetworkService] postBankProfile saved bank_profile_id:" << bankProfileId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,7 +961,7 @@ void NetworkService::postMaterial() {
|
|||||||
const QString materialId = result.toObject()["id"].toString();
|
const QString materialId = result.toObject()["id"].toString();
|
||||||
const int accountId = m_extraData.value("account_id").toInt();
|
const int accountId = m_extraData.value("account_id").toInt();
|
||||||
if (!materialId.isEmpty() && accountId > 0) {
|
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;
|
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);
|
QUrl url(m_apiBase + m_pathGetTasks);
|
||||||
QUrlQuery urlQuery;
|
QUrlQuery urlQuery;
|
||||||
const QStringList ids = materialIds.isEmpty() ? AccountDAO::getActiveMaterialIds() : materialIds;
|
const QStringList ids = materialIds.isEmpty() ? MaterialDAO::getActiveMaterialIds() : materialIds;
|
||||||
if (ids.isEmpty()) {
|
if (ids.isEmpty()) {
|
||||||
m_fetchingTasks = false;
|
m_fetchingTasks = false;
|
||||||
return;
|
return;
|
||||||
@ -903,8 +1248,62 @@ void NetworkService::start() {
|
|||||||
scheduleNextRefresh();
|
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() {
|
void NetworkService::stop() {
|
||||||
m_running = false;
|
m_running = false;
|
||||||
// Эмитируем finished() в потоке NetworkService, чтобы корректно завершить QThread
|
QMetaObject::invokeMethod(this, [this]() {
|
||||||
QMetaObject::invokeMethod(this, [this]() { emit finished(); }, Qt::QueuedConnection);
|
disableAllProfilesOnServer();
|
||||||
|
emit finished();
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,8 @@ public slots:
|
|||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
void disableAllProfilesOnServer();
|
||||||
|
void deleteDeviceFromServer();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
@ -103,8 +105,12 @@ private:
|
|||||||
QJsonValue getJson(const QString &path, bool withAuth = false);
|
QJsonValue getJson(const QString &path, bool withAuth = false);
|
||||||
QJsonValue postJson(const QString &path, const QJsonObject &body, 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 patchJson(const QString &path, const QJsonObject &body, bool withAuth = false);
|
||||||
|
QJsonValue deleteJson(const QString &path, bool withAuth = false);
|
||||||
|
|
||||||
bool refreshToken();
|
bool refreshToken();
|
||||||
bool ensureValidToken();
|
bool ensureValidToken();
|
||||||
void scheduleNextRefresh();
|
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 *bankListAction = new QAction("Банки", this);
|
||||||
auto *tutorialPageAction = new QAction("Инструкция", this);
|
auto *tutorialPageAction = new QAction("Инструкция", this);
|
||||||
auto *eventAction = new QAction("События", this);
|
auto *eventAction = new QAction("События", this);
|
||||||
auto *errorLogAction = new QAction("Ошибки", this);
|
|
||||||
auto *fileLogAction = new QAction("Логи", this);
|
auto *fileLogAction = new QAction("Логи", this);
|
||||||
menu->addAction(devicesPageAction);
|
menu->addAction(devicesPageAction);
|
||||||
// menu->addAction(accountPageAction);
|
// menu->addAction(accountPageAction);
|
||||||
menu->addAction(bankListAction);
|
menu->addAction(bankListAction);
|
||||||
menu->addAction(eventAction);
|
menu->addAction(eventAction);
|
||||||
menu->addAction(tutorialPageAction);
|
menu->addAction(tutorialPageAction);
|
||||||
menu->addAction(errorLogAction);
|
|
||||||
menu->addAction(fileLogAction);
|
menu->addAction(fileLogAction);
|
||||||
|
|
||||||
connect(devicesPageAction, &QAction::triggered, this, [=]() {
|
connect(devicesPageAction, &QAction::triggered, this, [=]() {
|
||||||
@ -98,15 +96,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
|||||||
m_eventWindow->activateWindow();
|
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, [=]() {
|
connect(fileLogAction, &QAction::triggered, this, [=]() {
|
||||||
if (!m_fileLogWindow) {
|
if (!m_fileLogWindow) {
|
||||||
m_fileLogWindow = new FileLogWindow(this);
|
m_fileLogWindow = new FileLogWindow(this);
|
||||||
|
|||||||
@ -32,7 +32,6 @@ private:
|
|||||||
QWidget *m_accountsWindow = nullptr;
|
QWidget *m_accountsWindow = nullptr;
|
||||||
QWidget *m_tutorialWindow = nullptr;
|
QWidget *m_tutorialWindow = nullptr;
|
||||||
QWidget *m_bankListWindow = nullptr;
|
QWidget *m_bankListWindow = nullptr;
|
||||||
QWidget *m_logWindow = nullptr;
|
|
||||||
QWidget *m_fileLogWindow = nullptr;
|
QWidget *m_fileLogWindow = nullptr;
|
||||||
QWidget *m_eventWindow = nullptr;
|
QWidget *m_eventWindow = nullptr;
|
||||||
|
|
||||||
|
|||||||
@ -19,15 +19,15 @@
|
|||||||
#include "black/LoginAndCheckAccountsScript.h"
|
#include "black/LoginAndCheckAccountsScript.h"
|
||||||
#include "black/GetProfileInfoScript.h"
|
#include "black/GetProfileInfoScript.h"
|
||||||
#include "black/GetCardInfoScript.h"
|
#include "black/GetCardInfoScript.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "bank/TransactionWindow.h"
|
#include "bank/TransactionWindow.h"
|
||||||
#include "device/EditBankAccountDialog.h"
|
#include "device/EditBankAccountDialog.h"
|
||||||
#include "device/EditBankDialog.h"
|
#include "device/EditBankDialog.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
|
|
||||||
AccountSettingsWindow::AccountSettingsWindow(
|
AccountSettingsWindow::AccountSettingsWindow(
|
||||||
QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app
|
QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app
|
||||||
) : QWidget(parent,
|
) : QWidget(parent,
|
||||||
Qt::Window
|
Qt::Window
|
||||||
| Qt::WindowTitleHint
|
| Qt::WindowTitleHint
|
||||||
@ -90,7 +90,7 @@ AccountSettingsWindow::AccountSettingsWindow(
|
|||||||
const QString comment = dlg.secondValue();
|
const QString comment = dlg.secondValue();
|
||||||
bool on = dlg.toggled();
|
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";
|
qDebug() << "Cant update bank data";
|
||||||
} else {
|
} else {
|
||||||
m_applicationInfo.pinCode = pinCode;
|
m_applicationInfo.pinCode = pinCode;
|
||||||
@ -132,6 +132,17 @@ AccountSettingsWindow::AccountSettingsWindow(
|
|||||||
m_profileLabel->setTextFormat(Qt::RichText);
|
m_profileLabel->setTextFormat(Qt::RichText);
|
||||||
m_profileLabel->setStyleSheet("margin-bottom: 8px;");
|
m_profileLabel->setStyleSheet("margin-bottom: 8px;");
|
||||||
profileRow->addWidget(m_profileLabel);
|
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);
|
layout->addLayout(profileRow);
|
||||||
|
|
||||||
QPushButton *soloButton = new QPushButton("Запустить проверку пин-кода");
|
QPushButton *soloButton = new QPushButton("Запустить проверку пин-кода");
|
||||||
@ -320,35 +331,6 @@ AccountSettingsWindow::AccountSettingsWindow(
|
|||||||
|
|
||||||
|
|
||||||
layout->addWidget(tableWidget);
|
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();
|
tableWidget->resizeRowsToContents();
|
||||||
|
|
||||||
@ -365,11 +347,22 @@ AccountSettingsWindow::AccountSettingsWindow(
|
|||||||
|
|
||||||
void AccountSettingsWindow::startCheckPinCode(const QString &appCode) {
|
void AccountSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||||
QThread *thread = new QThread(nullptr);
|
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") {
|
if (appCode == "ozon") {
|
||||||
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||||
worker->moveToThread(thread);
|
worker->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start);
|
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, thread, &QThread::quit);
|
||||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||||
connect(thread, &QThread::finished, thread, &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);
|
auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||||
worker->moveToThread(thread);
|
worker->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start);
|
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, thread, &QThread::quit);
|
||||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||||
connect(thread, &QThread::finished, thread, &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) {
|
void AccountSettingsWindow::startGetLastTransactions(const QString &appCode, int maxCount) {
|
||||||
if (appCode == "ozon") {
|
if (appCode == "ozon") {
|
||||||
QThread *thread = new QThread(nullptr);
|
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);
|
worker->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start);
|
connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start);
|
||||||
connect(worker, &Ozon::GetLastTransactionsScript::finished, thread, &QThread::quit);
|
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) {
|
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()) {
|
if (app.bankProfileId.isEmpty()) {
|
||||||
qWarning() << "[AccountSettings] sendAppStatus: no bankProfileId for" << code;
|
qWarning() << "[AccountSettings] sendAppStatus: no bankProfileId for" << code;
|
||||||
return;
|
return;
|
||||||
@ -477,7 +471,7 @@ void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString
|
|||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
void AccountSettingsWindow::updateAccountData(const MaterialInfo &account) {
|
||||||
QJsonArray list;
|
QJsonArray list;
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj["appName"] = account.appCode;
|
obj["appName"] = account.appCode;
|
||||||
@ -485,7 +479,7 @@ void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
|||||||
obj["amount"] = account.amount;
|
obj["amount"] = account.amount;
|
||||||
obj["description"] = account.description;
|
obj["description"] = account.description;
|
||||||
obj["currency"] = account.currency;
|
obj["currency"] = account.currency;
|
||||||
obj["status"] = accountStatusToString(account.status);
|
obj["status"] = materialStatusToString(account.status);
|
||||||
|
|
||||||
list.append(obj);
|
list.append(obj);
|
||||||
|
|
||||||
@ -502,11 +496,11 @@ void AccountSettingsWindow::updateAccountData(const AccountInfo &account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
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->clearContents();
|
||||||
tableWidget->setRowCount(accounts.length());
|
tableWidget->setRowCount(accounts.length());
|
||||||
tableWidget->setColumnCount(8);
|
tableWidget->setColumnCount(9);
|
||||||
tableWidget->setHorizontalHeaderLabels({
|
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) {
|
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"));
|
QTableWidgetItem *time = new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss"));
|
||||||
time->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
time->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
@ -554,18 +549,24 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
currency->setFlags(currency->flags() & ~Qt::ItemIsEditable);
|
currency->setFlags(currency->flags() & ~Qt::ItemIsEditable);
|
||||||
tableWidget->setItem(k, 4, currency);
|
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->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
status->setFlags(status->flags() & ~Qt::ItemIsEditable);
|
status->setFlags(status->flags() & ~Qt::ItemIsEditable);
|
||||||
if (account.status != AccountStatus::Active) {
|
if (account.status != MaterialStatus::Active) {
|
||||||
status->setForeground(QBrush(Qt::red));
|
status->setForeground(QBrush(Qt::red));
|
||||||
}
|
}
|
||||||
tableWidget->setItem(k, 5, status);
|
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);
|
auto *deleteBankAccountBtn = new QPushButton("Удалить", tableWidget);
|
||||||
deleteBankAccountBtn->setStyleSheet("background-color: red; color: white;");
|
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]() {
|
connect(deleteBankAccountBtn, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||||
QMessageBox msgBox(this);
|
QMessageBox msgBox(this);
|
||||||
@ -576,7 +577,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
msgBox.setDefaultButton(cancelBtn);
|
msgBox.setDefaultButton(cancelBtn);
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
if (msgBox.clickedButton() == deleteBtn) {
|
if (msgBox.clickedButton() == deleteBtn) {
|
||||||
if (AccountDAO::deleteAccount(account.id)) {
|
if (MaterialDAO::deleteAccount(account.id)) {
|
||||||
reloadContent(tableWidget);
|
reloadContent(tableWidget);
|
||||||
tableWidget->resizeColumnsToContents();
|
tableWidget->resizeColumnsToContents();
|
||||||
} else {
|
} else {
|
||||||
@ -587,7 +588,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
|
|
||||||
auto *transactionsBtn = new QPushButton("Транзакции", tableWidget);
|
auto *transactionsBtn = new QPushButton("Транзакции", tableWidget);
|
||||||
transactionsBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
transactionsBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||||
tableWidget->setCellWidget(k, 7, transactionsBtn);
|
tableWidget->setCellWidget(k, 8, transactionsBtn);
|
||||||
|
|
||||||
connect(transactionsBtn, &QPushButton::clicked, this, [this, account]() {
|
connect(transactionsBtn, &QPushButton::clicked, this, [this, account]() {
|
||||||
auto *txWindow = new TransactionWindow(account.id, this);
|
auto *txWindow = new TransactionWindow(account.id, this);
|
||||||
|
|||||||
@ -3,19 +3,19 @@
|
|||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
class AccountSettingsWindow final : public QWidget {
|
class AccountSettingsWindow final : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AccountSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app);
|
AccountSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_deviceId;
|
QString m_deviceId;
|
||||||
QString m_deviceName;
|
QString m_deviceName;
|
||||||
ApplicationInfo m_applicationInfo;
|
BankProfileInfo m_applicationInfo;
|
||||||
|
|
||||||
QLabel *m_statusLabel;
|
QLabel *m_statusLabel;
|
||||||
QLabel *m_commentLabel;
|
QLabel *m_commentLabel;
|
||||||
@ -33,5 +33,5 @@ private:
|
|||||||
|
|
||||||
void sendAppStatus(const QString &deviceId, const QString &code, const QString &status);
|
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 <QScrollArea>
|
||||||
|
|
||||||
#include "TransactionWindow.h"
|
#include "TransactionWindow.h"
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
|
|
||||||
AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
||||||
@ -30,7 +30,7 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
|||||||
QList<DeviceInfo> devices = DeviceDAO::getAllDevices(false);
|
QList<DeviceInfo> devices = DeviceDAO::getAllDevices(false);
|
||||||
for (int i = 0; i < devices.size(); ++i) {
|
for (int i = 0; i < devices.size(); ++i) {
|
||||||
DeviceInfo device = devices[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);
|
QString title = QString("%1 [%2] | %3").arg(device.name, device.id, device.status);
|
||||||
QLabel *label = new QLabel(title, this);
|
QLabel *label = new QLabel(title, this);
|
||||||
@ -54,14 +54,14 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
|||||||
|
|
||||||
|
|
||||||
for (int k = 0; k < accounts.size(); ++k) {
|
for (int k = 0; k < accounts.size(); ++k) {
|
||||||
AccountInfo account = accounts[k];
|
MaterialInfo account = accounts[k];
|
||||||
QTableWidgetItem *item = new QTableWidgetItem("*" + account.lastNumbers);
|
QTableWidgetItem *item = new QTableWidgetItem("*" + account.lastNumbers);
|
||||||
item->setData(Qt::UserRole, account.id);
|
item->setData(Qt::UserRole, account.id);
|
||||||
tableWidget->setItem(k, 0, item);
|
tableWidget->setItem(k, 0, item);
|
||||||
tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount)));
|
tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount)));
|
||||||
tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss")));
|
tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss")));
|
||||||
tableWidget->setItem(k, 3, new QTableWidgetItem(account.description));
|
tableWidget->setItem(k, 3, new QTableWidgetItem(account.description));
|
||||||
tableWidget->setItem(k, 4, new QTableWidgetItem(accountStatusToString(account.status)));
|
tableWidget->setItem(k, 4, new QTableWidgetItem(materialStatusToString(account.status)));
|
||||||
totalHeight += tableWidget->rowHeight(k);
|
totalHeight += tableWidget->rowHeight(k);
|
||||||
}
|
}
|
||||||
tableWidget->setFixedHeight(totalHeight + 2);
|
tableWidget->setFixedHeight(totalHeight + 2);
|
||||||
|
|||||||
@ -15,8 +15,9 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "dao/EventDAO.h"
|
||||||
|
#include "db/BankProfileInfo.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "common/PaddedItemDelegate.h"
|
#include "common/PaddedItemDelegate.h"
|
||||||
#include "device/EditBankDialog.h"
|
#include "device/EditBankDialog.h"
|
||||||
@ -29,7 +30,7 @@ QTableWidgetItem *BankSettingsWindow::createTableWidget(const QString &text) {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString parseStatus(const ApplicationInfo &app) {
|
QString parseStatus(const BankProfileInfo &app) {
|
||||||
if (!app.install) {
|
if (!app.install) {
|
||||||
return "не установлен";
|
return "не установлен";
|
||||||
}
|
}
|
||||||
@ -47,11 +48,22 @@ QString parseStatus(const ApplicationInfo &app) {
|
|||||||
|
|
||||||
void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
||||||
QThread *thread = new QThread(nullptr);
|
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") {
|
if (appCode == "ozon") {
|
||||||
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
auto *worker = new Ozon::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||||
worker->moveToThread(thread);
|
worker->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start);
|
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, thread, &QThread::quit);
|
||||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||||
connect(thread, &QThread::finished, thread, &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);
|
auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr);
|
||||||
worker->moveToThread(thread);
|
worker->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start);
|
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, thread, &QThread::quit);
|
||||||
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||||
@ -70,13 +83,45 @@ void BankSettingsWindow::startCheckPinCode(const QString &appCode) {
|
|||||||
thread->start();
|
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) {
|
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()) {
|
if (app.bankProfileId.isEmpty()) {
|
||||||
qWarning() << "[BankSettings] sendAppStatus: no bankProfileId for" << code;
|
qWarning() << "[BankSettings] sendAppStatus: no bankProfileId for" << code;
|
||||||
return;
|
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");
|
const bool enable = (status == "active");
|
||||||
auto *thread = new QThread;
|
auto *thread = new QThread;
|
||||||
auto *net = new NetworkService;
|
auto *net = new NetworkService;
|
||||||
@ -91,15 +136,16 @@ void sendAppStatus(const QString &deviceId, const QString &code, const QString &
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||||
QList<ApplicationInfo> applications = ApplicationDAO::getApplicationsByDeviceId(m_deviceId);
|
QList<BankProfileInfo> applications = BankProfileDAO::getApplicationsByDeviceId(m_deviceId);
|
||||||
|
|
||||||
tableWidget->setRowCount(applications.length());
|
tableWidget->setRowCount(applications.length());
|
||||||
tableWidget->setColumnCount(7);
|
tableWidget->setColumnCount(8);
|
||||||
tableWidget->setHorizontalHeaderLabels({
|
tableWidget->setHorizontalHeaderLabels({
|
||||||
"",
|
"",
|
||||||
"Имя",
|
"Имя",
|
||||||
"Пин-код",
|
"Пин-код",
|
||||||
"Статус",
|
"Статус",
|
||||||
|
"Сервер",
|
||||||
"Комментарий",
|
"Комментарий",
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
@ -109,17 +155,18 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
tableWidget->setTextElideMode(Qt::ElideNone);
|
tableWidget->setTextElideMode(Qt::ElideNone);
|
||||||
|
|
||||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
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);
|
tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < applications.length(); ++i) {
|
for (int i = 0; i < applications.length(); ++i) {
|
||||||
tableWidget->verticalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
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) {
|
for (int k = 0; k < applications.size(); ++k) {
|
||||||
const ApplicationInfo &app = applications[k];
|
const BankProfileInfo &app = applications[k];
|
||||||
|
|
||||||
|
|
||||||
auto *icon = new QLabel(tableWidget);
|
auto *icon = new QLabel(tableWidget);
|
||||||
@ -151,20 +198,23 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
|
|
||||||
tableWidget->setItem(k, 3, createTableWidget(parseStatus(app)));
|
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);
|
QTableWidgetItem *item = new QTableWidgetItem(app.comment);
|
||||||
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
// item->setSizeHint(QSize(100, app.comment.length() / 2)); // Минимальная высота ячейки для переноса
|
tableWidget->setItem(k, 5, item);
|
||||||
tableWidget->setItem(k, 4, item);
|
|
||||||
|
|
||||||
|
|
||||||
if (app.install) {
|
if (app.install) {
|
||||||
if (!app.pinCode.isEmpty() && !app.pinCodeChecked) {
|
if (!app.pinCode.isEmpty() && !app.pinCodeChecked) {
|
||||||
auto *pinCodeBtn = new QPushButton(" Проверить пин-код ", tableWidget);
|
auto *pinCodeBtn = new QPushButton(" Проверить пин-код ", tableWidget);
|
||||||
pinCodeBtn->setProperty("btnClass", "pincode");
|
pinCodeBtn->setProperty("btnClass", "pincode");
|
||||||
tableWidget->setCellWidget(k, 5, pinCodeBtn);
|
tableWidget->setCellWidget(k, 6, pinCodeBtn);
|
||||||
|
|
||||||
connect(pinCodeBtn, &QPushButton::clicked, this, [this, app]() {
|
connect(pinCodeBtn, &QPushButton::clicked, this, [this, app]() {
|
||||||
QMessageBox msgBox(this);
|
QMessageBox msgBox(this);
|
||||||
@ -187,7 +237,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
deleteBtn->setProperty("btnClass", "delete");
|
deleteBtn->setProperty("btnClass", "delete");
|
||||||
// deleteBtn->setStyleSheet("padding: 4px 8px;");
|
// deleteBtn->setStyleSheet("padding: 4px 8px;");
|
||||||
// deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
// deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||||
tableWidget->setCellWidget(k, 5, deleteBtn);
|
tableWidget->setCellWidget(k, 6, deleteBtn);
|
||||||
|
|
||||||
connect(deleteBtn, &QPushButton::clicked, this, [this,tableWidget, app]() {
|
connect(deleteBtn, &QPushButton::clicked, this, [this,tableWidget, app]() {
|
||||||
QMessageBox msgBox(this);
|
QMessageBox msgBox(this);
|
||||||
@ -201,7 +251,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
|
|
||||||
if (msgBox.clickedButton() == deleteBtn) {
|
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";
|
qDebug() << "Cant update bank data";
|
||||||
} else {
|
} else {
|
||||||
sendAppStatus(app.deviceId, app.code, "off");
|
sendAppStatus(app.deviceId, app.code, "off");
|
||||||
@ -222,7 +272,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
auto *btn = new QPushButton(" Редактировать ", tableWidget);
|
auto *btn = new QPushButton(" Редактировать ", tableWidget);
|
||||||
btn->setProperty("btnClass", "edit");
|
btn->setProperty("btnClass", "edit");
|
||||||
// btn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
// 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]() {
|
connect(btn, &QPushButton::clicked, this, [this, tableWidget, k,app]() {
|
||||||
BankEditDialog dlg(app, this);
|
BankEditDialog dlg(app, this);
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
@ -230,7 +280,7 @@ void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
const QString comment = dlg.secondValue();
|
const QString comment = dlg.secondValue();
|
||||||
bool on = dlg.toggled();
|
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";
|
qDebug() << "Cant update bank data";
|
||||||
} else {
|
} else {
|
||||||
sendAppStatus(app.deviceId, app.code, on ? "active" : "off");
|
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,
|
: QWidget(parent,
|
||||||
Qt::Window
|
Qt::Window
|
||||||
| Qt::WindowTitleHint
|
| Qt::WindowTitleHint
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <qtablewidget.h>
|
#include <qtablewidget.h>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
class BankSettingsWindow final : public QWidget {
|
class BankSettingsWindow final : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const ApplicationInfo &app);
|
BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_deviceId;
|
QString m_deviceId;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
#include "dao/AccountDAO.h"
|
#include "dao/MaterialDAO.h"
|
||||||
#include "dao/TransactionDAO.h"
|
#include "dao/TransactionDAO.h"
|
||||||
|
|
||||||
class AmountDelegate : public QStyledItemDelegate {
|
class AmountDelegate : public QStyledItemDelegate {
|
||||||
@ -67,7 +67,7 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
|||||||
scrollArea->setStyleSheet("border: none;"); // Убираем рамку
|
scrollArea->setStyleSheet("border: none;"); // Убираем рамку
|
||||||
layout->setAlignment(Qt::AlignTop);
|
layout->setAlignment(Qt::AlignTop);
|
||||||
|
|
||||||
AccountInfo account = AccountDAO::getAccountById(accountId);
|
MaterialInfo account = MaterialDAO::getAccountById(accountId);
|
||||||
QList<TransactionInfo> transactions = TransactionDAO::getTransactions(accountId);
|
QList<TransactionInfo> transactions = TransactionDAO::getTransactions(accountId);
|
||||||
|
|
||||||
QTableWidget *tableWidget = new QTableWidget(this);
|
QTableWidget *tableWidget = new QTableWidget(this);
|
||||||
|
|||||||
@ -7,11 +7,12 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
||||||
|
#include <QSettings>
|
||||||
#include "bank/AccountSettingsWindow.h"
|
#include "bank/AccountSettingsWindow.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
static QString formatStatus(const ApplicationInfo &app) {
|
static QString formatStatus(const BankProfileInfo &app) {
|
||||||
if (!app.install) return "не установлен";
|
if (!app.install) return "не установлен";
|
||||||
if (app.status == "active") {
|
if (app.status == "active") {
|
||||||
if (!app.pinCodeChecked)
|
if (!app.pinCodeChecked)
|
||||||
@ -63,7 +64,29 @@ DeviceSettingsWindow::DeviceSettingsWindow(QWidget *parent, const QString &devic
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeviceSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
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->clearContents();
|
||||||
tableWidget->setRowCount(apps.size());
|
tableWidget->setRowCount(apps.size());
|
||||||
@ -76,7 +99,7 @@ void DeviceSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
|||||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
|
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
|
||||||
|
|
||||||
for (int k = 0; k < apps.size(); ++k) {
|
for (int k = 0; k < apps.size(); ++k) {
|
||||||
const ApplicationInfo &app = apps[k];
|
const BankProfileInfo &app = apps[k];
|
||||||
|
|
||||||
// Иконка
|
// Иконка
|
||||||
auto *icon = new QLabel(tableWidget);
|
auto *icon = new QLabel(tableWidget);
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
#include <QGraphicsScene>
|
#include <QGraphicsScene>
|
||||||
@ -13,14 +15,15 @@
|
|||||||
#include "bank/AccountSettingsWindow.h"
|
#include "bank/AccountSettingsWindow.h"
|
||||||
#include "bank/BankSettingsWindow.h"
|
#include "bank/BankSettingsWindow.h"
|
||||||
#include "DeviceSettingsWindow.h"
|
#include "DeviceSettingsWindow.h"
|
||||||
#include "dao/ApplicationDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "dao/EventDAO.h"
|
||||||
|
#include "db/BankProfileInfo.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
|
|
||||||
QString getColorByStatus(const ApplicationInfo &app) {
|
QString getColorByStatus(const BankProfileInfo &app) {
|
||||||
if (!app.install) {
|
if (!app.install) {
|
||||||
return "black";
|
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) {
|
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()) {
|
if (app.bankProfileId.isEmpty()) {
|
||||||
qWarning() << "[DeviceWidget] sendBankStatus: no bankProfileId for" << code;
|
qWarning() << "[DeviceWidget] sendBankStatus: no bankProfileId for" << code;
|
||||||
return;
|
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");
|
const bool enable = (status == "active");
|
||||||
auto *thread = new QThread;
|
auto *thread = new QThread;
|
||||||
auto *net = new NetworkService;
|
auto *net = new NetworkService;
|
||||||
@ -56,7 +90,7 @@ void sendBankStatus(const QString &deviceId, const QString &code, const QString
|
|||||||
thread->start();
|
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->setContentsMargins(0, 0, 0, 0);
|
||||||
rowWidget->setStyleSheet(
|
rowWidget->setStyleSheet(
|
||||||
"background-color: rgba(0, 0, 0, 0.6);" // полупрозрачный фон
|
"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(
|
label->setStyleSheet(
|
||||||
"background-color: transparent;"
|
"background-color: transparent;"
|
||||||
"margin: 0px;"
|
"margin: 0px;"
|
||||||
@ -129,7 +172,7 @@ void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget,
|
|||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
|
|
||||||
if (msgBox.clickedButton() == confirmBtn) {
|
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";
|
qDebug() << "Cant update bank data";
|
||||||
} else {
|
} else {
|
||||||
sendBankStatus(app.deviceId, app.code, newStatus);
|
sendBankStatus(app.deviceId, app.code, newStatus);
|
||||||
@ -175,7 +218,7 @@ void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget,
|
|||||||
// hbox->addStretch();
|
// hbox->addStretch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceWidget::setOpenBanksSettings(QPushButton *button, const ApplicationInfo &app) {
|
void DeviceWidget::setOpenBanksSettings(QPushButton *button, const BankProfileInfo &app) {
|
||||||
QString deviceId = m_device.id;
|
QString deviceId = m_device.id;
|
||||||
QString deviceName = m_device.name;
|
QString deviceName = m_device.name;
|
||||||
connect(button, &QPushButton::clicked, this, [deviceId, deviceName, app]() {
|
connect(button, &QPushButton::clicked, this, [deviceId, deviceName, app]() {
|
||||||
@ -219,6 +262,7 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
|||||||
statusLabel->setStyleSheet(
|
statusLabel->setStyleSheet(
|
||||||
"background-color: rgba(0, 0, 0, 0.5);"
|
"background-color: rgba(0, 0, 0, 0.5);"
|
||||||
"color: white;"
|
"color: white;"
|
||||||
|
"font-size: 10px;"
|
||||||
);
|
);
|
||||||
layout->addWidget(statusLabel, 0, 0, Qt::AlignTop | Qt::AlignLeft);
|
layout->addWidget(statusLabel, 0, 0, Qt::AlignTop | Qt::AlignLeft);
|
||||||
|
|
||||||
@ -274,7 +318,15 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
|||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
|
|
||||||
if (msgBox.clickedButton() == confirmBtn) {
|
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";
|
const bool isOffline = device.status != "device";
|
||||||
|
|
||||||
QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(device.id);
|
QList<BankProfileInfo> apps = BankProfileDAO::getApplicationsByDeviceId(device.id);
|
||||||
for (ApplicationInfo &app: apps) {
|
for (BankProfileInfo &app: apps) {
|
||||||
if (app.install) {
|
if (app.install) {
|
||||||
auto *rowWidget = new QWidget(appsView);
|
auto *rowWidget = new QWidget(appsView);
|
||||||
buildBankRow(app, rowWidget, appsView, isOffline);
|
buildBankRow(app, rowWidget, appsView, isOffline);
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
#include <qtablewidget.h>
|
#include <qtablewidget.h>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
#include "db/DeviceInfo.h"
|
#include "db/DeviceInfo.h"
|
||||||
|
|
||||||
class DeviceWidget final : public QWidget {
|
class DeviceWidget final : public QWidget {
|
||||||
@ -15,9 +15,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
const DeviceInfo m_device;
|
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:
|
signals:
|
||||||
void deleteRequested(const QString &deviceId);
|
void deleteRequested(const QString &deviceId);
|
||||||
|
|||||||
@ -7,13 +7,13 @@
|
|||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "db/AccountInfo.h"
|
#include "db/MaterialInfo.h"
|
||||||
|
|
||||||
class EditBankAccountDialog final : public QDialog {
|
class EditBankAccountDialog final : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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) {
|
if (account.id > 0) {
|
||||||
setWindowTitle("Настройка аккаунта *" + account.lastNumbers);
|
setWindowTitle("Настройка аккаунта *" + account.lastNumbers);
|
||||||
} else {
|
} else {
|
||||||
@ -36,7 +36,7 @@ public:
|
|||||||
currency->setCurrentText(account.currency);
|
currency->setCurrentText(account.currency);
|
||||||
|
|
||||||
toggle = new QCheckBox(tr("В работе"), this);
|
toggle = new QCheckBox(tr("В работе"), this);
|
||||||
toggle->setChecked(account.status == AccountStatus::Active);
|
toggle->setChecked(account.status == MaterialStatus::Active);
|
||||||
|
|
||||||
form->addRow(tr("Номер карты:"), cardNumber);
|
form->addRow(tr("Номер карты:"), cardNumber);
|
||||||
form->addRow(tr("Валюта:"), currency);
|
form->addRow(tr("Валюта:"), currency);
|
||||||
|
|||||||
@ -5,13 +5,13 @@
|
|||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <qtextedit.h>
|
#include <qtextedit.h>
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/BankProfileInfo.h"
|
||||||
|
|
||||||
class BankEditDialog final : public QDialog {
|
class BankEditDialog final : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BankEditDialog(const ApplicationInfo &app, QWidget *parent = nullptr) : QDialog(parent) {
|
explicit BankEditDialog(const BankProfileInfo &app, QWidget *parent = nullptr) : QDialog(parent) {
|
||||||
setWindowTitle("Настройка " + app.name);
|
setWindowTitle("Настройка " + app.name);
|
||||||
setModal(true);
|
setModal(true);
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ static QString typeDisplayName(EventType t) {
|
|||||||
|
|
||||||
EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
||||||
setWindowTitle("События");
|
setWindowTitle("События");
|
||||||
resize(1400, 600);
|
resize(1400, 700);
|
||||||
|
|
||||||
// ── Таблица ───────────────────────────────────────────────────────────────
|
// ── Таблица ───────────────────────────────────────────────────────────────
|
||||||
m_table = new QTableWidget(this);
|
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_SENT, 45);
|
||||||
m_table->setColumnWidth(COL_ACTION, 80);
|
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_prevBtn = new QPushButton("←", this);
|
||||||
m_nextBtn = new QPushButton("→", this);
|
m_nextBtn = new QPushButton("→", this);
|
||||||
@ -108,7 +121,7 @@ EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
|||||||
paginationLayout->addWidget(refreshBtn);
|
paginationLayout->addWidget(refreshBtn);
|
||||||
|
|
||||||
auto *mainLayout = new QVBoxLayout(this);
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
mainLayout->addWidget(m_table, 1);
|
mainLayout->addWidget(splitter, 1);
|
||||||
mainLayout->addLayout(paginationLayout);
|
mainLayout->addLayout(paginationLayout);
|
||||||
|
|
||||||
// ── Сигналы ───────────────────────────────────────────────────────────────
|
// ── Сигналы ───────────────────────────────────────────────────────────────
|
||||||
@ -122,6 +135,11 @@ EventWindow::EventWindow(QWidget *parent) : QDialog(parent) {
|
|||||||
m_page = 0;
|
m_page = 0;
|
||||||
loadPage();
|
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) {
|
connect(m_table, &QTableWidget::cellDoubleClicked, this, [this](int row, int col) {
|
||||||
if (col != COL_JSON) return;
|
if (col != COL_JSON) return;
|
||||||
auto *item = m_table->item(row, col);
|
auto *item = m_table->item(row, col);
|
||||||
@ -150,9 +168,11 @@ void EventWindow::loadPage() {
|
|||||||
m_page = qBound(0, m_page, m_totalPages - 1);
|
m_page = qBound(0, m_page, m_totalPages - 1);
|
||||||
|
|
||||||
const QList<EventInfo> events = EventDAO::getEvents(m_page, m_pageSize);
|
const QList<EventInfo> events = EventDAO::getEvents(m_page, m_pageSize);
|
||||||
|
m_eventIds.clear();
|
||||||
|
|
||||||
m_table->setRowCount(static_cast<int>(events.size()));
|
m_table->setRowCount(static_cast<int>(events.size()));
|
||||||
for (int i = 0; i < events.size(); ++i) {
|
for (int i = 0; i < events.size(); ++i) {
|
||||||
|
m_eventIds.append(events[i].id);
|
||||||
const auto &e = events[i];
|
const auto &e = events[i];
|
||||||
m_table->setItem(i, COL_ID, new QTableWidgetItem(QString::number(e.id)));
|
m_table->setItem(i, COL_ID, new QTableWidgetItem(QString::number(e.id)));
|
||||||
m_table->setItem(i, COL_TIME, new QTableWidgetItem(
|
m_table->setItem(i, COL_TIME, new QTableWidgetItem(
|
||||||
@ -204,6 +224,24 @@ void EventWindow::loadPage() {
|
|||||||
updatePagination();
|
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() {
|
void EventWindow::updatePagination() {
|
||||||
m_pageLabel->setText(QString("Страница %1 из %2").arg(m_page + 1).arg(m_totalPages));
|
m_pageLabel->setText(QString("Страница %1 из %2").arg(m_page + 1).arg(m_totalPages));
|
||||||
m_prevBtn->setEnabled(m_page > 0);
|
m_prevBtn->setEnabled(m_page > 0);
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QSplitter>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
|
|
||||||
class EventWindow final : public QDialog {
|
class EventWindow final : public QDialog {
|
||||||
@ -13,6 +14,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
QTableWidget *m_table;
|
QTableWidget *m_table;
|
||||||
QLabel *m_pageLabel;
|
QLabel *m_pageLabel;
|
||||||
|
QLabel *m_screenshotLabel;
|
||||||
QPushButton *m_prevBtn;
|
QPushButton *m_prevBtn;
|
||||||
QPushButton *m_nextBtn;
|
QPushButton *m_nextBtn;
|
||||||
|
|
||||||
@ -20,6 +22,10 @@ private:
|
|||||||
int m_pageSize = 50;
|
int m_pageSize = 50;
|
||||||
int m_totalPages = 1;
|
int m_totalPages = 1;
|
||||||
|
|
||||||
|
// Кешируем event id текущей страницы для загрузки скриншота по клику
|
||||||
|
QList<int> m_eventIds;
|
||||||
|
|
||||||
void loadPage();
|
void loadPage();
|
||||||
void updatePagination();
|
void updatePagination();
|
||||||
|
void showScreenshot(int eventId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,21 @@
|
|||||||
#include "FileLogWindow.h"
|
#include "FileLogWindow.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QHttpMultiPart>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QTemporaryFile>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "dao/EventDAO.h"
|
||||||
#include "dao/GeneralLogDAO.h"
|
#include "dao/GeneralLogDAO.h"
|
||||||
|
#include "dao/SettingsDAO.h"
|
||||||
|
|
||||||
static constexpr int COL_ID = 0;
|
static constexpr int COL_ID = 0;
|
||||||
static constexpr int COL_TIME = 1;
|
static constexpr int COL_TIME = 1;
|
||||||
@ -38,6 +49,8 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
|||||||
m_pageLabel->setMinimumWidth(140);
|
m_pageLabel->setMinimumWidth(140);
|
||||||
|
|
||||||
auto *refreshBtn = new QPushButton("Обновить", this);
|
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;
|
auto *paginationLayout = new QHBoxLayout;
|
||||||
paginationLayout->addWidget(m_prevBtn);
|
paginationLayout->addWidget(m_prevBtn);
|
||||||
@ -46,6 +59,7 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
|||||||
paginationLayout->addStretch();
|
paginationLayout->addStretch();
|
||||||
paginationLayout->addWidget(m_showAllCheck);
|
paginationLayout->addWidget(m_showAllCheck);
|
||||||
paginationLayout->addWidget(refreshBtn);
|
paginationLayout->addWidget(refreshBtn);
|
||||||
|
paginationLayout->addWidget(m_sendBtn);
|
||||||
|
|
||||||
auto *mainLayout = new QVBoxLayout(this);
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
mainLayout->addWidget(m_table, 1);
|
mainLayout->addWidget(m_table, 1);
|
||||||
@ -65,6 +79,7 @@ FileLogWindow::FileLogWindow(QWidget *parent) : QDialog(parent) {
|
|||||||
m_page = 0;
|
m_page = 0;
|
||||||
loadPage();
|
loadPage();
|
||||||
});
|
});
|
||||||
|
connect(m_sendBtn, &QPushButton::clicked, this, &FileLogWindow::sendLogToTelegram);
|
||||||
|
|
||||||
loadPage();
|
loadPage();
|
||||||
}
|
}
|
||||||
@ -102,3 +117,141 @@ void FileLogWindow::updatePagination() {
|
|||||||
m_prevBtn->setEnabled(m_page > 0);
|
m_prevBtn->setEnabled(m_page > 0);
|
||||||
m_nextBtn->setEnabled(m_page < m_totalPages - 1);
|
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;
|
QLabel *m_pageLabel;
|
||||||
QPushButton *m_prevBtn;
|
QPushButton *m_prevBtn;
|
||||||
QPushButton *m_nextBtn;
|
QPushButton *m_nextBtn;
|
||||||
|
QPushButton *m_sendBtn;
|
||||||
QCheckBox *m_showAllCheck;
|
QCheckBox *m_showAllCheck;
|
||||||
|
|
||||||
int m_page = 0;
|
int m_page = 0;
|
||||||
@ -25,4 +26,5 @@ private:
|
|||||||
QStringList currentLevelFilter() const;
|
QStringList currentLevelFilter() const;
|
||||||
void loadPage();
|
void loadPage();
|
||||||
void updatePagination();
|
void updatePagination();
|
||||||
|
void sendLogToTelegram();
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user