Add taskJson parameter to sendTaskError, introduce sendTaskSuccess, enhance error and success logging with detailed JSON data, and refine time mismatch handling.
This commit is contained in:
parent
66279d4767
commit
cd98644f32
@ -740,13 +740,15 @@ void GetLastTransactionsScript::searchAll(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка времени по HH:MM:SS (±10 минут, с учётом wraparound полуночи)
|
// Проверка времени по HH:MM:SS (+10 минут, одностороннее окно, с учётом wraparound полуночи)
|
||||||
if (cand.isValid() && !tx.time.isEmpty()) {
|
if (cand.isValid() && !tx.time.isEmpty()) {
|
||||||
const QTime listTime = QTime::fromString(tx.time, "HH:mm:ss");
|
const QTime listTime = QTime::fromString(tx.time, "HH:mm:ss");
|
||||||
const QTime searchLocalTime = cand.toTimeZone(tjTz).time();
|
const QTime searchLocalTime = cand.toTimeZone(tjTz).time();
|
||||||
if (listTime.isValid() && searchLocalTime.isValid()) {
|
if (listTime.isValid() && searchLocalTime.isValid()) {
|
||||||
const int diffSecs = qAbs(listTime.secsTo(searchLocalTime));
|
int diffSecs = searchLocalTime.secsTo(listTime); // >0 = listTime (tx) позже цели
|
||||||
if (diffSecs > 600 && diffSecs < 85800) continue; // 85800 = 23h50m
|
if (diffSecs < -43200) diffSecs += 86400; // wraparound через полночь вперёд
|
||||||
|
else if (diffSecs > 43200) diffSecs -= 86400; // wraparound назад
|
||||||
|
if (diffSecs < 0 || diffSecs > 600) continue; // tx раньше цели или позже неё на >10 мин
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,7 +917,7 @@ void GetLastTransactionsScript::searchAll(
|
|||||||
<< "date=" << detail.date << "time=" << detail.time
|
<< "date=" << detail.date << "time=" << detail.time
|
||||||
<< "amount=" << detail.amount << "receiver=" << detail.receiverAccount;
|
<< "amount=" << detail.amount << "receiver=" << detail.receiverAccount;
|
||||||
|
|
||||||
// Проверяем время ±10 минут
|
// Проверяем время: txTime не раньше цели и не позже неё на 10 минут (+10м, одностороннее окно)
|
||||||
if (searchTime.isValid() && !detail.date.isEmpty() && !detail.time.isEmpty()) {
|
if (searchTime.isValid() && !detail.date.isEmpty() && !detail.time.isEmpty()) {
|
||||||
QDateTime txTime = QDateTime::fromString(
|
QDateTime txTime = QDateTime::fromString(
|
||||||
detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss");
|
detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss");
|
||||||
@ -924,9 +926,9 @@ void GetLastTransactionsScript::searchAll(
|
|||||||
}
|
}
|
||||||
if (txTime.isValid()) {
|
if (txTime.isValid()) {
|
||||||
txTime.setTimeZone(tjTz);
|
txTime.setTimeZone(tjTz);
|
||||||
const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(searchTime.toUTC()));
|
const qint64 diffSecs = searchTime.toUTC().secsTo(txTime.toUTC()); // >0 = txTime позже цели
|
||||||
if (diffSecs > 600) {
|
if (diffSecs < 0 || diffSecs > 600) {
|
||||||
qDebug() << "[Dushanbe::GetLastTransactions] Time mismatch: diff=" << diffSecs << "sec (>10min), skipping";
|
qDebug() << "[Dushanbe::GetLastTransactions] Time mismatch: diff=" << diffSecs << "sec (out of +10min window), skipping";
|
||||||
AdbUtils::goBack(m_deviceId);
|
AdbUtils::goBack(m_deviceId);
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
continue;
|
continue;
|
||||||
@ -956,9 +958,15 @@ void GetLastTransactionsScript::searchAll(
|
|||||||
if (txTime.isValid()) txTime.setTimeZone(tjTz);
|
if (txTime.isValid()) txTime.setTimeZone(tjTz);
|
||||||
|
|
||||||
QJsonObject txJson;
|
QJsonObject txJson;
|
||||||
txJson["bank_transaction_id"] = detail.operationId.isEmpty()
|
// Фолбэк-id (когда «Номер операции» не распарсился) строим из
|
||||||
? QString("dushanbe_") + detail.date + "_" + detail.time
|
// created_at задачи — он детерминирован и стабилен между запусками,
|
||||||
: detail.operationId;
|
// что важно для дедупликации на сервере. Раньше брали распарсенные
|
||||||
|
// с экрана дату+время, которые могли «плавать» из-за OCR.
|
||||||
|
txJson["bank_transaction_id"] = !detail.operationId.isEmpty()
|
||||||
|
? detail.operationId
|
||||||
|
: (searchTime.isValid()
|
||||||
|
? QStringLiteral("dushanbe_") + searchTime.toUTC().toString(Qt::ISODate)
|
||||||
|
: QStringLiteral("dushanbe_") + detail.date + "_" + detail.time);
|
||||||
// Списание хранится с отрицательным знаком, пополнение — положительным.
|
// Списание хранится с отрицательным знаком, пополнение — положительным.
|
||||||
// detail.amount всегда положителен (OCR), знак берём из списка Выписки.
|
// detail.amount всегда положителен (OCR), знак берём из списка Выписки.
|
||||||
const double signedAmount = (tx.amount < 0 ? -1.0 : 1.0) * qAbs(finalAmount);
|
const double signedAmount = (tx.amount < 0 ? -1.0 : 1.0) * qAbs(finalAmount);
|
||||||
@ -971,7 +979,11 @@ void GetLastTransactionsScript::searchAll(
|
|||||||
QJsonObject extraObj;
|
QJsonObject extraObj;
|
||||||
extraObj["fee"] = static_cast<int>(finalFee * 100);
|
extraObj["fee"] = static_cast<int>(finalFee * 100);
|
||||||
txJson["extra"] = extraObj;
|
txJson["extra"] = extraObj;
|
||||||
txJson["created_at"] = txTime.isValid()
|
// created_at — время из задачи (поисковый target), real_created_at —
|
||||||
|
// фактическое время транзакции, спарсенное с экрана приложения.
|
||||||
|
txJson["created_at"] = searchTime.isValid()
|
||||||
|
? searchTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
||||||
|
txJson["real_created_at"] = txTime.isValid()
|
||||||
? txTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
? txTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
||||||
|
|
||||||
qDebug() << "[Dushanbe::GetLastTransactions] Emitting parsed tx:"
|
qDebug() << "[Dushanbe::GetLastTransactions] Emitting parsed tx:"
|
||||||
|
|||||||
@ -618,10 +618,10 @@ bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем время (±10 минут)
|
// Проверяем время: txTime не раньше цели и не позже неё на 10 минут (+10м, одностороннее окно)
|
||||||
if (txTime.isValid() && searchTime.isValid()) {
|
if (txTime.isValid() && searchTime.isValid()) {
|
||||||
const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(searchTime.toUTC()));
|
const qint64 diffSecs = searchTime.toUTC().secsTo(txTime.toUTC()); // >0 = txTime позже цели
|
||||||
if (diffSecs > 600) {
|
if (diffSecs < 0 || diffSecs > 600) {
|
||||||
qDebug() << "[FetchTransactions] Time mismatch:" << dtStr
|
qDebug() << "[FetchTransactions] Time mismatch:" << dtStr
|
||||||
<< "expected" << searchLocal.toString("dd.MM.yyyy, HH:mm")
|
<< "expected" << searchLocal.toString("dd.MM.yyyy, HH:mm")
|
||||||
<< "diff=" << diffSecs << "sec";
|
<< "diff=" << diffSecs << "sec";
|
||||||
@ -644,6 +644,16 @@ bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountI
|
|||||||
|
|
||||||
QString externalId = detail.bankTrExternalId;
|
QString externalId = detail.bankTrExternalId;
|
||||||
|
|
||||||
|
// Фолбэк-id (когда реальный ID банка не распарсился) строим из
|
||||||
|
// created_at задачи — он детерминирован и стабилен между запусками,
|
||||||
|
// что важно для дедупликации на сервере. Раньше брали распарсенное
|
||||||
|
// с экрана время (dtStr), которое могло «плавать» из-за OCR.
|
||||||
|
const QString finalExternalId = externalId.isEmpty()
|
||||||
|
? QStringLiteral("ozon_") + (searchTime.isValid()
|
||||||
|
? searchTime.toUTC().toString(Qt::ISODate)
|
||||||
|
: QString(dtStr).remove(',').trimmed().replace(' ', '_'))
|
||||||
|
: externalId;
|
||||||
|
|
||||||
// Сохраняем
|
// Сохраняем
|
||||||
if (accountId != -1) {
|
if (accountId != -1) {
|
||||||
TransactionInfo tx;
|
TransactionInfo tx;
|
||||||
@ -651,9 +661,7 @@ bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountI
|
|||||||
tx.amount = parsed.amount;
|
tx.amount = parsed.amount;
|
||||||
tx.bankName = m_appCode;
|
tx.bankName = m_appCode;
|
||||||
tx.bankTime = dtStr;
|
tx.bankTime = dtStr;
|
||||||
tx.bankTrExternalId = externalId.isEmpty()
|
tx.bankTrExternalId = finalExternalId;
|
||||||
? QString("ozon_") + QString(dtStr).remove(',').trimmed().replace(' ', '_')
|
|
||||||
: externalId;
|
|
||||||
tx.status = TransactionStatus::Complete;
|
tx.status = TransactionStatus::Complete;
|
||||||
tx.timestamp = DateUtils::getUtcNow();
|
tx.timestamp = DateUtils::getUtcNow();
|
||||||
if (txTime.isValid()) tx.completeTime = txTime;
|
if (txTime.isValid()) tx.completeTime = txTime;
|
||||||
@ -671,10 +679,6 @@ bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountI
|
|||||||
|
|
||||||
// Отдаём результат в EventHandler через сигнал (как Dushanbe)
|
// Отдаём результат в EventHandler через сигнал (как Dushanbe)
|
||||||
{
|
{
|
||||||
const QString finalExternalId = externalId.isEmpty()
|
|
||||||
? QString("ozon_") + QString(dtStr).remove(',').trimmed().replace(' ', '_')
|
|
||||||
: externalId;
|
|
||||||
|
|
||||||
QJsonObject txJson;
|
QJsonObject txJson;
|
||||||
txJson["bank_transaction_id"] = finalExternalId;
|
txJson["bank_transaction_id"] = finalExternalId;
|
||||||
txJson["amount"] = static_cast<int>(parsed.amount * 100);
|
txJson["amount"] = static_cast<int>(parsed.amount * 100);
|
||||||
@ -687,7 +691,11 @@ bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountI
|
|||||||
QJsonObject extraObj;
|
QJsonObject extraObj;
|
||||||
extraObj["fee"] = 0;
|
extraObj["fee"] = 0;
|
||||||
txJson["extra"] = extraObj;
|
txJson["extra"] = extraObj;
|
||||||
txJson["created_at"] = txTime.isValid()
|
// created_at — время из задачи (поисковый target), real_created_at —
|
||||||
|
// фактическое время транзакции, спарсенное с экрана приложения.
|
||||||
|
txJson["created_at"] = searchTime.isValid()
|
||||||
|
? searchTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
||||||
|
txJson["real_created_at"] = txTime.isValid()
|
||||||
? txTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
? txTime.toUTC().toString(Qt::ISODate) : QJsonValue(QJsonValue::Null);
|
||||||
|
|
||||||
qDebug() << "[FetchTransactions] Emitting parsed tx:"
|
qDebug() << "[FetchTransactions] Emitting parsed tx:"
|
||||||
|
|||||||
@ -21,6 +21,8 @@ pathDevice=/api/v1/device/
|
|||||||
pathDeviceScreenshot=/api/v1/device/add_screenshot/
|
pathDeviceScreenshot=/api/v1/device/add_screenshot/
|
||||||
pathMaterial=/api/v1/profile/material
|
pathMaterial=/api/v1/profile/material
|
||||||
pathProfilePing=/api/v1/profile/ping
|
pathProfilePing=/api/v1/profile/ping
|
||||||
|
pathGetTasks=/api/v1/task/get_tasks
|
||||||
|
pathTaskResult=/api/v1/task/task_result
|
||||||
pathMonitoringLog=/monitoring/log
|
pathMonitoringLog=/monitoring/log
|
||||||
pathMonitoringDump=/monitoring/dump
|
pathMonitoringDump=/monitoring/dump
|
||||||
pathMonitoringScreenshot=/monitoring/screenshot
|
pathMonitoringScreenshot=/monitoring/screenshot
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
#include "dushanbe/PayByPhoneScript.h"
|
#include "dushanbe/PayByPhoneScript.h"
|
||||||
|
|
||||||
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName, const QJsonValue &data, int eventId = -1, bool updateStatus = true);
|
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName, const QJsonValue &data, int eventId = -1, bool updateStatus = true);
|
||||||
static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName, const QString &description, const QByteArray &screenshot = {}, int eventId = -1, const QString &transactionId = {}, int accountId = -1);
|
static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName, const QString &description, const QByteArray &screenshot = {}, int eventId = -1, const QString &transactionId = {}, int accountId = -1, const QString &taskJson = {});
|
||||||
static void sendMonitoringLog(const QString &type, const QString &event, const QString &message, const QByteArray &image = {}, int eventId = -1, bool toApiBase = false);
|
static void sendMonitoringLog(const QString &type, const QString &event, const QString &message, const QByteArray &image = {}, int eventId = -1, bool toApiBase = false);
|
||||||
|
|
||||||
EventHandler::EventHandler(QObject *parent) : QObject(parent) {}
|
EventHandler::EventHandler(QObject *parent) : QObject(parent) {}
|
||||||
@ -79,7 +79,7 @@ void EventHandler::start() {
|
|||||||
if (e.sentToServer) continue;
|
if (e.sentToServer) continue;
|
||||||
EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted");
|
EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted");
|
||||||
EventDAO::markSentToServer(e.id);
|
EventDAO::markSentToServer(e.id);
|
||||||
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, e.id, e.transactionId, e.accountId);
|
sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, e.id, e.transactionId, e.accountId, e.json);
|
||||||
qDebug() << "[EventHandler] Closed stale event id:" << e.id;
|
qDebug() << "[EventHandler] Closed stale event id:" << e.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
const MaterialInfo account = MaterialDAO::findByMaterialId(materialId);
|
const MaterialInfo account = MaterialDAO::findByMaterialId(materialId);
|
||||||
if (account.id == -1) {
|
if (account.id == -1) {
|
||||||
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
||||||
sendTaskError(typeStr, materialId, bankName, "Account not found for material_id: " + materialId, {}, -1, incomingTransactionId);
|
sendTaskError(typeStr, materialId, bankName, "Account not found for material_id: " + materialId, {}, -1, incomingTransactionId, -1, QString::fromUtf8(QJsonDocument(task).toJson(QJsonDocument::Compact)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
if (eventType == EventType::FetchProfile) {
|
if (eventType == EventType::FetchProfile) {
|
||||||
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile);
|
const QList<EventInfo> cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile);
|
||||||
for (const EventInfo &c : cancelled) {
|
for (const EventInfo &c : cancelled) {
|
||||||
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, c.id, c.transactionId, c.accountId);
|
sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, c.id, c.transactionId, c.accountId, c.json);
|
||||||
EventDAO::markSentToServer(c.id);
|
EventDAO::markSentToServer(c.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
|||||||
if (!onlineDevices.contains(account.deviceId)) {
|
if (!onlineDevices.contains(account.deviceId)) {
|
||||||
qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId;
|
qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId;
|
||||||
EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline");
|
EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline");
|
||||||
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, eventId, incomingTransactionId, account.id);
|
sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, eventId, incomingTransactionId, account.id, event.json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ static void sendTaskResult(const QString &type, const QString &materialId, const
|
|||||||
|
|
||||||
static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName,
|
static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName,
|
||||||
const QString &description, const QByteArray &screenshot, int eventId,
|
const QString &description, const QByteArray &screenshot, int eventId,
|
||||||
const QString &transactionId, int accountId) {
|
const QString &transactionId, int accountId, const QString &taskJson) {
|
||||||
QJsonObject errorData;
|
QJsonObject errorData;
|
||||||
errorData["status"] = QStringLiteral("error");
|
errorData["status"] = QStringLiteral("error");
|
||||||
errorData["event_type"] = type;
|
errorData["event_type"] = type;
|
||||||
@ -261,6 +261,15 @@ static void sendTaskError(const QString &type, const QString &materialId, const
|
|||||||
if (!appVersion.isEmpty())
|
if (!appVersion.isEmpty())
|
||||||
lines << QStringLiteral("AppVersion: %1").arg(appVersion.toHtmlEscaped());
|
lines << QStringLiteral("AppVersion: %1").arg(appVersion.toHtmlEscaped());
|
||||||
lines << QStringLiteral("Error: %1").arg(description.toHtmlEscaped());
|
lines << QStringLiteral("Error: %1").arg(description.toHtmlEscaped());
|
||||||
|
// JSON задачи, пришедшей с сервера (pretty-print). НЕ экранируем — админка
|
||||||
|
// (_render_msg) экранирует один раз и выносит блок после «JSON: » в <pre>.
|
||||||
|
if (!taskJson.isEmpty()) {
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(taskJson.toUtf8());
|
||||||
|
const QString pretty = doc.isNull()
|
||||||
|
? taskJson
|
||||||
|
: QString::fromUtf8(doc.toJson(QJsonDocument::Indented));
|
||||||
|
lines << QStringLiteral("JSON: %1").arg(pretty);
|
||||||
|
}
|
||||||
const QString message = lines.join(QStringLiteral("\n"));
|
const QString message = lines.join(QStringLiteral("\n"));
|
||||||
sendMonitoringLog("error", "task_error", message, screenshot, eventId);
|
sendMonitoringLog("error", "task_error", message, screenshot, eventId);
|
||||||
|
|
||||||
@ -268,6 +277,50 @@ static void sendTaskError(const QString &type, const QString &materialId, const
|
|||||||
// чтобы покрывать также non-event-driven задачи (login flow и т.п.).
|
// чтобы покрывать также non-event-driven задачи (login flow и т.п.).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Успешное завершение задачи в arc-monitoring (мониторинговый дроплет, не apiBase).
|
||||||
|
// Бизнес-результат уходит отдельно (sendTaskResult / per-tx streaming) — здесь
|
||||||
|
// только человекочитаемый лог в админку с теми же метаданными, что у task_error
|
||||||
|
// (Desktop/Profile/Material/Phone/AppVersion), чтобы корректно парсились и
|
||||||
|
// фильтровались. `summary` — операционные детали (Amount/Found/…).
|
||||||
|
static void sendTaskSuccess(const QString &type, const QString &materialId, const QString &bankName,
|
||||||
|
const QString &summary, const QByteArray &screenshot, int eventId,
|
||||||
|
int accountId) {
|
||||||
|
QString profileUuid, profilePhone, appVersion;
|
||||||
|
if (accountId > 0) {
|
||||||
|
const MaterialInfo acc = MaterialDAO::getAccountById(accountId);
|
||||||
|
if (acc.id != -1) {
|
||||||
|
const BankProfileInfo app = BankProfileDAO::getApplication(acc.deviceId, acc.appCode);
|
||||||
|
profileUuid = app.bankProfileId;
|
||||||
|
profilePhone = app.phone;
|
||||||
|
appVersion = app.appVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const QString desktopId = SettingsDAO::get("desktop_id");
|
||||||
|
const QString clientVersion =
|
||||||
|
QSettings("config.ini", QSettings::IniFormat).value("common/version").toString();
|
||||||
|
QStringList lines;
|
||||||
|
lines << QStringLiteral("<b>[TASK_SUCCESS] [%1] [%2]</b>")
|
||||||
|
.arg(type.toHtmlEscaped(), bankName.toUpper().toHtmlEscaped());
|
||||||
|
if (!desktopId.isEmpty())
|
||||||
|
lines << QStringLiteral("Desktop: %1").arg(desktopId.toHtmlEscaped());
|
||||||
|
if (!clientVersion.isEmpty())
|
||||||
|
lines << QStringLiteral("DesktopVersion: %1").arg(clientVersion.toHtmlEscaped());
|
||||||
|
if (!profileUuid.isEmpty())
|
||||||
|
lines << QStringLiteral("Profile: %1").arg(profileUuid.toHtmlEscaped());
|
||||||
|
if (!materialId.isEmpty())
|
||||||
|
lines << QStringLiteral("Material: %1").arg(materialId.toHtmlEscaped());
|
||||||
|
if (!profilePhone.isEmpty())
|
||||||
|
lines << QStringLiteral("Phone: %1").arg(profilePhone.toHtmlEscaped());
|
||||||
|
if (!appVersion.isEmpty())
|
||||||
|
lines << QStringLiteral("AppVersion: %1").arg(appVersion.toHtmlEscaped());
|
||||||
|
// summary содержит JSON — НЕ экранируем: админка (_render_msg) экранирует
|
||||||
|
// ровно один раз сама. Двойной toHtmlEscaped превратил бы " в " в тексте.
|
||||||
|
if (!summary.isEmpty())
|
||||||
|
lines << summary;
|
||||||
|
const QString message = lines.join(QStringLiteral("\n"));
|
||||||
|
sendMonitoringLog("info", "task_success", message, screenshot, eventId, /*toApiBase=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
static void sendMonitoringLog(const QString &type, const QString &event, const QString &message,
|
static void sendMonitoringLog(const QString &type, const QString &event, const QString &message,
|
||||||
const QByteArray &image, int eventId, bool toApiBase) {
|
const QByteArray &image, int eventId, bool toApiBase) {
|
||||||
auto *thread = new QThread;
|
auto *thread = new QThread;
|
||||||
@ -327,6 +380,7 @@ bool EventHandler::tryRetry(const QString &key, const EventInfo &event, const QS
|
|||||||
// счётчик отправленных в 0.
|
// счётчик отправленных в 0.
|
||||||
m_screenshots.remove(key);
|
m_screenshots.remove(key);
|
||||||
m_parsedTxCount.remove(key);
|
m_parsedTxCount.remove(key);
|
||||||
|
m_parsedTxJson.remove(key);
|
||||||
|
|
||||||
// Снимаем текущий поток/таймер (в timeout-ветке они уже сняты — take() вернёт null)
|
// Снимаем текущий поток/таймер (в timeout-ветке они уже сняты — take() вернёт null)
|
||||||
if (const auto tm = m_timers.take(key)) {
|
if (const auto tm = m_timers.take(key)) {
|
||||||
@ -397,7 +451,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
.arg(eventTypeToString(event.type), deviceLabel, event.bankName, result);
|
.arg(eventTypeToString(event.type), deviceLabel, event.bankName, result);
|
||||||
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot);
|
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot);
|
||||||
|
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId, event.accountId);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId, event.accountId, event.json);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "[EventHandler] Interrupted — server error report skipped (cause already reported by interrupter), event id:" << event.id;
|
qDebug() << "[EventHandler] Interrupted — server error report skipped (cause already reported by interrupter), event id:" << event.id;
|
||||||
}
|
}
|
||||||
@ -445,6 +499,9 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
EventDAO::markSentToServer(event.id);
|
EventDAO::markSentToServer(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON результата, ушедшего на apiBase — кладём в текст task_success ниже.
|
||||||
|
QString apiResultJson;
|
||||||
|
|
||||||
if (newStatus != EventStatus::Error) {
|
if (newStatus != EventStatus::Error) {
|
||||||
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
const MaterialInfo account = MaterialDAO::getAccountById(event.accountId);
|
||||||
const QMap<QString, int> currencyCodes = {
|
const QMap<QString, int> currencyCodes = {
|
||||||
@ -505,6 +562,8 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
obj["extra"] = extraObj;
|
obj["extra"] = extraObj;
|
||||||
|
|
||||||
taskData = obj;
|
taskData = obj;
|
||||||
|
apiResultJson = QString::fromUtf8(
|
||||||
|
QJsonDocument(obj).toJson(QJsonDocument::Indented));
|
||||||
} else {
|
} else {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj["status"] = "completed";
|
obj["status"] = "completed";
|
||||||
@ -528,8 +587,10 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial;
|
const QString adbSerial = device.adbSerial.isEmpty() ? event.deviceId : device.adbSerial;
|
||||||
|
|
||||||
if (event.type == EventType::CreateTransaction) {
|
if (event.type == EventType::CreateTransaction) {
|
||||||
const QString msg = QStringLiteral("[CREATE_TRANSACTION] bank=%1 material=%2 amount=%3 phone=%4")
|
QString msg = QStringLiteral("[CREATE_TRANSACTION] bank=%1 material=%2 amount=%3 phone=%4")
|
||||||
.arg(event.bankName, event.externalId, QString::number(event.amount, 'f', 2), event.phone);
|
.arg(event.bankName, event.externalId, QString::number(event.amount, 'f', 2), event.phone);
|
||||||
|
if (!apiResultJson.isEmpty())
|
||||||
|
msg += QStringLiteral("\nJSON: %1").arg(apiResultJson);
|
||||||
QByteArray screenshot;
|
QByteArray screenshot;
|
||||||
const QString tmpPath = QDir::tempPath() + "/arc_tx_" + QString::number(event.id);
|
const QString tmpPath = QDir::tempPath() + "/arc_tx_" + QString::number(event.id);
|
||||||
if (AdbUtils::takeScreenshot(event.deviceId, tmpPath)) {
|
if (AdbUtils::takeScreenshot(event.deviceId, tmpPath)) {
|
||||||
@ -541,6 +602,14 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
f.remove();
|
f.remove();
|
||||||
}
|
}
|
||||||
sendMonitoringLog("info", "task_success", msg, screenshot, event.id, /*toApiBase=*/true);
|
sendMonitoringLog("info", "task_success", msg, screenshot, event.id, /*toApiBase=*/true);
|
||||||
|
// Дублируем успех в arc-monitoring (см. sendTaskSuccess) — туда же,
|
||||||
|
// куда уходят ошибки, чтобы видеть и удачные создания транзакций.
|
||||||
|
QString createSummary = QStringLiteral("Amount: %1\nRecipient: %2")
|
||||||
|
.arg(QString::number(event.amount, 'f', 2), event.phone);
|
||||||
|
if (!apiResultJson.isEmpty())
|
||||||
|
createSummary += QStringLiteral("\nJSON: %1").arg(apiResultJson);
|
||||||
|
sendTaskSuccess("CREATE_TRANSACTION", event.externalId, event.bankName,
|
||||||
|
createSummary, screenshot, event.id, event.accountId);
|
||||||
|
|
||||||
// Декрементируем материальный/профильный баланс на сумму расхода + комиссию.
|
// Декрементируем материальный/профильный баланс на сумму расхода + комиссию.
|
||||||
// Между запусками FETCH_PROFILE это держит UI/локальный баланс
|
// Между запусками FETCH_PROFILE это держит UI/локальный баланс
|
||||||
@ -586,10 +655,22 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == EventType::FetchTransactions) {
|
if (event.type == EventType::FetchTransactions) {
|
||||||
const QString msg = QStringLiteral("[FETCH_TRANSACTIONS] bank=%1 material=%2")
|
// JSON для текста: массив транзакций, отправленных потоково на apiBase.
|
||||||
.arg(event.bankName, event.externalId);
|
const QJsonArray txArr = m_parsedTxJson.value(key);
|
||||||
|
const QString txJson = QString::fromUtf8(
|
||||||
|
QJsonDocument(txArr).toJson(QJsonDocument::Indented));
|
||||||
|
QString msg = QStringLiteral("[FETCH_TRANSACTIONS] bank=%1 material=%2 found=%3")
|
||||||
|
.arg(event.bankName, event.externalId, QString::number(txArr.size()));
|
||||||
|
if (!txArr.isEmpty())
|
||||||
|
msg += QStringLiteral("\nJSON: %1").arg(txJson);
|
||||||
const QByteArray scr = m_screenshots.take(key);
|
const QByteArray scr = m_screenshots.take(key);
|
||||||
sendMonitoringLog("info", "task_success", msg, scr, event.id, /*toApiBase=*/true);
|
sendMonitoringLog("info", "task_success", msg, scr, event.id, /*toApiBase=*/true);
|
||||||
|
// Дублируем успех в arc-monitoring с теми же метаданными, что у ошибок.
|
||||||
|
QString fetchSummary = QStringLiteral("Found: %1").arg(txArr.size());
|
||||||
|
if (!txArr.isEmpty())
|
||||||
|
fetchSummary += QStringLiteral("\nJSON: %1").arg(txJson);
|
||||||
|
sendTaskSuccess("FETCH_TRANSACTIONS", event.externalId, event.bankName,
|
||||||
|
fetchSummary, scr, event.id, event.accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == EventType::FetchProfile) {
|
if (event.type == EventType::FetchProfile) {
|
||||||
@ -608,6 +689,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
|||||||
m_threads.remove(key);
|
m_threads.remove(key);
|
||||||
m_retryCount.remove(event.id);
|
m_retryCount.remove(event.id);
|
||||||
m_parsedTxCount.remove(key);
|
m_parsedTxCount.remove(key);
|
||||||
|
m_parsedTxJson.remove(key);
|
||||||
|
|
||||||
// Убиваем банковское приложение на устройстве после завершения задачи
|
// Убиваем банковское приложение на устройстве после завершения задачи
|
||||||
{
|
{
|
||||||
@ -707,6 +789,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
|
|||||||
// ретраить уже частично отработанную задачу.
|
// ретраить уже частично отработанную задачу.
|
||||||
auto streamParsedTx = [this, key, event](const QJsonObject &tx) {
|
auto streamParsedTx = [this, key, event](const QJsonObject &tx) {
|
||||||
m_parsedTxCount[key] = m_parsedTxCount.value(key, 0) + 1;
|
m_parsedTxCount[key] = m_parsedTxCount.value(key, 0) + 1;
|
||||||
|
m_parsedTxJson[key].append(tx); // копим для показа JSON в task_success
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
arr.append(tx);
|
arr.append(tx);
|
||||||
sendTaskResult(eventTypeToString(EventType::FetchTransactions),
|
sendTaskResult(eventTypeToString(EventType::FetchTransactions),
|
||||||
@ -796,7 +879,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
|
|||||||
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")
|
const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout")
|
||||||
.arg(eventTypeToString(event.type), event.bankName, event.externalId);
|
.arg(eventTypeToString(event.type), event.bankName, event.externalId);
|
||||||
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg);
|
GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg);
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, event.id, event.transactionId, event.accountId);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, timeoutMsg, {}, event.id, event.transactionId, event.accountId, event.json);
|
||||||
EventDAO::markSentToServer(event.id);
|
EventDAO::markSentToServer(event.id);
|
||||||
|
|
||||||
// Убиваем банковское приложение на устройстве после таймаута
|
// Убиваем банковское приложение на устройстве после таймаута
|
||||||
@ -833,7 +916,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
|||||||
if (waitSecs > 5 * 60) {
|
if (waitSecs > 5 * 60) {
|
||||||
qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id;
|
qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id;
|
||||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s");
|
EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s");
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, event.id, event.transactionId, event.accountId);
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, event.id, event.transactionId, event.accountId, event.json);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -843,7 +926,7 @@ void EventHandler::processNextTask(const QString &deviceId) {
|
|||||||
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId;
|
||||||
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled");
|
||||||
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
|
sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName,
|
||||||
"Bank profile is disabled: " + account.appCode, {}, event.id, event.transactionId, event.accountId);
|
"Bank profile is disabled: " + account.appCode, {}, event.id, event.transactionId, event.accountId, event.json);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,6 +969,7 @@ void EventHandler::clearAll() {
|
|||||||
m_timers.clear();
|
m_timers.clear();
|
||||||
m_screenshots.clear();
|
m_screenshots.clear();
|
||||||
m_parsedTxCount.clear();
|
m_parsedTxCount.clear();
|
||||||
|
m_parsedTxJson.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandler::stop() {
|
void EventHandler::stop() {
|
||||||
|
|||||||
@ -35,6 +35,7 @@ private:
|
|||||||
QMap<QString, QTimer *> m_timers;
|
QMap<QString, QTimer *> m_timers;
|
||||||
QMap<QString, QByteArray> m_screenshots; // deviceId → скриншот от скрипта
|
QMap<QString, QByteArray> m_screenshots; // deviceId → скриншот от скрипта
|
||||||
QMap<QString, int> m_parsedTxCount; // key → сколько транзакций уже отправлено на сервер (FETCH_TRANSACTIONS, per-tx streaming)
|
QMap<QString, int> m_parsedTxCount; // key → сколько транзакций уже отправлено на сервер (FETCH_TRANSACTIONS, per-tx streaming)
|
||||||
|
QMap<QString, QJsonArray> m_parsedTxJson; // key → сами отправленные tx (для показа JSON в task_success мониторинга)
|
||||||
QMap<int, int> m_retryCount; // event.id → число уже сделанных ретраев (только FETCH_TRANSACTIONS)
|
QMap<int, int> m_retryCount; // event.id → число уже сделанных ретраев (только FETCH_TRANSACTIONS)
|
||||||
QTimer *m_pollTimer = nullptr;
|
QTimer *m_pollTimer = nullptr;
|
||||||
NetworkService *m_network = nullptr;
|
NetworkService *m_network = nullptr;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user