1
0
forked from BRT/arc
arc/database/DatabaseManager.cpp
slava 8eedd07ffd Add database migration, DAO enhancements, and OCR-based transaction handling updates
Implemented database migration for adding new fields (`json`, `sent_to_server`) in `events` table. Updated DAO methods to support the new fields and introduced `markSentToServer`. Enhanced Ozon transaction workflows with OCR-based extraction, event error handling improvements, and result reporting. Refactored `EventHandler` for streamlined task processing and stale event cleanup.
2026-03-08 14:20:12 +07:00

384 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "DatabaseManager.h"
#include <QCoreApplication>
#include <QSqlError>
#include <QSqlQuery>
#include <QDebug>
#include <QDir>
#include <QSettings>
#include <QThread>
DatabaseManager::DatabaseManager() = default;
DatabaseManager::~DatabaseManager() {
QMutexLocker locker(&mutex);
// QSqlDatabase требует живого QCoreApplication — при завершении
// статический синглтон может разрушаться после QApplication
if (!QCoreApplication::instance()) {
connectionPool.clear();
return;
}
for (auto it = connectionPool.begin(); it != connectionPool.end(); ++it) {
QSqlDatabase::removeDatabase(it.key());
}
connectionPool.clear();
qDebug() << "Все соединения закрыты";
}
DatabaseManager &DatabaseManager::instance() {
static DatabaseManager instance;
return instance;
}
QSqlDatabase DatabaseManager::database() {
QMutexLocker locker(&mutex);
QString name = connectionName();
// Создаем новое соединение, если его ещё нет
if (!connectionPool.contains(name)) {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", name);
const QSettings settings("config.ini", QSettings::IniFormat);
QString dbPath = settings.value("db/dbPath").toString();
if (dbPath.startsWith("~")) {
dbPath.replace(0, 1, QDir::homePath());
}
// qDebug() << settings.value("db/dbPath").toString();
// db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
db.setDatabaseName(dbPath);
if (!db.open()) {
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
}
connectionPool[name] = db;
// Автоматическое удаление соединения при завершении потока
QObject::connect(QThread::currentThread(), &QThread::finished, [this, name]() {
QMutexLocker cLocker(&mutex);
if (connectionPool.contains(name)) {
connectionPool.remove(name);
QSqlDatabase::removeDatabase(name);
qDebug() << "Соединение" << name << "удалено";
}
});
}
return connectionPool[name];
}
QString DatabaseManager::connectionName() {
return QString("connection_%1").arg(reinterpret_cast<quintptr>(QThread::currentThreadId()));
}
static bool columnExists(QSqlQuery &q, const QString &table, const QString &column) {
q.exec(QString("SELECT COUNT(*) FROM pragma_table_info('%1') WHERE name='%2'")
.arg(table, column));
return q.next() && q.value(0).toInt() > 0;
}
void DatabaseManager::initSchema() {
QSqlDatabase db = database();
QSqlQuery q(db);
// Читаем текущую версию схемы
q.exec("PRAGMA user_version");
const int version = q.next() ? q.value(0).toInt() : 0;
// ── Версия 1: начальная схема + api_id для devices ────────────────────────
if (version < 1) {
db.transaction();
q.exec(R"(CREATE TABLE IF NOT EXISTS devices (
id TEXT PRIMARY KEY,
api_id TEXT,
status TEXT,
name TEXT,
android TEXT,
screen_width INTEGER,
screen_height INTEGER,
battery INTEGER,
update_time DATETIME,
timestamp DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
image BLOB
))");
q.exec(R"(CREATE TABLE IF NOT EXISTS applications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
pin_code TEXT,
pin_code_checked TEXT DEFAULT 'no',
name TEXT,
code TEXT,
package TEXT,
status TEXT DEFAULT 'off',
install TEXT DEFAULT 'no',
comment TEXT,
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
UNIQUE (device_id, code)
))");
q.exec(R"(CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
app_code TEXT,
last_numbers TEXT,
description TEXT,
amount REAL,
currency TEXT DEFAULT '',
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
status TEXT DEFAULT 'active',
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
UNIQUE (device_id, app_code, last_numbers)
))");
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER,
external_id INTEGER,
amount REAL,
fee REAL,
status TEXT,
type TEXT,
phone TEXT,
name TEXT,
short_description TEXT,
updated_short_description TEXT,
description TEXT,
updated_description TEXT,
bank_name TEXT,
bank_time TEXT,
bank_tr_external_id TEXT,
update_time DATETIME,
complete_time DATETIME,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
))");
q.exec(R"(CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER,
external_id INTEGER,
device_id TEXT,
amount REAL,
phone TEXT,
bank_name TEXT,
comment TEXT,
status TEXT,
type TEXT,
update_time DATETIME,
complete_time DATETIME,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
UNIQUE (external_id)
))");
// Добавляем api_id в devices если ещё нет (для существующих БД)
if (!columnExists(q, "devices", "api_id")) {
q.exec("ALTER TABLE devices ADD COLUMN api_id TEXT");
}
db.commit();
q.exec("PRAGMA user_version = 1");
qDebug() << "[DB] Migration 1 applied";
}
// ── Версия 2: currency в accounts + переименование app_name → app_code ───
if (version < 2) {
db.transaction();
// Добавляем currency если ещё нет (существующие БД без этого поля)
if (!columnExists(q, "accounts", "currency")) {
q.exec("ALTER TABLE accounts ADD COLUMN currency TEXT DEFAULT ''");
}
// Переименовываем app_name → app_code (SQLite 3.25+)
if (columnExists(q, "accounts", "app_name")) {
q.exec("ALTER TABLE accounts RENAME COLUMN app_name TO app_code");
if (q.lastError().isValid()) {
qWarning() << "[DB] Migration 2: rename app_name failed:" << q.lastError().text();
}
}
db.commit();
q.exec("PRAGMA user_version = 2");
qDebug() << "[DB] Migration 2 applied";
}
// ── Версия 3: phone и full_name в applications ────────────────────────────
if (version < 3) {
db.transaction();
if (!columnExists(q, "applications", "phone")) {
q.exec("ALTER TABLE applications ADD COLUMN phone TEXT DEFAULT ''");
}
if (!columnExists(q, "applications", "full_name")) {
q.exec("ALTER TABLE applications ADD COLUMN full_name TEXT DEFAULT ''");
}
db.commit();
q.exec("PRAGMA user_version = 3");
qDebug() << "[DB] Migration 3 applied";
}
// ── Версия 4: card_number в accounts ──────────────────────────────────────
if (version < 4) {
db.transaction();
if (!columnExists(q, "accounts", "card_number")) {
q.exec("ALTER TABLE accounts ADD COLUMN card_number TEXT DEFAULT ''");
}
db.commit();
q.exec("PRAGMA user_version = 4");
qDebug() << "[DB] Migration 4 applied";
}
// ── Версия 5: bank_profile_id в applications ──────────────────────────────
if (version < 5) {
db.transaction();
if (!columnExists(q, "applications", "bank_profile_id")) {
q.exec("ALTER TABLE applications ADD COLUMN bank_profile_id TEXT DEFAULT ''");
}
db.commit();
q.exec("PRAGMA user_version = 5");
qDebug() << "[DB] Migration 5 applied";
}
// ── Версия 6: currency и amount в applications ─────────────────────────────
if (version < 6) {
db.transaction();
if (!columnExists(q, "applications", "currency")) {
q.exec("ALTER TABLE applications ADD COLUMN currency TEXT DEFAULT ''");
}
if (!columnExists(q, "applications", "amount")) {
q.exec("ALTER TABLE applications ADD COLUMN amount REAL DEFAULT 0");
}
db.commit();
q.exec("PRAGMA user_version = 6");
qDebug() << "[DB] Migration 6 applied";
}
// ── Версия 7: material_id в accounts ──────────────────────────────────────
if (version < 7) {
db.transaction();
if (!columnExists(q, "accounts", "material_id")) {
q.exec("ALTER TABLE accounts ADD COLUMN material_id TEXT DEFAULT ''");
}
db.commit();
q.exec("PRAGMA user_version = 7");
qDebug() << "[DB] Migration 7 applied";
}
// ── Версия 8: receipt_image в transactions ──────────────────────────────────
if (version < 8) {
db.transaction();
if (!columnExists(q, "transactions", "receipt_image")) {
q.exec("ALTER TABLE transactions ADD COLUMN receipt_image BLOB");
}
db.commit();
q.exec("PRAGMA user_version = 8");
qDebug() << "[DB] Migration 8 applied";
}
// ── Версия 9: таблица banks ───────────────────────────────────────────────
if (version < 9) {
db.transaction();
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT UNIQUE,
name TEXT,
nspk_name TEXT
))");
db.commit();
q.exec("PRAGMA user_version = 9");
qDebug() << "[DB] Migration 9 applied";
}
// ── Версия 10: таблица app_logs ───────────────────────────────────────────
if (version < 10) {
db.transaction();
q.exec(R"(CREATE TABLE IF NOT EXISTS app_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL DEFAULT '',
device_id TEXT NOT NULL DEFAULT '',
message TEXT NOT NULL,
screenshot BLOB,
timestamp TEXT NOT NULL
))");
q.exec("CREATE INDEX IF NOT EXISTS idx_app_logs_ts ON app_logs (id DESC)");
db.commit();
q.exec("PRAGMA user_version = 10");
qDebug() << "[DB] Migration 10 applied";
}
// ── Версия 11: убираем UNIQUE(external_id) из events ─────────────────────
if (version < 11) {
db.transaction();
q.exec(R"(CREATE TABLE IF NOT EXISTS events_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER,
external_id TEXT,
device_id TEXT,
amount REAL,
phone TEXT,
bank_name TEXT,
comment TEXT,
status TEXT,
type TEXT,
update_time DATETIME,
complete_time DATETIME,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
))");
q.exec(R"(INSERT INTO events_new
SELECT * FROM events)");
q.exec("DROP TABLE events");
q.exec("ALTER TABLE events_new RENAME TO events");
db.commit();
q.exec("PRAGMA user_version = 11");
qDebug() << "[DB] Migration 11 applied";
}
// ── Версия 12: json и sent_to_server в events ────────────────────────────
if (version < 12) {
db.transaction();
q.exec("ALTER TABLE events ADD COLUMN json TEXT DEFAULT ''");
q.exec("ALTER TABLE events ADD COLUMN sent_to_server INTEGER DEFAULT 0");
db.commit();
q.exec("PRAGMA user_version = 12");
qDebug() << "[DB] Migration 12 applied";
}
}
void DatabaseManager::closeConnection() {
QMutexLocker locker(&mutex);
if (const QString name = connectionName(); connectionPool.contains(name)) {
connectionPool.remove(name);
QSqlDatabase::removeDatabase(name);
qDebug() << "Соединение" << name << "удалено вручную";
}
}