1
0
forked from BRT/arc

Add event handling and improved account management

Introduced a new "events" table and corresponding EventDAO for managing event data. Enhanced account handling with device ID and partial card number support. Added a NetworkService method to fetch payments from an API and store them as events.
This commit is contained in:
slava 2025-05-21 20:18:53 +07:00
parent a506bca01d
commit 89ea2ac53a
11 changed files with 318 additions and 42 deletions

View File

@ -244,13 +244,6 @@ void PayByPhoneScript::doStart() {
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
const ApplicationInfo app = ApplicationDAO::getApplicationsByDeviceId(m_account.deviceId, m_account.appName);
for (TransactionInfo &transaction: TransactionDAO::getTransactions(m_account.id)) {
// postNewTransactionData(m_account, transaction);
updateTransactionData(m_account, transaction.id, transaction.status, transaction.updatedDescription, transaction.description);
}
emit finishedWithResult(m_error);
return;
if (device.id.isEmpty() || app.id.isEmpty()) {
qDebug() << "Device or app not found....";
return;

Binary file not shown.

View File

@ -44,6 +44,20 @@ bool AccountDAO::deleteAccount(const int id) {
return query.exec();
}
AccountInfo parseAccount(const QSqlQuery &query) {
AccountInfo acc;
acc.id = query.value("id").toInt();
acc.deviceId = query.value("device_id").toString();
acc.appName = query.value("app_name").toString();
acc.lastNumbers = query.value("last_numbers").toString();
acc.numbers = query.value("numbers").toString();
acc.amount = query.value("amount").toDouble();
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
acc.status = accountStatusFromString(query.value("status").toString());
acc.description = query.value("description").toString();
return acc;
}
QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QString &appName) {
QList<AccountInfo> accounts;
QSqlQuery query(DatabaseManager::instance().database());
@ -63,36 +77,12 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
}
while (query.next()) {
AccountInfo acc;
acc.id = query.value("id").toInt();
acc.deviceId = query.value("device_id").toString();
acc.appName = query.value("app_name").toString();
acc.numbers = query.value("numbers").toString();
acc.lastNumbers = query.value("last_numbers").toString();
acc.amount = query.value("amount").toDouble();
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
acc.status = accountStatusFromString(query.value("status").toString());
acc.description = query.value("description").toString();
accounts.append(acc);
accounts.append(parseAccount(query));
}
return accounts;
}
AccountInfo parseAccount(const QSqlQuery &query) {
AccountInfo acc;
acc.id = query.value("id").toInt();
acc.deviceId = query.value("device_id").toString();
acc.appName = query.value("app_name").toString();
acc.lastNumbers = query.value("last_numbers").toString();
acc.numbers = query.value("numbers").toString();
acc.amount = query.value("amount").toDouble();
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
acc.status = accountStatusFromString(query.value("status").toString());
acc.description = query.value("description").toString();
return acc;
}
AccountInfo AccountDAO::getAccountById(const int accountId) {
QSqlQuery query(DatabaseManager::instance().database());
@ -116,18 +106,19 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
return {};
}
AccountInfo AccountDAO::findAppAccount(const QString &appName, const QString &cardNumber) {
AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &appName, const QString &lastNumber) {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare(R"(
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
FROM accounts
WHERE app_name = :app_name AND numbers = :numbers
WHERE app_name = :appName AND last_numbers = :lastNumber AND device_id = :deviceId
LIMIT 1
)");
query.bindValue(":app_name", appName);
query.bindValue(":numbers", cardNumber);
query.bindValue(":deviceId", deviceId);
query.bindValue(":appName", appName);
query.bindValue(":lastNumber", lastNumber);
if (!query.exec()) {
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();

View File

@ -11,5 +11,9 @@ public:
[[nodiscard]] static AccountInfo getAccountById(int accountId);
[[nodiscard]] static AccountInfo findAppAccount(const QString &appName, const QString &cardNumber);
[[nodiscard]] static AccountInfo findAppAccount(
const QString &deviceId,
const QString &appName,
const QString &lastNumber
);
};

108
database/dao/EventDAO.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "EventDAO.h"
#include <QSqlError>
#include <QSqlQuery>
#include "DatabaseManager.h"
#include "time/DateUtils.h"
EventInfo parseEvent(const QSqlQuery &query) {
EventInfo event;
event.id = query.value("id").toInt();
event.status = eventStatusFromString(query.value("status").toString());
event.externalId = query.value("external_id").toInt();
event.accountId = query.value("account_id").toInt();
event.amount = query.value("amount").toDouble();
event.phone = query.value("phone").toString();
event.bankName = query.value("bank_name").toString();
event.timestamp = DateUtils::fromUtcString(query.value("timestamp").toString());
event.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
event.completeTime = DateUtils::fromUtcString(query.value("complete_time").toString());
return event;
}
int EventDAO::insertEvent(const EventInfo &event) {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare(R"(
INSERT INTO events (
status, external_id, account_id, amount, phone, bank_name, timestamp
) VALUES (
:status, :external_id, :account_id, :amount, :phone, :bank_name, :timestamp
)
)");
query.bindValue(":status", eventStatusToString(event.status));
query.bindValue(":external_id", event.externalId);
query.bindValue(":account_id", event.accountId);
query.bindValue(":amount", event.amount);
query.bindValue(":phone", event.phone);
query.bindValue(":bank_name", event.bankName);
query.bindValue(":timestamp", DateUtils::toUtcString(DateUtils::getUtcNow()));
if (!query.exec()) {
qWarning() << "Ошибка при вставке event:" << query.lastError().text();
return -1;
}
return query.lastInsertId().toInt();
}
bool EventDAO::updateEvent(
const int id,
const EventStatus &status
) {
QSqlQuery query(DatabaseManager::instance().database());
query.prepare(R"(
UPDATE events SET
status = :status,
update_time = :update_time,
complete_time = :complete_time
WHERE id = :id
)");
query.bindValue(":id", id);
query.bindValue(":status", eventStatusToString(status));
if (status == EventStatus::Wait) {
query.bindValue(":update_time", DateUtils::toUtcString(DateUtils::getUtcNow()));
} else {
query.bindValue(":update_time", QVariant(QMetaType(QMetaType::QString)));
}
if (status == EventStatus::Complete) {
query.bindValue(":complete_time", DateUtils::toUtcString(DateUtils::getUtcNow()));
} else {
query.bindValue(":complete_time", QVariant(QMetaType(QMetaType::QString)));
}
if (!query.exec()) {
qWarning() << "Ошибка при обновлении event:" << query.lastError().text();
return false;
}
return true;
}
QList<EventInfo> EventDAO::getAllEvents(const EventStatus &status) {
QList<EventInfo> events;
QSqlQuery query(DatabaseManager::instance().database());
query.prepare(R"(
SELECT id, account_id, external_id, amount, phone, bank_name, status, update_time, complete_time, timestamp
FROM events
WHERE status = :status
)");
query.bindValue(":status", eventStatusToString(status));
if (!query.exec()) {
qWarning() << "Ошибка при получении списка устройств:" << query.lastError().text();
return events;
}
while (query.next()) {
events.append(parseEvent(query));
}
return events;
}

15
database/dao/EventDAO.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <db/EventInfo.h>
class EventDAO {
public:
[[nodiscard]] static int insertEvent(const EventInfo &event);
[[nodiscard]] static bool updateEvent(
int id,
const EventStatus &status
);
[[nodiscard]] static QList<EventInfo> getAllEvents(const EventStatus &status);
};

View File

@ -16,6 +16,7 @@
#include <chrono>
#include <QFile>
#include "net/NetworkService.h"
#include "rshb/GetLastDaysHistoryScript.h"
#include "rshb/HomeScreenScript.h"
#include "rshb/PayByPhoneScript.h"
@ -52,8 +53,9 @@ void setupWorkerAndThread(QCoreApplication &app) {
void setupWorkerAndThreadHistory(QCoreApplication &app) {
const QString appName = "rshb";
const QString cardNumber = "2200380317107183";
AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber);
const QString lastNumber = "7183";
const QString deviceId = "6edc4a47";
AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumber);
auto *thread = new QThread;
auto *deviceScreenerWorker = new GetLastDaysHistoryScript(account);
@ -72,14 +74,15 @@ void setupWorkerAndThreadHistory(QCoreApplication &app) {
void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
const QString appName = "rshb";
const QString cardNumber = "2200380317107183";
const QString lastNumber = "7183";
const QString phoneNumber = "79641815146";
const QString deviceId = "6edc4a47";
const QString bankName = "Альфа-Банк";
// const QString bankName = "Сбербанк";
const double amount = 320;
const AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber);
const AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumber);
if (account.id != -1) {
// Функция для подключения потоков к завершению приложения
auto connectStopOnQuit = [&](QObject *worker, QThread *thread) {
@ -193,7 +196,20 @@ int main(int argc, char *argv[]) {
// Устанавливаем свой обработчик
qInstallMessageHandler(messageHandler);
setupScriptAutoPaymentAndThread(app);
auto *thread = new QThread;
auto *worker = new NetworkService;
// FIXME доставить десктоп ид из настроеек
worker->setDeviceData("DT88bokcwQ", "");
worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, worker, &NetworkService::getPayments);
QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit);
QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
// setupScriptAutoPaymentAndThread(app);
// setupWorkerAndThread(app);
MainWindow window;

View File

@ -67,4 +67,23 @@ CREATE TABLE IF NOT EXISTS transactions
complete_time DATETIME,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS events
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER,
external_id INTEGER,
amount REAL,
phone TEXT,
bank_name TEXT,
status TEXT,
update_time DATETIME,
complete_time DATETIME,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
UNIQUE (external_id)
);

44
models/db/EventInfo.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <QString>
#include <QDateTime>
enum class EventStatus {
Wait,
InProgress,
Complete,
Unknown
};
inline QString eventStatusToString(const EventStatus type) {
switch (type) {
case EventStatus::Wait: return "wait";
case EventStatus::Complete: return "complete";
case EventStatus::InProgress: return "iPprogress";
default: return "unknown";
}
}
inline EventStatus eventStatusFromString(const QString &str) {
if (str == "wait") return EventStatus::Wait;
if (str == "complete") return EventStatus::Complete;
if (str == "iPprogress") return EventStatus::InProgress;
return EventStatus::Unknown;
}
struct EventInfo {
int id = -1;
int externalId = -1;
int accountId = -1;
double amount = 0;
QString phone;
QString bankName;
EventStatus status = EventStatus::Unknown;
QDateTime updateTime;
QDateTime completeTime;
QDateTime timestamp;
};

View File

@ -1,11 +1,16 @@
#include "NetworkService.h"
#include <QEventLoop>
#include <QUrlQuery>
#include <QHttpPart>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonArray>
#include "dao/AccountDAO.h"
#include "dao/EventDAO.h"
#include "dao/TransactionDAO.h"
#include "db/EventInfo.h"
NetworkService::NetworkService(QObject *parent) : QObject(parent) {
}
@ -69,6 +74,47 @@ QJsonValue NetworkService::post(QHttpMultiPart *multiPart, const QString &path)
return result;
}
QJsonArray NetworkService::get(const QString &path, const QMap<QString, QString> &params) {
QNetworkAccessManager manager; // создаём внутри потока
QUrl url(path);
QUrlQuery query;
for (auto it = params.constBegin(); it != params.constEnd(); ++it) {
query.addQueryItem(it.key(), it.value());
}
url.setQuery(query);
const QNetworkRequest request(url);
QNetworkReply *reply = manager.get(request);
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
loop.quit();
});
loop.exec(); // <- здесь и запускается event-loop потока
QJsonArray result;
if (reply->error() == QNetworkReply::NoError) {
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.value("success").toBool()) {
result = root.value("data").toArray();
} else {
qWarning() << "Ошибка:" << root;
}
}
} else {
qWarning() << "Ошибка:" << reply->errorString();
}
reply->deleteLater();
return result;
}
void NetworkService::postAccountsData() {
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
@ -83,6 +129,43 @@ void NetworkService::postAccountsData() {
emit finished();
}
void NetworkService::getPayments() {
QMap<QString, QString> params;
params["desktopUniqueName"] = m_desktopId;
const QJsonArray data = get("http://localhost:9999/api/v1/payments", params);
for (QJsonValue item: data) {
QJsonObject obj = item.toObject();
EventInfo eventInfo;
eventInfo.status = EventStatus::Wait;
eventInfo.externalId = obj["id"].toInt();
eventInfo.amount = obj["amount"].toDouble();
eventInfo.bankName = obj["bankName"].toString();
eventInfo.phone = obj["phone"].toString();
QString deviceId = obj["deviceId"].toString();
QString appName = obj["appName"].toString();
QString lastNumbers = obj["lastNumbers"].toString();
AccountInfo account = AccountDAO::findAppAccount(deviceId, appName, lastNumbers);
eventInfo.accountId = account.id;
int id = EventDAO::insertEvent(eventInfo);
if (id == -1) {
qDebug() << "Cant insert event";
} else {
if (!EventDAO::updateEvent(id, EventStatus::Wait)) {
qDebug() << "Cant update event";
}
}
}
emit finished();
}
void NetworkService::updateTransactionData() {
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

View File

@ -23,6 +23,7 @@ public:
public slots:
void postAccountsData();
void getPayments();
void insertTransactionData();
@ -42,4 +43,6 @@ private:
QJsonValue post(QHttpMultiPart *multiPart, const QString &path);
QJsonArray get(const QString &path, const QMap<QString, QString> &params);
};