#include #include #include #include "MaterialDAO.h" #include "db/MaterialInfo.h" #include "DatabaseManager.h" #include "time/DateUtils.h" bool MaterialDAO::upsertAccount(const MaterialInfo &info) { // Валидация: lastNumbers должен содержать только цифры static const QRegularExpression digitsOnly(R"(^\d+$)"); if (!info.lastNumbers.isEmpty() && !digitsOnly.match(info.lastNumbers).hasMatch()) { qWarning() << "[MaterialDAO] upsertAccount rejected: lastNumbers is not numeric:" << info.lastNumbers << "appCode:" << info.appCode; return false; } QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( INSERT INTO materials (device_id, app_code, last_numbers, card_number, account_number, amount, update_time, status, description, currency, material_id) VALUES (:device_id, :app_code, :last_numbers, :card_number, :account_number, :amount, :update_time, :status, :description, :currency, :material_id) ON CONFLICT(device_id, app_code, last_numbers) DO UPDATE SET amount = excluded.amount, update_time = excluded.update_time, last_numbers = excluded.last_numbers, card_number = CASE WHEN excluded.card_number != '' THEN excluded.card_number ELSE materials.card_number END, account_number = CASE WHEN excluded.account_number != '' THEN excluded.account_number ELSE materials.account_number END, status = excluded.status, description = excluded.description, currency = excluded.currency, material_id = CASE WHEN excluded.material_id != '' THEN excluded.material_id ELSE materials.material_id END )"); query.bindValue(":device_id", info.deviceId); query.bindValue(":app_code", info.appCode); query.bindValue(":last_numbers", info.lastNumbers); query.bindValue(":card_number", info.cardNumber); query.bindValue(":account_number", info.accountNumber); query.bindValue(":currency", info.currency); query.bindValue(":description", info.description); query.bindValue(":amount", info.amount); query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime)); query.bindValue(":status", materialStatusToString(info.status)); query.bindValue(":material_id", info.materialId); if (!query.exec()) { qWarning() << "Ошибка при вставке/обновлении account:" << query.lastError().text(); return false; } return true; } bool MaterialDAO::updateAccountById( const int id, const QString &status, const QString &description, const QString ¤cy ) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( UPDATE materials SET status = :status, description = :description, currency = :currency WHERE id = :id )"); query.bindValue(":status", status); query.bindValue(":description", description); query.bindValue(":currency", currency); query.bindValue(":id", id); if (!query.exec()) { qWarning() << "Ошибка при обновлении account по id:" << query.lastError().text(); return false; } return true; } bool MaterialDAO::updateStatus(const int id, const QString &status) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("UPDATE materials SET status = :status WHERE id = :id"); query.bindValue(":status", status); query.bindValue(":id", id); if (!query.exec()) { qWarning() << "[MaterialDAO] updateStatus failed:" << query.lastError().text(); return false; } return true; } bool MaterialDAO::deleteAccount(const int id) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("DELETE FROM materials WHERE id = :id"); query.bindValue(":id", id); return query.exec(); } bool MaterialDAO::updateCardNumber(const int accountId, const QString &cardNumber) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( UPDATE materials SET card_number = :card_number WHERE id = :id )"); query.bindValue(":card_number", cardNumber); query.bindValue(":id", accountId); if (!query.exec()) { qWarning() << "Ошибка при обновлении card_number:" << query.lastError().text(); return false; } return true; } bool MaterialDAO::updateMaterialId(const int accountId, const QString &materialId) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("UPDATE materials SET material_id = :material_id WHERE id = :id"); query.bindValue(":material_id", materialId); query.bindValue(":id", accountId); if (!query.exec()) { qWarning() << "Ошибка при обновлении material_id:" << query.lastError().text(); return false; } return true; } MaterialInfo parseAccount(const QSqlQuery &query) { MaterialInfo acc; acc.id = query.value("id").toInt(); acc.deviceId = query.value("device_id").toString(); acc.appCode = query.value("app_code").toString(); acc.lastNumbers = query.value("last_numbers").toString(); acc.cardNumber = query.value("card_number").toString(); acc.accountNumber = query.value("account_number").toString(); acc.materialId = query.value("material_id").toString(); acc.amount = query.value("amount").toDouble(); acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString()); acc.status = materialStatusFromString(query.value("status").toString()); acc.description = query.value("description").toString(); acc.currency = query.value("currency").toString(); return acc; } QList MaterialDAO::activateExclusive( const QString &deviceId, const QString &appCode, const int targetId) { QList deactivated; QSqlDatabase db = DatabaseManager::instance().database(); db.transaction(); QSqlQuery sel(db); sel.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE device_id = :device_id AND app_code = :app_code AND status = 'active' AND id != :target_id )"); sel.bindValue(":device_id", deviceId); sel.bindValue(":app_code", appCode); sel.bindValue(":target_id", targetId); if (sel.exec()) { while (sel.next()) deactivated.append(parseAccount(sel)); } else { qWarning() << "[MaterialDAO] activateExclusive select failed:" << sel.lastError().text(); db.rollback(); return {}; } QSqlQuery off(db); off.prepare(R"( UPDATE materials SET status = 'off' WHERE device_id = :device_id AND app_code = :app_code AND id != :target_id )"); off.bindValue(":device_id", deviceId); off.bindValue(":app_code", appCode); off.bindValue(":target_id", targetId); if (!off.exec()) { qWarning() << "[MaterialDAO] activateExclusive off-update failed:" << off.lastError().text(); db.rollback(); return {}; } QSqlQuery on(db); on.prepare("UPDATE materials SET status = 'active' WHERE id = :id"); on.bindValue(":id", targetId); if (!on.exec()) { qWarning() << "[MaterialDAO] activateExclusive on-update failed:" << on.lastError().text(); db.rollback(); return {}; } db.commit(); return deactivated; } QList MaterialDAO::getAccounts(const QString &deviceId, const QString &appName) { QList accounts; QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE device_id = :device_id AND app_code = :app_code ORDER BY id )"); query.bindValue(":device_id", deviceId); query.bindValue(":app_code", appName); if (!query.exec()) { qWarning() << "Ошибка при получении materials:" << query.lastError().text(); return accounts; } while (query.next()) { accounts.append(parseAccount(query)); } return accounts; } MaterialInfo MaterialDAO::getAccountById(const int accountId) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE id = :id LIMIT 1 )"); query.bindValue(":id", accountId); if (!query.exec()) { qWarning() << "Ошибка при получении account по ID:" << query.lastError().text(); return {}; } if (query.next()) { return parseAccount(query); } return {}; } QList MaterialDAO::getAccountsByAppCode(const QString &appCode) { QList accounts; QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE app_code = :app_code )"); query.bindValue(":app_code", appCode); if (!query.exec()) { qWarning() << "Ошибка при получении materials по app_code:" << query.lastError().text(); return accounts; } while (query.next()) { accounts.append(parseAccount(query)); } return accounts; } MaterialInfo MaterialDAO::findAppAccount(const QString &deviceId, const QString &appName, const QString &lastNumber) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE app_code = :appName AND last_numbers = :lastNumber AND device_id = :deviceId LIMIT 1 )"); query.bindValue(":deviceId", deviceId); query.bindValue(":appName", appName); query.bindValue(":lastNumber", lastNumber); if (!query.exec()) { qWarning() << "Ошибка при получении account по ID:" << query.lastError().text(); return {}; } if (query.next()) { return parseAccount(query); } return {}; } QStringList MaterialDAO::getActiveMaterialIds() { QStringList ids; QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT a.material_id FROM materials a JOIN bank_profiles app ON a.device_id = app.device_id AND a.app_code = app.code JOIN devices d ON a.device_id = d.id WHERE app.status = 'active' AND a.material_id IS NOT NULL AND a.material_id != '' AND d.status = 'device' AND d.image IS NOT NULL )"); if (!query.exec()) { qWarning() << "Ошибка при получении active material_ids:" << query.lastError().text(); return ids; } while (query.next()) { ids << query.value(0).toString(); } return ids; } QStringList MaterialDAO::getActiveMaterialIdsExcludingDevices(const QSet &excludeDevices) { const QStringList all = getActiveMaterialIds(); if (excludeDevices.isEmpty()) return all; QStringList filtered; for (const QString &mid : all) { const MaterialInfo acc = findByMaterialId(mid); if (!excludeDevices.contains(acc.deviceId)) { filtered << mid; } } return filtered; } MaterialInfo MaterialDAO::findByMaterialId(const QString &materialId) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( SELECT id, device_id, app_code, last_numbers, card_number, account_number, material_id, amount, update_time, status, description, currency FROM materials WHERE material_id = :material_id LIMIT 1 )"); query.bindValue(":material_id", materialId); if (!query.exec()) { qWarning() << "Ошибка при поиске account по material_id:" << query.lastError().text(); return {}; } if (query.next()) { return parseAccount(query); } return {}; } bool MaterialDAO::migrateDeviceId(const QString &oldDeviceId, const QString &newDeviceId) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("UPDATE materials SET device_id = :new_id WHERE device_id = :old_id"); query.bindValue(":new_id", newDeviceId); query.bindValue(":old_id", oldDeviceId); if (!query.exec()) { qWarning() << "[MaterialDAO] migrateDeviceId failed:" << query.lastError().text(); return false; } qDebug() << "[MaterialDAO] migrated" << query.numRowsAffected() << "materials from" << oldDeviceId << "to" << newDeviceId; return true; } bool MaterialDAO::deleteAccountsByApp(const QString &deviceId, const QString &appCode) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("DELETE FROM materials WHERE device_id = :device_id AND app_code = :app_code AND (material_id IS NULL OR material_id = '')"); query.bindValue(":device_id", deviceId); query.bindValue(":app_code", appCode); if (!query.exec()) { qWarning() << "[MaterialDAO] deleteAccountsByApp failed:" << query.lastError().text(); return false; } return true; }