#include "AccountSettingsWindow.h" #include #include #include #include #include #include #include #include #include #include "ozon/LoginAndCheckAccountsScript.h" #include "ozon/GetProfileInfoScript.h" #include "ozon/GetCardInfoScript.h" #include "black/LoginAndCheckAccountsScript.h" #include "black/GetProfileInfoScript.h" #include "black/GetCardInfoScript.h" #include "dushanbe/GetProfileInfoScript.h" #include "dushanbe/GetCardInfoScript.h" #include "dao/BankProfileDAO.h" #include "dao/MaterialDAO.h" #include "net/NetworkService.h" AccountSettingsWindow::AccountSettingsWindow( QWidget *parent, QString deviceId, QString deviceName, const BankProfileInfo &app ) : QWidget(parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::WindowMinMaxButtonsHint), m_deviceId(std::move(deviceId)), m_deviceName(std::move(deviceName)), m_app(app) { setWindowTitle("Настройки банка [" + m_app.name + "]"); setMinimumSize(700, 400); resize(750, 500); auto *mainLayout = new QVBoxLayout(this); auto *scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); auto *content = new QWidget; auto *layout = new QVBoxLayout(content); layout->setContentsMargins(8, 8, 8, 8); layout->setAlignment(Qt::AlignTop); // ── Статус профиля ───────────────────────────────────────────────── auto *statusRow = new QHBoxLayout; statusRow->addWidget(new QLabel("Статус профиля:")); m_statusLabel = new QLabel; m_statusLabel->setTextFormat(Qt::RichText); statusRow->addWidget(m_statusLabel); statusRow->addStretch(); m_toggleBtn = new QPushButton; m_toggleBtn->setFixedWidth(120); connect(m_toggleBtn, &QPushButton::clicked, this, &AccountSettingsWindow::toggleProfileStatus); statusRow->addWidget(m_toggleBtn); layout->addLayout(statusRow); // ── Пин-код ──────────────────────────────────────────────────────── auto *pinRow = new QHBoxLayout; pinRow->addWidget(new QLabel("Пин-код:")); m_pinLabel = new QLabel; m_pinLabel->setTextFormat(Qt::RichText); pinRow->addWidget(m_pinLabel); pinRow->addStretch(); auto *editPinBtn = new QPushButton("Редактировать"); editPinBtn->setStyleSheet("background-color: green; color: white; padding: 4px 12px;"); connect(editPinBtn, &QPushButton::clicked, this, &AccountSettingsWindow::editPinCode); pinRow->addWidget(editPinBtn); auto *checkPinBtn = new QPushButton("Проверить пин-код"); checkPinBtn->setStyleSheet("background-color: orange; color: white; padding: 4px 12px;"); connect(checkPinBtn, &QPushButton::clicked, this, &AccountSettingsWindow::startCheckPinCode); pinRow->addWidget(checkPinBtn); layout->addLayout(pinRow); // ── Профиль ──────────────────────────────────────────────────────── m_profileLabel = new QLabel; m_profileLabel->setTextFormat(Qt::RichText); m_profileLabel->setStyleSheet("margin: 4px 0;"); layout->addWidget(m_profileLabel); // ── Кнопки обновления ────────────────────────────────────────────── auto *actionsRow = new QHBoxLayout; m_profileBtn = new QPushButton("Обновить профиль"); m_profileBtn->setStyleSheet("background-color: #1565C0; color: white; padding: 4px 12px;"); connect(m_profileBtn, &QPushButton::clicked, this, &AccountSettingsWindow::startGetProfileInfo); actionsRow->addWidget(m_profileBtn); m_cardsBtn = new QPushButton("Проверить аккаунты"); m_cardsBtn->setStyleSheet("background-color: #1565C0; color: white; padding: 4px 12px;"); connect(m_cardsBtn, &QPushButton::clicked, this, &AccountSettingsWindow::startGetCardInfo); actionsRow->addWidget(m_cardsBtn); actionsRow->addStretch(); layout->addLayout(actionsRow); // ── Таблица карт ─────────────────────────────────────────────────── layout->addSpacing(8); layout->addWidget(new QLabel("Счета")); m_cardsTable = new QTableWidget; m_cardsTable->setSelectionMode(QAbstractItemView::NoSelection); m_cardsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); m_cardsTable->setFocusPolicy(Qt::NoFocus); layout->addWidget(m_cardsTable); content->setLayout(layout); scrollArea->setWidget(content); mainLayout->addWidget(scrollArea); // Overlay (скрыт по умолчанию) m_overlay = new QWidget(this); m_overlay->setStyleSheet("background-color: rgba(0, 0, 0, 150);"); m_overlay->hide(); m_overlayText = new QLabel(m_overlay); m_overlayText->setAlignment(Qt::AlignCenter); m_overlayText->setStyleSheet("color: white; font-size: 16px; font-weight: bold;"); auto *overlayLayout = new QVBoxLayout(m_overlay); overlayLayout->addWidget(m_overlayText); m_overlayTimer = new QTimer(this); m_overlayTimer->setSingleShot(true); updateStatusUI(); reloadCards(); } // ── Overlay ──────────────────────────────────────────────────────────────── void AccountSettingsWindow::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); if (m_overlay) m_overlay->setGeometry(rect()); } void AccountSettingsWindow::showOverlay(const QString &text) { m_overlayText->setText(text); m_overlay->setGeometry(rect()); m_overlay->raise(); m_overlay->show(); } void AccountSettingsWindow::hideOverlay(bool success, const QString &message, std::function onHidden) { m_overlayText->setText(message); m_overlayText->setStyleSheet(success ? "color: #4CAF50; font-size: 16px; font-weight: bold;" : "color: #F44336; font-size: 16px; font-weight: bold;"); m_overlayTimer->disconnect(); connect(m_overlayTimer, &QTimer::timeout, this, [this, onHidden]() { m_overlay->hide(); m_overlayText->setStyleSheet("color: white; font-size: 16px; font-weight: bold;"); if (onHidden) onHidden(); }); m_overlayTimer->start(1500); } // ── UI update ────────────────────────────────────────────────────────────── void AccountSettingsWindow::updateStatusUI() { // Статус профиля const bool active = m_app.status == "active"; m_statusLabel->setText(active ? "Активен" : "Неактивен"); m_toggleBtn->setText(active ? "Выключить" : "Включить"); m_toggleBtn->setStyleSheet(active ? "background-color: red; color: white; padding: 4px 12px;" : "background-color: green; color: white; padding: 4px 12px;"); // Пин-код const QString pinDisplay = m_app.pinCode.isEmpty() ? "не задан" : m_app.pinCode; const QString pinStatus = m_app.pinCodeChecked ? "" + pinDisplay + " (проверен)" : "" + pinDisplay + " (не проверен)"; m_pinLabel->setText(pinStatus); // Профиль if (m_app.fullName.isEmpty() && m_app.phone.isEmpty()) { m_profileLabel->setText("Данные профиля не получены"); } else { QString text; if (!m_app.fullName.isEmpty()) text += m_app.fullName; if (!m_app.phone.isEmpty()) text += " | " + m_app.phone; if (!m_app.email.isEmpty()) text += " | " + m_app.email; const QString syncText = m_app.bankProfileId.isEmpty() ? " ● Не синхронизирован" : " ● Синхронизирован"; m_profileLabel->setText(text + syncText); } } // ── Toggle On/Off ────────────────────────────────────────────────────────── void AccountSettingsWindow::toggleProfileStatus() { const bool isActive = m_app.status == "active"; const QString newStatus = isActive ? "off" : "active"; // Если включаем — проверяем что есть хотя бы одна активная карта if (!isActive) { const QList materials = MaterialDAO::getAccounts(m_deviceId, m_app.code); bool hasActive = false; for (const auto &m : materials) { if (m.status == MaterialStatus::Active) { hasActive = true; break; } } if (!hasActive) { QMessageBox::warning(this, "Невозможно включить", "Ни одна карта не активна.\nВключите хотя бы одну карту перед включением профиля."); return; } } if (!BankProfileDAO::update(m_app.id, m_app.pinCode, m_app.comment, newStatus)) return; m_app.status = newStatus; showOverlay(isActive ? "Выключение профиля..." : "Включение профиля..."); sendAppStatus(newStatus); } // ── Edit PIN ─────────────────────────────────────────────────────────────── void AccountSettingsWindow::editPinCode() { bool ok; const QString pin = QInputDialog::getText(this, "Пин-код", "Введите пин-код:", QLineEdit::Normal, m_app.pinCode, &ok); if (!ok) return; if (BankProfileDAO::update(m_app.id, pin, m_app.comment, m_app.status)) { m_app.pinCode = pin; m_app.pinCodeChecked = false; BankProfileDAO::updatePinCodeStatus(m_app.id, "no", ""); updateStatusUI(); } } // ── Check PIN ────────────────────────────────────────────────────────────── void AccountSettingsWindow::startCheckPinCode() { if (m_app.pinCode.isEmpty()) { QMessageBox::warning(this, "Пин-код не задан", "Сначала укажите пин-код через кнопку \"Редактировать\"."); return; } auto *thread = new QThread(nullptr); const QString deviceId = m_deviceId; const QString appCode = m_app.code; const int appId = m_app.id; auto onResult = [this, appId](const QString &result) { if (result.isEmpty()) { BankProfileDAO::updatePinCodeStatus(appId, "yes", ""); m_app.pinCodeChecked = true; QMetaObject::invokeMethod(this, [this]() { updateStatusUI(); }, Qt::QueuedConnection); } }; if (appCode == "ozon") { auto *worker = new Ozon::LoginAndCheckAccountsScript(deviceId, appCode, nullptr, false, true); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start); connect(worker, &Ozon::LoginAndCheckAccountsScript::finishedWithResult, this, onResult, Qt::QueuedConnection); connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, thread, &QThread::quit); connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else if (appCode == "black") { auto *worker = new Black::LoginAndCheckAccountsScript(deviceId, appCode, nullptr, true); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start); connect(worker, &Black::LoginAndCheckAccountsScript::finishedWithResult, this, onResult, Qt::QueuedConnection); connect(worker, &Black::LoginAndCheckAccountsScript::finished, thread, &QThread::quit); connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else { thread->deleteLater(); return; } thread->start(); } // ── Get Profile ──────────────────────────────────────────────────────────── void AccountSettingsWindow::startGetProfileInfo() { m_profileBtn->setEnabled(false); m_cardsBtn->setEnabled(false); auto *thread = new QThread(nullptr); const QString deviceId = m_deviceId; const QString appCode = m_app.code; auto onDone = [this, appCode]() { QMetaObject::invokeMethod(this, [this, appCode]() { m_profileBtn->setEnabled(true); m_cardsBtn->setEnabled(true); m_app = BankProfileDAO::getApplication(m_deviceId, appCode); updateStatusUI(); }, Qt::QueuedConnection); }; if (appCode == "ozon") { auto *worker = new Ozon::GetProfileInfoScript(deviceId, appCode, nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Ozon::GetProfileInfoScript::start); connect(worker, &Ozon::GetProfileInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Ozon::GetProfileInfoScript::finished, thread, &QThread::quit); connect(worker, &Ozon::GetProfileInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else if (appCode == "black") { auto *worker = new Black::GetProfileInfoScript(deviceId, appCode, nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Black::GetProfileInfoScript::start); connect(worker, &Black::GetProfileInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Black::GetProfileInfoScript::finished, thread, &QThread::quit); connect(worker, &Black::GetProfileInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else if (appCode == "dushanbe_city_bank") { auto *worker = new Dushanbe::GetProfileInfoScript(deviceId, appCode, nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Dushanbe::GetProfileInfoScript::start); connect(worker, &Dushanbe::GetProfileInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Dushanbe::GetProfileInfoScript::finished, thread, &QThread::quit); connect(worker, &Dushanbe::GetProfileInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else { thread->deleteLater(); return; } thread->start(); } // ── Get Cards ────────────────────────────────────────────────────────────── void AccountSettingsWindow::startGetCardInfo() { m_profileBtn->setEnabled(false); m_cardsBtn->setEnabled(false); auto *thread = new QThread(nullptr); const QString deviceId = m_deviceId; const QString appCode = m_app.code; auto onDone = [this]() { QMetaObject::invokeMethod(this, [this]() { m_profileBtn->setEnabled(true); m_cardsBtn->setEnabled(true); reloadCards(); }, Qt::QueuedConnection); }; if (appCode == "ozon") { auto *worker = new Ozon::GetCardInfoScript(deviceId, appCode, "", nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Ozon::GetCardInfoScript::start); connect(worker, &Ozon::GetCardInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Ozon::GetCardInfoScript::finished, thread, &QThread::quit); connect(worker, &Ozon::GetCardInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else if (appCode == "black") { auto *worker = new Black::GetCardInfoScript(deviceId, appCode, nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Black::GetCardInfoScript::start); connect(worker, &Black::GetCardInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Black::GetCardInfoScript::finished, thread, &QThread::quit); connect(worker, &Black::GetCardInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else if (appCode == "dushanbe_city_bank") { auto *worker = new Dushanbe::GetCardInfoScript(deviceId, appCode, nullptr); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Dushanbe::GetCardInfoScript::start); connect(worker, &Dushanbe::GetCardInfoScript::finished, this, onDone, Qt::QueuedConnection); connect(worker, &Dushanbe::GetCardInfoScript::finished, thread, &QThread::quit); connect(worker, &Dushanbe::GetCardInfoScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else { thread->deleteLater(); return; } thread->start(); } // ── Send status to server ────────────────────────────────────────────────── void AccountSettingsWindow::sendAppStatus(const QString &status) { if (m_app.bankProfileId.isEmpty()) { qWarning() << "[AccountSettings] sendAppStatus: no bankProfileId for" << m_app.code; hideOverlay(true, "Сохранено локально", [this]() { updateStatusUI(); }); return; } const bool enable = (status == "active"); auto *thread = new QThread; auto *net = new NetworkService; net->moveToThread(thread); net->addExtra("bank_profile_id", m_app.bankProfileId); net->addExtra("enable", enable); connect(thread, &QThread::started, net, &NetworkService::toggleBankProfile); connect(net, &NetworkService::finishedWithResult, this, [this](int result) { if (result == 0) { hideOverlay(true, "Успешно", [this]() { updateStatusUI(); }); } else { hideOverlay(false, "Ошибка отправки на сервер", [this]() { updateStatusUI(); }); } }, Qt::QueuedConnection); connect(net, &NetworkService::finished, thread, &QThread::quit); connect(net, &NetworkService::finished, net, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } // ── Toggle card status ───────────────────────────────────────────────────── void AccountSettingsWindow::toggleMaterialStatus(const MaterialInfo &account) { const bool isActive = account.status == MaterialStatus::Active; const QString newStatus = isActive ? "off" : "active"; if (!MaterialDAO::updateStatus(account.id, newStatus)) { qWarning() << "[AccountSettings] Failed to update material status:" << account.id; return; } showOverlay(isActive ? "Выключение карты..." : "Включение карты..."); auto finishToggle = [this, isActive]() { // Если выключили — проверяем остались ли активные if (isActive) { const QList materials = MaterialDAO::getAccounts(m_deviceId, m_app.code); bool hasActive = false; for (const auto &m : materials) { if (m.status == MaterialStatus::Active) { hasActive = true; break; } } if (!hasActive && m_app.status == "active") { BankProfileDAO::update(m_app.id, m_app.pinCode, m_app.comment, "off"); m_app.status = "off"; sendAppStatus("off"); return; // sendAppStatus покажет свой overlay } } reloadCards(); }; if (!account.materialId.isEmpty()) { auto *thread = new QThread; auto *net = new NetworkService; net->moveToThread(thread); net->addExtra("material_id", account.materialId); net->addExtra("enable", !isActive); connect(thread, &QThread::started, net, &NetworkService::toggleMaterial); connect(net, &NetworkService::finishedWithResult, this, [this, finishToggle](int result) { if (result == 0) { hideOverlay(true, "Успешно", finishToggle); } else { hideOverlay(false, "Ошибка отправки на сервер", finishToggle); } }, Qt::QueuedConnection); connect(net, &NetworkService::finished, thread, &QThread::quit); connect(net, &NetworkService::finished, net, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } else { hideOverlay(true, "Сохранено локально", finishToggle); } } // ── Cards table ──────────────────────────────────────────────────────────── void AccountSettingsWindow::reloadCards() { const QList accounts = MaterialDAO::getAccounts(m_deviceId, m_app.code); m_cardsTable->clearContents(); m_cardsTable->setRowCount(accounts.size()); m_cardsTable->setColumnCount(7); m_cardsTable->setHorizontalHeaderLabels({ "Сервер", "Статус", "Время обновления", "Номер", "Сумма", "Валюта", "" }); m_cardsTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); for (int k = 0; k < accounts.size(); ++k) { const MaterialInfo &acc = accounts[k]; // Сервер auto *syncItem = new QTableWidgetItem(acc.materialId.isEmpty() ? "Не синхр." : "Синхр."); syncItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); syncItem->setForeground(acc.materialId.isEmpty() ? QBrush(Qt::red) : QBrush(Qt::darkGreen)); m_cardsTable->setItem(k, 0, syncItem); // Статус const bool isActive = acc.status == MaterialStatus::Active; auto *statusItem = new QTableWidgetItem(materialStatusToUiString(acc.status)); statusItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); statusItem->setForeground(isActive ? QBrush(Qt::darkGreen) : QBrush(Qt::red)); m_cardsTable->setItem(k, 1, statusItem); // Время обновления auto *timeItem = new QTableWidgetItem(acc.updateTime.toString("dd.MM.yyyy HH:mm:ss")); 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); m_cardsTable->setItem(k, 3, numItem); // Сумма auto *amountItem = new QTableWidgetItem(QString::number(acc.amount)); amountItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_cardsTable->setItem(k, 4, amountItem); // Валюта auto *currItem = new QTableWidgetItem(acc.currency); currItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_cardsTable->setItem(k, 5, currItem); // Кнопка вкл/выкл auto *toggleBtn = new QPushButton(isActive ? "Выключить" : "Включить", m_cardsTable); toggleBtn->setStyleSheet(isActive ? "background-color: orange; color: white;" : "background-color: green; color: white;"); m_cardsTable->setCellWidget(k, 6, toggleBtn); connect(toggleBtn, &QPushButton::clicked, this, [this, acc]() { toggleMaterialStatus(acc); }); } m_cardsTable->resizeColumnsToContents(); m_cardsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); m_cardsTable->resizeRowsToContents(); }