From 3751406c361f3d95d85d5efeee9b3c19ef41390e Mon Sep 17 00:00:00 2001 From: slava Date: Wed, 13 May 2026 02:03:20 +0700 Subject: [PATCH] Add PAN field detection and account number UI updates. - Extend `ScreenXmlParser` with `findPanCardNumberInput` for detecting empty PAN fields in Ozon WebView. - Update `PayByCardScript` to handle missing hints for recipient card input. - Temporarily disable advanced account number parsing in `GetCardInfoScript`. - Add `patchMaterial` method to `NetworkService` for updating account details. - Update card management UI with refined "Switch" button logic and label changes. --- android/dushanbe/GetCardInfoScript.cpp | 139 ++++++++--------- android/ozon/GetCardInfoScript.cpp | 197 +++++++++++++------------ android/ozon/PayByCardScript.cpp | 6 + android/ozon/ScreenXmlParser.cpp | 41 +++++ android/ozon/ScreenXmlParser.h | 6 + services/net/NetworkService.cpp | 35 +++++ services/net/NetworkService.h | 2 + views/bank/AccountSettingsWindow.cpp | 23 +-- 8 files changed, 271 insertions(+), 178 deletions(-) diff --git a/android/dushanbe/GetCardInfoScript.cpp b/android/dushanbe/GetCardInfoScript.cpp index 1020128..07ae89e 100644 --- a/android/dushanbe/GetCardInfoScript.cpp +++ b/android/dushanbe/GetCardInfoScript.cpp @@ -162,76 +162,77 @@ void GetCardInfoScript::doStart() { } // Достаём номер счёта: Настройки карты → Реквизиты карты → таб "Реквизиты счета". + // ВРЕМЕННО ОТКЛЮЧЕНО 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; - } + // 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; diff --git a/android/ozon/GetCardInfoScript.cpp b/android/ozon/GetCardInfoScript.cpp index bc98deb..aaf0ea4 100644 --- a/android/ozon/GetCardInfoScript.cpp +++ b/android/ozon/GetCardInfoScript.cpp @@ -415,103 +415,104 @@ 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; - } - } - } - } - } + // ВРЕМЕННО ОТКЛЮЧЕНО + // 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); @@ -530,7 +531,7 @@ void GetCardInfoScript::doStart() { QJsonObject materialBody; materialBody["bank_profile_id"] = updatedApp.bankProfileId; materialBody["card_number"] = dbCard.cardNumber; - // materialBody["account_number"] = dbCard.accountNumber; + 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); diff --git a/android/ozon/PayByCardScript.cpp b/android/ozon/PayByCardScript.cpp index 12a80b4..2509f0f 100644 --- a/android/ozon/PayByCardScript.cpp +++ b/android/ozon/PayByCardScript.cpp @@ -248,6 +248,12 @@ void PayByCardScript::makePayment( for (int attempt = 0; attempt < 10; ++attempt) { if (!checkApp()) return; cardInput = xmlScreenParser.findEditTextByHint("Номер карты получателя"); + if (cardInput.isEmpty()) { + // На части сборок Ozon WebView hint у поля PAN пустой, а лейбла + // "Номер карты получателя" на экране нет (есть только "КУДА"). + // Поле PAN всегда внутри элемента resource-id="movable-pan-iframe". + cardInput = xmlScreenParser.findPanCardNumberInput(); + } if (!cardInput.isEmpty()) break; qDebug() << "[PayByCard] Waiting for 'Номер карты получателя' input, attempt" << attempt + 1 << "/ 10"; QThread::msleep(1500); diff --git a/android/ozon/ScreenXmlParser.cpp b/android/ozon/ScreenXmlParser.cpp index b98b32a..eccb5ed 100644 --- a/android/ozon/ScreenXmlParser.cpp +++ b/android/ozon/ScreenXmlParser.cpp @@ -176,6 +176,47 @@ Node ScreenXmlParser::findEditTextByHint(const QString &hintPrefix) { return best; } +static QDomElement findPanIframeElement(const QDomElement &root) { + if (root.isNull()) return {}; + if (root.attribute("resource-id") == "movable-pan-iframe") return root; + for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { + QDomElement found = findPanIframeElement(child); + if (!found.isNull()) return found; + } + return {}; +} + +static QDomElement findFirstEditTextDescendant(const QDomElement &root) { + if (root.isNull()) return {}; + if (root.attribute("class") == "android.widget.EditText") return root; + for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { + QDomElement found = findFirstEditTextDescendant(child); + if (!found.isNull()) return found; + } + return {}; +} + +Node ScreenXmlParser::findPanCardNumberInput() { + const QDomElement iframe = findPanIframeElement(m_xml); + if (iframe.isNull()) return {}; + const QDomElement edit = findFirstEditTextDescendant(iframe); + if (edit.isNull()) return {}; + + Node node; + if (QRegularExpressionMatch m = boundsRegex.match(edit.attribute("bounds")); m.hasMatch()) { + node.setBounds(m.captured(1).toInt(), m.captured(2).toInt(), + m.captured(3).toInt(), m.captured(4).toInt()); + } + node.className = edit.attribute("class"); + node.text = edit.attribute("text"); + node.contentDesc = edit.attribute("content-desc"); + node.resourceId = edit.attribute("resource-id"); + node.hint = edit.attribute("hint"); + node.clickable = edit.attribute("clickable") == "true"; + node.focusable = edit.attribute("focusable") == "true"; + return node; +} + Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) { for (const Node &node: m_nodes) { if (contains) { diff --git a/android/ozon/ScreenXmlParser.h b/android/ozon/ScreenXmlParser.h index 04df241..b6f0089 100644 --- a/android/ozon/ScreenXmlParser.h +++ b/android/ozon/ScreenXmlParser.h @@ -30,6 +30,12 @@ public: Node findEditTextByHint(const QString &hintPrefix); + // Возвращает EditText внутри Ozon "movable-pan-iframe" — поле номера карты + // получателя на экране "Перевод по номеру карты". Нужно как fallback, когда + // WebView не отдаёт hint и на экране нет TextView-лейбла "Номер карты + // получателя" (лейбл — просто "КУДА"). + Node findPanCardNumberInput(); + Node findButtonNode(const QString &text, bool contains); QList findAllButtons(); diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp index 6b35aba..fc2c704 100644 --- a/services/net/NetworkService.cpp +++ b/services/net/NetworkService.cpp @@ -1010,6 +1010,41 @@ void NetworkService::patchBankProfile() { emit finished(); } +void NetworkService::patchMaterial() { + if (!ensureValidToken()) { emit finishedWithResult(-1); emit finished(); return; } + + const QString materialId = m_extraData.value("material_id").toString(); + if (materialId.isEmpty()) { + qWarning() << "[NetworkService] patchMaterial: material_id is empty"; + emit finishedWithResult(-1); emit finished(); + return; + } + + QJsonObject body; + if (m_extraData.contains("account_number")) { + body["account_number"] = m_extraData.value("account_number").toString(); + } + if (m_extraData.contains("state")) { + body["state"] = m_extraData.value("state").toString(); + } + + const QString path = m_apiBase + m_pathMaterial + "/" + materialId; + const QByteArray bodyJson = QJsonDocument(body).toJson(QJsonDocument::Indented); + qDebug().noquote() << "[NetworkService] patchMaterial PATCH" << path + << "\nbody:\n" << bodyJson; + + const QJsonValue result = patchJson(path, body, true); + if (result.isNull() || result.isUndefined()) { + qWarning() << "[NetworkService] patchMaterial failed:" << materialId; + emit finishedWithResult(-1); + } else { + qDebug() << "[NetworkService] patchMaterial done:" << materialId; + emit finishedWithResult(0); + } + + emit finished(); +} + void NetworkService::toggleMaterial() { if (!ensureValidToken()) { emit finishedWithResult(-1); emit finished(); return; } diff --git a/services/net/NetworkService.h b/services/net/NetworkService.h index 53df775..d551118 100644 --- a/services/net/NetworkService.h +++ b/services/net/NetworkService.h @@ -32,6 +32,8 @@ public slots: void postMaterial(); + void patchMaterial(); + void toggleMaterial(); void postDeviceScreenshot(); diff --git a/views/bank/AccountSettingsWindow.cpp b/views/bank/AccountSettingsWindow.cpp index 56340ff..71c48ff 100644 --- a/views/bank/AccountSettingsWindow.cpp +++ b/views/bank/AccountSettingsWindow.cpp @@ -97,6 +97,7 @@ AccountSettingsWindow::AccountSettingsWindow( m_cardsBtn = new QPushButton("Проверить аккаунты"); m_cardsBtn->setStyleSheet("background-color: #1565C0; color: white; padding: 4px 12px;"); connect(m_cardsBtn, &QPushButton::clicked, this, &AccountSettingsWindow::startGetCardInfo); + m_cardsBtn->hide(); actionsRow->addWidget(m_cardsBtn); actionsRow->addStretch(); @@ -572,7 +573,7 @@ void AccountSettingsWindow::reloadCards() { m_cardsTable->setRowCount(accounts.size()); m_cardsTable->setColumnCount(7); m_cardsTable->setHorizontalHeaderLabels({ - "Сервер", "Статус", "Время обновления", "Номер", "Сумма", "Валюта", "" + "Сервер", "Статус", "Время обновления", "Номер карты", "Сумма", "Валюта", "" }); m_cardsTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); @@ -597,7 +598,7 @@ void AccountSettingsWindow::reloadCards() { timeItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_cardsTable->setItem(k, 2, timeItem); - // Номер + // Номер карты const QString displayNumber = acc.cardNumber.isEmpty() ? acc.lastNumbers : acc.cardNumber; auto *numItem = new QTableWidgetItem(displayNumber); numItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); @@ -613,16 +614,16 @@ void AccountSettingsWindow::reloadCards() { currItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_cardsTable->setItem(k, 5, currItem); - // Кнопка вкл/выкл - auto *toggleBtn = new QPushButton(isActive ? "Выключить" : "Включить", m_cardsTable); - toggleBtn->setStyleSheet(isActive - ? "background-color: red; color: white;" - : "background-color: green; color: white;"); - m_cardsTable->setCellWidget(k, 6, toggleBtn); + // Кнопка переключения: только для неактивных — включит этот и выключит остальные + if (!isActive) { + auto *toggleBtn = new QPushButton("Переключить", m_cardsTable); + toggleBtn->setStyleSheet("background-color: green; color: white;"); + m_cardsTable->setCellWidget(k, 6, toggleBtn); - connect(toggleBtn, &QPushButton::clicked, this, [this, acc]() { - toggleMaterialStatus(acc); - }); + connect(toggleBtn, &QPushButton::clicked, this, [this, acc]() { + toggleMaterialStatus(acc); + }); + } } m_cardsTable->resizeColumnsToContents();