1
0
forked from BRT/arc
arc/views/bank/AccountSettingsWindow.cpp
slava 58513eb15a - Add buttons for profile and card operations to AccountSettingsWindow with threading integration and UI state management.
- Implement `Dushanbe` automation for `GetProfileInfoScript` and `GetCardInfoScript`.
- Add device validation in `DeviceDAO` and enhance duplicate checks in `NetworkService` during synchronization.
2026-04-06 14:59:00 +07:00

540 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "AccountSettingsWindow.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QScrollArea>
#include <QCoreApplication>
#include <QMessageBox>
#include <QInputDialog>
#include <QThread>
#include <QTimer>
#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("<b>Счета</b>"));
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<void()> 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
? "<span style='color:green;'>Активен</span>"
: "<span style='color:red;'>Неактивен</span>");
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
? "<span style='color:green;'>" + pinDisplay + " (проверен)</span>"
: "<span style='color:red;'>" + pinDisplay + " (не проверен)</span>";
m_pinLabel->setText(pinStatus);
// Профиль
if (m_app.fullName.isEmpty() && m_app.phone.isEmpty()) {
m_profileLabel->setText("<span style='color:gray;'>Данные профиля не получены</span>");
} 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()
? " <span style='color:red;'>&#9679; Не синхронизирован</span>"
: " <span style='color:green;'>&#9679; Синхронизирован</span>";
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<MaterialInfo> 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<MaterialInfo> 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<MaterialInfo> 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();
}