Add account number parsing and database integration:
- Extend `ScreenXmlParser` with methods for detecting and extracting account numbers from multiple screens. - Update database schema and DAO to support `account_number` field. - Modify card information scripts to collect, save, and utilize account numbers. - Enhance `GetCardInfoScript` logic to fetch account numbers via screen parsing and interaction.
This commit is contained in:
parent
9a7a718d52
commit
377844e94b
@ -157,11 +157,84 @@ void GetCardInfoScript::doStart() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Достаём номер счёта: Настройки карты → Реквизиты карты → таб "Реквизиты счета".
|
||||
QString accountNumber;
|
||||
const Node settingsBtn = xmlScreenParser.findCardSettingsButton();
|
||||
if (settingsBtn.x() > 0) {
|
||||
AdbUtils::makeTap(m_deviceId, settingsBtn.x(), settingsBtn.y());
|
||||
QThread::msleep(1500);
|
||||
|
||||
bool sheetFound = false;
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isCardSettingsBottomSheet()) {
|
||||
sheetFound = true;
|
||||
break;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
if (sheetFound) {
|
||||
const Node requisitesItem = xmlScreenParser.findCardRequisitesMenuItem();
|
||||
if (requisitesItem.x() > 0) {
|
||||
AdbUtils::makeTap(m_deviceId, requisitesItem.x(), requisitesItem.y());
|
||||
QThread::msleep(1500);
|
||||
|
||||
bool reqScreenFound = false;
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isCardRequisitesScreen()) {
|
||||
reqScreenFound = true;
|
||||
break;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
if (reqScreenFound) {
|
||||
const Node accountTab = xmlScreenParser.findAccountRequisitesTab();
|
||||
if (accountTab.x() > 0) {
|
||||
AdbUtils::makeTap(m_deviceId, accountTab.x(), accountTab.y());
|
||||
QThread::msleep(1500);
|
||||
|
||||
// Несколько попыток — данные могут подгружаться
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
accountNumber = xmlScreenParser.parseAccountNumber();
|
||||
if (!accountNumber.isEmpty()) break;
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
qDebug() << "[Dushanbe::GetCardInfo] accountNumber for card" << i
|
||||
<< ":" << accountNumber;
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::GetCardInfo] Account tab not found for card" << i;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::GetCardInfo] Requisites screen not detected for card" << i;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::GetCardInfo] 'Реквизиты карты' menu item not found for card" << i;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::GetCardInfo] Card settings bottom sheet not detected for card" << i;
|
||||
}
|
||||
|
||||
// Возврат на экран детали карты — bottom sheet и/или экран Реквизитов
|
||||
for (int attempt = 0; attempt < 4; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isCardDetailScreen()) break;
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1200);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::GetCardInfo] 'Настройки карты' button not found for card" << i;
|
||||
}
|
||||
|
||||
MaterialInfo account;
|
||||
account.deviceId = device.id;
|
||||
account.appCode = m_appCode;
|
||||
account.lastNumbers = lastNumbers;
|
||||
account.cardNumber = QString(fullNumber).remove(' ');
|
||||
account.accountNumber = accountNumber;
|
||||
account.amount = cardBalance;
|
||||
account.currency = "TJS";
|
||||
account.description = cards[i].description.isEmpty() ? holder : cards[i].description;
|
||||
@ -187,6 +260,7 @@ void GetCardInfoScript::doStart() {
|
||||
QJsonObject materialBody;
|
||||
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
||||
materialBody["card_number"] = QString(fullNumber).remove(' ');
|
||||
materialBody["account_number"] = accountNumber;
|
||||
materialBody["name"] = updatedApp.fullName;
|
||||
materialBody["state"] = materialStatusToString(savedAccount.status) == "active" ? "enabled" : "disabled";
|
||||
materialBody["currency"] = currencyCodes.value(savedAccount.currency.toUpper(), 972);
|
||||
|
||||
@ -410,6 +410,86 @@ QString ScreenXmlParser::parseCardDetailHolder() {
|
||||
return {};
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findCardSettingsButton() {
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.clickable && node.contentDesc == QString::fromUtf8("Настройки карты")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isCardSettingsBottomSheet() {
|
||||
bool hasRequisites = false;
|
||||
bool hasBlock = false;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.contentDesc == QString::fromUtf8("Реквизиты карты")) hasRequisites = true;
|
||||
if (node.contentDesc == QString::fromUtf8("Заблокировать карту")) hasBlock = true;
|
||||
if (hasRequisites && hasBlock) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findCardRequisitesMenuItem() {
|
||||
// На bottom sheet это ImageView; на экране "Реквизиты и информация"
|
||||
// одноимённый таб — Button. Берём именно ImageView, чтобы не перепутать.
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.clickable
|
||||
&& node.className == "android.widget.ImageView"
|
||||
&& node.contentDesc == QString::fromUtf8("Реквизиты карты")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isCardRequisitesScreen() {
|
||||
bool hasCardTab = false;
|
||||
bool hasAccountTab = false;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.Button") continue;
|
||||
if (node.contentDesc == QString::fromUtf8("Реквизиты карты")) hasCardTab = true;
|
||||
if (node.contentDesc == QString::fromUtf8("Реквизиты счета")) hasAccountTab = true;
|
||||
if (hasCardTab && hasAccountTab) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findAccountRequisitesTab() {
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.clickable
|
||||
&& node.className == "android.widget.Button"
|
||||
&& node.contentDesc == QString::fromUtf8("Реквизиты счета")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString ScreenXmlParser::parseAccountNumber() {
|
||||
// Берём ноду "Номер счета" (label) и ищем ближайший узел снизу
|
||||
// с чисто цифровым content-desc — это и есть номер счёта.
|
||||
Node label;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.contentDesc == QString::fromUtf8("Номер счета")) {
|
||||
label = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (label.y2() < 0) return {};
|
||||
|
||||
static const QRegularExpression digitsOnly(R"(^\d{10,}$)");
|
||||
Node best;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.y1() < label.y2()) continue;
|
||||
if (!digitsOnly.match(node.contentDesc).hasMatch()) continue;
|
||||
if (best.y1() < 0 || node.y1() < best.y1()) {
|
||||
best = node;
|
||||
}
|
||||
}
|
||||
return best.contentDesc;
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isHistoryScreen() {
|
||||
bool hasOperacii = false;
|
||||
bool hasVypiska = false;
|
||||
@ -527,32 +607,15 @@ Node ScreenXmlParser::findEditTextByHint(const QString &hint) {
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findSumEditText() {
|
||||
// Якорь: View "Мин. сумма: ..." расположен под полем "Комментарий",
|
||||
// которое следует за полем суммы. То есть над якорем два EditText'а:
|
||||
// ближайший = "Комментарий", следующий вверх = "Сумма".
|
||||
Node anchor;
|
||||
// На экране перевода по карте порядок EditText'ов сверху вниз:
|
||||
// [0] номер карты, [1] сумма, [2] комментарий.
|
||||
QList<Node> editTexts;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.contentDesc.contains(QString::fromUtf8("Мин. сумма"))) {
|
||||
anchor = node;
|
||||
break;
|
||||
if (node.className == "android.widget.EditText") editTexts.append(node);
|
||||
}
|
||||
}
|
||||
if (anchor.x1() < 0) return {};
|
||||
|
||||
QList<Node> aboveAnchor;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.EditText") continue;
|
||||
if (node.y2() > anchor.y1()) continue;
|
||||
aboveAnchor.append(node);
|
||||
}
|
||||
// Сортируем по нижней границе по убыванию: [0] — ближайший (Комментарий),
|
||||
// [1] — следующий выше (Сумма).
|
||||
std::sort(aboveAnchor.begin(), aboveAnchor.end(),
|
||||
[](const Node &a, const Node &b) { return a.y2() > b.y2(); });
|
||||
|
||||
if (aboveAnchor.size() >= 2) return aboveAnchor[1];
|
||||
if (aboveAnchor.size() == 1) return aboveAnchor[0]; // нестандартная разметка
|
||||
return {};
|
||||
std::sort(editTexts.begin(), editTexts.end(),
|
||||
[](const Node &a, const Node &b) { return a.y1() < b.y1(); });
|
||||
return editTexts.size() >= 2 ? editTexts[1] : Node{};
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::tryToFindSystemPermissionDenyButton() {
|
||||
|
||||
@ -82,6 +82,24 @@ public:
|
||||
// Парсит имя держателя с экрана детали
|
||||
QString parseCardDetailHolder();
|
||||
|
||||
// Кнопка "Настройки карты" на экране детали карты
|
||||
Node findCardSettingsButton();
|
||||
|
||||
// Bottom sheet с пунктами "Реквизиты карты" / "Заблокировать карту" и т.д.
|
||||
bool isCardSettingsBottomSheet();
|
||||
|
||||
// Кнопка "Реквизиты карты" внутри bottom sheet "Настройки карты"
|
||||
Node findCardRequisitesMenuItem();
|
||||
|
||||
// Экран "Реквизиты и информация" с табами "Реквизиты карты" / "Реквизиты счета"
|
||||
bool isCardRequisitesScreen();
|
||||
|
||||
// Таб "Реквизиты счета" (правый) на экране "Реквизиты и информация"
|
||||
Node findAccountRequisitesTab();
|
||||
|
||||
// Значение под лейблом "Номер счета" — длинная цифровая строка
|
||||
QString parseAccountNumber();
|
||||
|
||||
// Определяет экран истории операций (табы "Операции" + "Выписка")
|
||||
bool isHistoryScreen();
|
||||
|
||||
|
||||
@ -410,6 +410,105 @@ void GetCardInfoScript::doStart() {
|
||||
}
|
||||
}
|
||||
|
||||
// 2.5. Достаём номер счёта (общий для всех карт): Основной счёт → Посмотреть реквизиты
|
||||
QString accountNumber;
|
||||
{
|
||||
bool needFetch = false;
|
||||
for (const MaterialInfo &c : cards) {
|
||||
const MaterialInfo dbCard = MaterialDAO::findAppAccount(device.id, m_appCode, c.lastNumbers);
|
||||
if (dbCard.id != -1 && dbCard.accountNumber.isEmpty()) { needFetch = true; break; }
|
||||
}
|
||||
|
||||
if (needFetch) {
|
||||
// Возвращаемся на главный экран — критерий именно по наличию кнопки
|
||||
// "Основной счёт ... ₽". isHomeScreen() для Озона нестрогий
|
||||
// (Перевести/Пополнить присутствуют и на других экранах).
|
||||
Node mainAcc;
|
||||
for (int attempt = 0; attempt < 6; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
mainAcc = xmlScreenParser.findMainAccountButton();
|
||||
if (mainAcc.x() > 0) break;
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (mainAcc.x() > 0) {
|
||||
AdbUtils::makeTap(m_deviceId, mainAcc.x(), mainAcc.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Скроллим WebView, пока "Посмотреть реквизиты" не окажется в верхней половине,
|
||||
// чтобы тап не ушёл в системный нав-бар.
|
||||
Node showReq;
|
||||
const int targetMaxY = device.screenHeight / 2;
|
||||
for (int attempt = 0; attempt < 6; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
Node candidate = xmlScreenParser.findShowRequisitesButton();
|
||||
if (candidate.x() > 0 && candidate.y() <= targetMaxY) {
|
||||
showReq = candidate;
|
||||
break;
|
||||
}
|
||||
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2,
|
||||
device.screenHeight * 2 / 3,
|
||||
device.screenWidth / 2,
|
||||
device.screenHeight / 3);
|
||||
QThread::msleep(800);
|
||||
}
|
||||
|
||||
if (showReq.x() > 0) {
|
||||
AdbUtils::makeTap(m_deviceId, showReq.x(), showReq.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
bool reqScreen = false;
|
||||
for (int attempt = 0; attempt < 6; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isAccountRequisitesScreen()) {
|
||||
reqScreen = true;
|
||||
break;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
if (reqScreen) {
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
accountNumber = xmlScreenParser.parseAccountNumberFromRequisites();
|
||||
if (!accountNumber.isEmpty()) break;
|
||||
QThread::msleep(800);
|
||||
}
|
||||
qDebug() << "[GetCardInfo] accountNumber:" << accountNumber;
|
||||
} else {
|
||||
qWarning() << "[GetCardInfo] Account requisites screen not detected";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetCardInfo] 'Посмотреть реквизиты' not reached";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetCardInfo] 'Основной счёт' button not found on home";
|
||||
}
|
||||
|
||||
// Возврат на главный — критерий по кнопке "Основной счёт ... ₽"
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.findMainAccountButton().x() > 0) break;
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
// Записываем номер счёта во все материалы device/app
|
||||
if (!accountNumber.isEmpty()) {
|
||||
for (const MaterialInfo &c : cards) {
|
||||
MaterialInfo upd = MaterialDAO::findAppAccount(device.id, m_appCode, c.lastNumbers);
|
||||
if (upd.id == -1) continue;
|
||||
upd.accountNumber = accountNumber;
|
||||
upd.updateTime = QDateTime::currentDateTimeUtc();
|
||||
if (!MaterialDAO::upsertAccount(upd)) {
|
||||
qWarning() << "[GetCardInfo] Failed to save accountNumber for" << upd.lastNumbers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
|
||||
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||||
if (!updatedApp.bankProfileId.isEmpty()) {
|
||||
@ -427,6 +526,7 @@ void GetCardInfoScript::doStart() {
|
||||
QJsonObject materialBody;
|
||||
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
||||
materialBody["card_number"] = dbCard.cardNumber;
|
||||
materialBody["account_number"] = dbCard.accountNumber;
|
||||
materialBody["name"] = updatedApp.fullName;
|
||||
materialBody["state"] = materialStatusToString(dbCard.status) == "active" ? "enabled" : "disabled";
|
||||
materialBody["currency"] = currencyCodes.value(dbCard.currency.toUpper(), 643);
|
||||
|
||||
@ -400,6 +400,50 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findMainAccountButton() {
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.Button") continue;
|
||||
QString t = node.text;
|
||||
t.replace(QChar(0x00A0), ' ').replace(QChar(0x202F), ' ');
|
||||
if (t.contains("Основной", Qt::CaseInsensitive) && t.contains("₽")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findShowRequisitesButton() {
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.text.trimmed() == QString::fromUtf8("Посмотреть реквизиты")) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isAccountRequisitesScreen() {
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.text.trimmed() == QString::fromUtf8("Реквизиты счёта")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ScreenXmlParser::parseAccountNumberFromRequisites() {
|
||||
// text="Номер счёта получателя 40914810100039708263"
|
||||
// Префикс может варьироваться по регистру/пробелам — берём после первого совпадения цифр в конце.
|
||||
static const QRegularExpression tailDigits(R"((\d{15,}))");
|
||||
static const QString prefix = QString::fromUtf8("Номер счёта получателя");
|
||||
for (const Node &node : m_nodes) {
|
||||
const QString t = node.text;
|
||||
if (!t.contains(prefix)) continue;
|
||||
const QRegularExpressionMatch m = tailDigits.match(t);
|
||||
if (m.hasMatch()) return m.captured(1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<MaterialInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
||||
// "Основной счёт X ₽" — общая кнопка с балансом для всех карт.
|
||||
// Карты — отдельные кнопки с текстом из 4 цифр (могут быть любые non-breaking spaces).
|
||||
|
||||
@ -85,6 +85,18 @@ public:
|
||||
// Finds a TextView with full card number (format "XXXX XXXX XXXX XXXX")
|
||||
QString findFullCardNumber();
|
||||
|
||||
// Кнопка "Основной счёт X ₽" на главном экране
|
||||
Node findMainAccountButton();
|
||||
|
||||
// Кнопка/текст "Посмотреть реквизиты" на экране "Основной счёт"
|
||||
Node findShowRequisitesButton();
|
||||
|
||||
// Экран "Реквизиты счёта" — заголовок text="Реквизиты счёта"
|
||||
bool isAccountRequisitesScreen();
|
||||
|
||||
// Парсит "Номер счёта получателя <20 цифр>" из текстовой ноды
|
||||
QString parseAccountNumberFromRequisites();
|
||||
|
||||
bool isTransactionListScreen();
|
||||
|
||||
// Возвращает кнопки транзакций на экране "Операции" (до maxCount штук)
|
||||
|
||||
@ -134,11 +134,14 @@ void DatabaseManager::initSchema() {
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
account_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
))");
|
||||
|
||||
q.exec("ALTER TABLE materials ADD COLUMN account_number TEXT DEFAULT ''");
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER,
|
||||
|
||||
@ -19,14 +19,15 @@ bool MaterialDAO::upsertAccount(const MaterialInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO materials (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency, material_id)
|
||||
VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency, :material_id)
|
||||
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,
|
||||
@ -37,6 +38,7 @@ bool MaterialDAO::upsertAccount(const MaterialInfo &info) {
|
||||
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);
|
||||
@ -133,6 +135,7 @@ MaterialInfo parseAccount(const QSqlQuery &query) {
|
||||
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());
|
||||
@ -151,7 +154,7 @@ QList<MaterialInfo> MaterialDAO::activateExclusive(
|
||||
|
||||
QSqlQuery sel(db);
|
||||
sel.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, 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
|
||||
@ -199,7 +202,7 @@ QList<MaterialInfo> MaterialDAO::getAccounts(const QString &deviceId, const QStr
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
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
|
||||
)");
|
||||
@ -223,7 +226,7 @@ MaterialInfo MaterialDAO::getAccountById(const int accountId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
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
|
||||
@ -247,7 +250,7 @@ QList<MaterialInfo> MaterialDAO::getAccountsByAppCode(const QString &appCode) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
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
|
||||
)");
|
||||
@ -270,7 +273,7 @@ MaterialInfo MaterialDAO::findAppAccount(const QString &deviceId, const QString
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
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
|
||||
@ -334,7 +337,7 @@ MaterialInfo MaterialDAO::findByMaterialId(const QString &materialId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
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
|
||||
|
||||
@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS materials
|
||||
update_time DATETIME DEFAULT (strftime('%d.%m.%Y %H:%M:%S', 'now')),
|
||||
status TEXT DEFAULT 'active',
|
||||
card_number TEXT DEFAULT '',
|
||||
account_number TEXT DEFAULT '',
|
||||
material_id TEXT DEFAULT '',
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_code, last_numbers)
|
||||
|
||||
@ -35,6 +35,7 @@ struct MaterialInfo {
|
||||
QString appCode;
|
||||
QString lastNumbers;
|
||||
QString cardNumber; // full 16-digit card number
|
||||
QString accountNumber; // full bank account number (IBAN-like)
|
||||
QString materialId;
|
||||
QString currency;
|
||||
QString description;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user