Refine transaction ID generation by replacing history-based retrieval with timestamp-based fallback logic. Adjust source card selection and transaction grouping in payment scripts.
This commit is contained in:
parent
fd99281dfb
commit
ffb206303e
@ -294,10 +294,6 @@ void GetLastTransactionsScript::doStart() {
|
||||
AdbUtils::makeTap(m_deviceId, okBtn.x(), okBtn.y());
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
// Свайп вниз для обновления (pull-to-refresh)
|
||||
const int x = device.screenWidth / 2;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight / 4, x, device.screenHeight * 3 / 4);
|
||||
QThread::msleep(3000);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -339,11 +335,8 @@ void GetLastTransactionsScript::doStart() {
|
||||
qDebug() << "[Dushanbe::GetLastTransactions] Error dialog on Выписка, tapping OK";
|
||||
AdbUtils::makeTap(m_deviceId, okBtn.x(), okBtn.y());
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
}
|
||||
const int x = device.screenWidth / 2;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight / 4, x, device.screenHeight * 3 / 4);
|
||||
QThread::msleep(3000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
// Находим accountId
|
||||
@ -364,43 +357,93 @@ void GetLastTransactionsScript::doStart() {
|
||||
if (!tjTz.isValid()) tjTz = QTimeZone("Asia/Dushanbe");
|
||||
const int maxScrolls = 10;
|
||||
|
||||
struct VypiskaItem {
|
||||
double amount = 0.0;
|
||||
QString time; // "21:31:21"
|
||||
QString date; // "05.04.2026"
|
||||
Node node;
|
||||
};
|
||||
|
||||
for (int scroll = 0; scroll <= maxScrolls; ++scroll) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
const QList<ScreenXmlParser::TransactionItem> items = xmlScreenParser.parseTransactionItems();
|
||||
|
||||
// 1. Собираем список дат-групп из родительского ImageView "Обновлено: ...\n05.04.2026\n..."
|
||||
// Поддерживаем "DD.MM.YYYY", "Сегодня", "Вчера"
|
||||
const QDate todayLocal = QDateTime::currentDateTimeUtc().toTimeZone(tjTz).date();
|
||||
QStringList groupDates;
|
||||
static const QRegularExpression reDate(R"(^\d{2}\.\d{2}\.\d{4}$)");
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (n.className != "android.widget.ImageView") continue;
|
||||
if (!n.contentDesc.startsWith(QString::fromUtf8("Обновлено"))) continue;
|
||||
for (const QString &line : n.contentDesc.split('\n')) {
|
||||
const QString t = line.trimmed();
|
||||
if (reDate.match(t).hasMatch()) {
|
||||
groupDates.append(t);
|
||||
} else if (t.compare(QString::fromUtf8("Сегодня"), Qt::CaseInsensitive) == 0) {
|
||||
groupDates.append(todayLocal.toString("dd.MM.yyyy"));
|
||||
} else if (t.compare(QString::fromUtf8("Вчера"), Qt::CaseInsensitive) == 0) {
|
||||
groupDates.append(todayLocal.addDays(-1).toString("dd.MM.yyyy"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Собираем View-транзакции (формат Выписки)
|
||||
static const QRegularExpression reTime(R"(\d{2}:\d{2}:\d{2})");
|
||||
QList<VypiskaItem> items;
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (n.className != "android.view.View") continue;
|
||||
if (!n.contentDesc.contains("DCWallet") && !n.contentDesc.contains("***")) continue;
|
||||
const QStringList lines = n.contentDesc.split('\n');
|
||||
if (lines.size() < 5) continue;
|
||||
const QString last = lines.last().trimmed();
|
||||
if (!reTime.match(last).hasMatch()) continue;
|
||||
|
||||
VypiskaItem it;
|
||||
bool ok = false;
|
||||
it.amount = lines[2].trimmed().toDouble(&ok);
|
||||
if (!ok) continue;
|
||||
it.time = last;
|
||||
it.node = n;
|
||||
items.append(it);
|
||||
}
|
||||
|
||||
// 3. Сортируем по Y и группируем по разрывам (> 50px → новая группа-день)
|
||||
std::sort(items.begin(), items.end(), [](const VypiskaItem &a, const VypiskaItem &b) {
|
||||
return a.node.y1() < b.node.y1();
|
||||
});
|
||||
int groupIdx = 0;
|
||||
int prevY2 = -1;
|
||||
for (VypiskaItem &it : items) {
|
||||
if (prevY2 >= 0 && it.node.y1() - prevY2 > 50) ++groupIdx;
|
||||
if (groupIdx < groupDates.size()) it.date = groupDates[groupIdx];
|
||||
prevY2 = it.node.y2();
|
||||
}
|
||||
|
||||
qDebug() << "[Dushanbe::GetLastTransactions] Vypiska items:" << items.size()
|
||||
<< "groups:" << groupDates;
|
||||
|
||||
for (const auto &tx : items) {
|
||||
if (qAbs(tx.amount - m_searchAmount) > 0.01) continue;
|
||||
|
||||
qDebug() << "[Dushanbe::GetLastTransactions] Amount match:" << tx.amount
|
||||
<< "phone:" << tx.phone << "time:" << tx.time;
|
||||
<< "date:" << tx.date << "time:" << tx.time;
|
||||
|
||||
// Предварительная проверка времени из списка (±1 час)
|
||||
if (m_searchTime.isValid() && !tx.time.isEmpty()) {
|
||||
const QTime listTime = QTime::fromString(tx.time, "HH:mm:ss");
|
||||
const QTime searchLocal = m_searchTime.toTimeZone(tjTz).time();
|
||||
if (listTime.isValid() && searchLocal.isValid()) {
|
||||
const int diffSecs = qAbs(listTime.secsTo(searchLocal));
|
||||
if (diffSecs > 3600 && diffSecs < 82800) { // 82800 = 24h - 1h (для полуночи)
|
||||
qDebug() << "[Dushanbe::GetLastTransactions] Time pre-filter skip:"
|
||||
<< tx.time << "vs" << searchLocal.toString("HH:mm:ss")
|
||||
<< "diff=" << diffSecs << "sec";
|
||||
// Проверка даты+времени (±1 час)
|
||||
if (m_searchTime.isValid() && !tx.date.isEmpty() && !tx.time.isEmpty()) {
|
||||
QDateTime txDt = QDateTime::fromString(tx.date + " " + tx.time,
|
||||
"dd.MM.yyyy HH:mm:ss");
|
||||
if (txDt.isValid()) {
|
||||
txDt.setTimeZone(tjTz);
|
||||
const qint64 diffSecs = qAbs(txDt.toUTC().secsTo(m_searchTime.toUTC()));
|
||||
if (diffSecs > 3600) {
|
||||
qDebug() << "[Dushanbe::GetLastTransactions] Time diff too big:" << diffSecs << "sec, skipping";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ищем соответствующий node чтобы по нему тапнуть
|
||||
Node txNode;
|
||||
for (const Node &node : xmlScreenParser.findAllNodes()) {
|
||||
if (node.className != "android.widget.ImageView") continue;
|
||||
if (!node.contentDesc.contains("***")) continue;
|
||||
// Проверяем что содержит нужную сумму и время
|
||||
const QString amountStr = QString::number(tx.amount, 'f', 2);
|
||||
if (node.contentDesc.contains(amountStr) && node.contentDesc.contains(tx.time)) {
|
||||
txNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Node txNode = tx.node;
|
||||
|
||||
if (txNode.x() == 0 && txNode.y() == 0) {
|
||||
qWarning() << "[Dushanbe::GetLastTransactions] Cannot find node for transaction";
|
||||
|
||||
@ -250,10 +250,31 @@ void PayByCardScript::makePayment(
|
||||
AdbUtils::inputText(deviceId, amountStr);
|
||||
QThread::msleep(1000);
|
||||
|
||||
// 7. Скрываем клавиатуру и нажимаем "Далее"
|
||||
// 6a. Скрываем клавиатуру и выбираем карту-источник (под полем "Комментарий")
|
||||
AdbUtils::goBack(deviceId);
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
{
|
||||
const QString cardMask = "****" + m_account.lastNumbers;
|
||||
Node cardNode;
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (!n.clickable) continue;
|
||||
if (n.contentDesc.contains(cardMask)) { cardNode = n; break; }
|
||||
}
|
||||
if (cardNode.isEmpty()) {
|
||||
m_error = "Source card not found: " + cardMask;
|
||||
qWarning() << "[Dushanbe::PayByCard]" << m_error;
|
||||
AppLogger::log("dushanbe/PayByCard", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
qDebug() << "[Dushanbe::PayByCard] Selecting source card:" << cardMask;
|
||||
AdbUtils::makeTap(deviceId, cardNode.x(), cardNode.y());
|
||||
QThread::msleep(500);
|
||||
}
|
||||
|
||||
// 7. Нажимаем "Далее"
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node daleeBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Далее"));
|
||||
if (daleeBtn.isEmpty()) {
|
||||
daleeBtn = xmlScreenParser.findButtonNode(QString::fromUtf8("Далее"), false);
|
||||
@ -409,53 +430,11 @@ void PayByCardScript::makePayment(
|
||||
qDebug() << "[Dushanbe::PayByCard] Result: date=" << bankTime << "fee=" << fee;
|
||||
}
|
||||
|
||||
// Переходим в историю для получения ID операции через OCR
|
||||
// 1. Тапаем "На главную" (внизу экрана результата)
|
||||
Node naGlavnuyuNode = xmlScreenParser.findNodeByContentDesc(
|
||||
QString::fromUtf8("Платеж выполнен"), true);
|
||||
if (!naGlavnuyuNode.isEmpty()) {
|
||||
const int tapY = height * 92 / 100;
|
||||
AdbUtils::makeTap(deviceId, width / 2, tapY);
|
||||
QThread::msleep(2000);
|
||||
}
|
||||
|
||||
// 2. Тапаем "История" в нижней навигации
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node historyTab = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("История"));
|
||||
if (!historyTab.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, historyTab.x(), historyTab.y());
|
||||
QThread::msleep(3000);
|
||||
}
|
||||
|
||||
// 3. Тапаем первую транзакцию в списке (содержит m_cardNumber)
|
||||
QString operationId;
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node txNode = xmlScreenParser.findNodeByContentDesc(m_cardNumber, true);
|
||||
if (txNode.isEmpty()) {
|
||||
// Fallback: ищем "Сегодня" — транзакция обычно сразу после
|
||||
txNode = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Сегодня"), true);
|
||||
}
|
||||
if (!txNode.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, txNode.x(), txNode.y());
|
||||
QThread::msleep(3000);
|
||||
|
||||
// 4. Делаем скриншот детали и OCR
|
||||
const QByteArray detailScreenshot = AdbUtils::captureScreenshotBytes(deviceId);
|
||||
if (!detailScreenshot.isEmpty()) {
|
||||
operationId = recognizeOperationId(detailScreenshot);
|
||||
if (!operationId.isEmpty()) {
|
||||
qDebug() << "[Dushanbe::PayByCard] Operation ID:" << operationId;
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::PayByCard] Could not extract operation ID";
|
||||
}
|
||||
}
|
||||
|
||||
// Назад из деталей
|
||||
AdbUtils::goBack(deviceId);
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::PayByCard] Transaction not found in history";
|
||||
}
|
||||
// Генерируем ID транзакции из даты+времени (без перехода в историю)
|
||||
const QString operationId = bankTime.isEmpty()
|
||||
? QString("dushanbe_") + QDateTime::currentDateTimeUtc().toString("dd.MM.yyyy_HH:mm:ss")
|
||||
: QString("dushanbe_") + QString(bankTime).replace(' ', '_');
|
||||
qDebug() << "[Dushanbe::PayByCard] Generated operationId:" << operationId;
|
||||
|
||||
// Сохраняем транзакцию
|
||||
TransactionInfo tx;
|
||||
|
||||
@ -250,10 +250,31 @@ void PayByPhoneScript::makePayment(
|
||||
AdbUtils::inputText(deviceId, amountStr);
|
||||
QThread::msleep(1000);
|
||||
|
||||
// 7. Скрываем клавиатуру и нажимаем "Далее"
|
||||
// 6a. Скрываем клавиатуру и выбираем карту-источник (под полем "Комментарий")
|
||||
AdbUtils::goBack(deviceId);
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
{
|
||||
const QString cardMask = "****" + m_account.lastNumbers;
|
||||
Node cardNode;
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (!n.clickable) continue;
|
||||
if (n.contentDesc.contains(cardMask)) { cardNode = n; break; }
|
||||
}
|
||||
if (cardNode.isEmpty()) {
|
||||
m_error = "Source card not found: " + cardMask;
|
||||
qWarning() << "[Dushanbe::PayByPhone]" << m_error;
|
||||
AppLogger::log("dushanbe/PayByPhone", deviceId, m_error,
|
||||
AdbUtils::captureScreenshotBytes(deviceId), "CREATE_TRANSACTION");
|
||||
return;
|
||||
}
|
||||
qDebug() << "[Dushanbe::PayByPhone] Selecting source card:" << cardMask;
|
||||
AdbUtils::makeTap(deviceId, cardNode.x(), cardNode.y());
|
||||
QThread::msleep(500);
|
||||
}
|
||||
|
||||
// 7. Нажимаем "Далее"
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node daleeBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Далее"));
|
||||
if (daleeBtn.isEmpty()) {
|
||||
daleeBtn = xmlScreenParser.findButtonNode(QString::fromUtf8("Далее"), false);
|
||||
@ -400,47 +421,11 @@ void PayByPhoneScript::makePayment(
|
||||
qDebug() << "[Dushanbe::PayByPhone] Result: date=" << bankTime << "fee=" << fee;
|
||||
}
|
||||
|
||||
// Переходим в историю для получения ID операции через OCR
|
||||
Node naGlavnuyuNode = xmlScreenParser.findNodeByContentDesc(
|
||||
QString::fromUtf8("Платеж выполнен"), true);
|
||||
if (!naGlavnuyuNode.isEmpty()) {
|
||||
const int tapY = height * 92 / 100;
|
||||
AdbUtils::makeTap(deviceId, width / 2, tapY);
|
||||
QThread::msleep(2000);
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node historyTab = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("История"));
|
||||
if (!historyTab.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, historyTab.x(), historyTab.y());
|
||||
QThread::msleep(3000);
|
||||
}
|
||||
|
||||
QString operationId;
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node txNode = xmlScreenParser.findNodeByContentDesc(phoneDigits, true);
|
||||
if (txNode.isEmpty()) {
|
||||
txNode = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Сегодня"), true);
|
||||
}
|
||||
if (!txNode.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, txNode.x(), txNode.y());
|
||||
QThread::msleep(3000);
|
||||
|
||||
const QByteArray detailScreenshot = AdbUtils::captureScreenshotBytes(deviceId);
|
||||
if (!detailScreenshot.isEmpty()) {
|
||||
operationId = recognizeOperationId(detailScreenshot);
|
||||
if (!operationId.isEmpty()) {
|
||||
qDebug() << "[Dushanbe::PayByPhone] Operation ID:" << operationId;
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::PayByPhone] Could not extract operation ID";
|
||||
}
|
||||
}
|
||||
|
||||
AdbUtils::goBack(deviceId);
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
qWarning() << "[Dushanbe::PayByPhone] Transaction not found in history";
|
||||
}
|
||||
// Генерируем ID транзакции из даты+времени (без перехода в историю)
|
||||
const QString operationId = bankTime.isEmpty()
|
||||
? QString("dushanbe_") + QDateTime::currentDateTimeUtc().toString("dd.MM.yyyy_HH:mm:ss")
|
||||
: QString("dushanbe_") + QString(bankTime).replace(' ', '_');
|
||||
qDebug() << "[Dushanbe::PayByPhone] Generated operationId:" << operationId;
|
||||
|
||||
// Сохраняем транзакцию
|
||||
TransactionInfo tx;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user