From 62442721193576ddd1516c8df0611918fa23a63f Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 3 Nov 2025 11:12:15 +0100 Subject: [PATCH 01/52] Add libshv dependency to enable QxEvent service implementation --- .clang-tidy | 1 + .gitmodules | 3 + 3rdparty/libshv | 1 + CMakeLists.txt | 4 + .../libqfgui/src/framework/datadialogwidget.h | 2 +- quickevent/app/quickevent/CMakeLists.txt | 8 + .../Event/src/services/qx/qxclientservice.cpp | 187 ++++++++++++++---- .../Event/src/services/qx/qxclientservice.h | 16 ++ .../Event/src/services/serviceswidget.cpp | 7 +- .../Event/src/services/servicewidget.cpp | 11 +- .../Event/src/services/servicewidget.h | 2 +- 11 files changed, 188 insertions(+), 54 deletions(-) create mode 160000 3rdparty/libshv diff --git a/.clang-tidy b/.clang-tidy index fd21b3531..64c3c28fa 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -40,6 +40,7 @@ Checks: -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-enum-class -hicpp-avoid-c-arrays, -hicpp-avoid-goto, -hicpp-braces-around-statements, diff --git a/.gitmodules b/.gitmodules index 6e2bec6bb..5006631e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "3rdparty/necrolog"] path = 3rdparty/necrolog url = https://github.com/fvacek/necrolog.git +[submodule "3rdparty/libshv"] + path = 3rdparty/libshv + url = https://github.com/silicon-heaven/libshv.git diff --git a/3rdparty/libshv b/3rdparty/libshv new file mode 160000 index 000000000..3cbe496ea --- /dev/null +++ b/3rdparty/libshv @@ -0,0 +1 @@ +Subproject commit 3cbe496ea4d2c48695994850a09c2cc794c3f80b diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a214da86..b5b73faa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.18.4) set(QF_BUILD_QML_PLUGINS ON CACHE BOOL "Build with QML Plugins support") +set(QF_WITH_LIBSHV OFF CACHE BOOL "Build with libshv") project(quickbox LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) @@ -34,6 +35,9 @@ endif (WIN32) if (NOT TARGET libnecrolog) add_subdirectory(3rdparty/necrolog) endif() +if (QF_WITH_LIBSHV) + add_subdirectory(3rdparty/libshv) +endif() set(USE_QT6 ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Sql Qml Xml LinguistTools PrintSupport Svg SerialPort Multimedia Network) diff --git a/libqf/libqfgui/src/framework/datadialogwidget.h b/libqf/libqfgui/src/framework/datadialogwidget.h index 37c4c1d91..ec4cca8e0 100644 --- a/libqf/libqfgui/src/framework/datadialogwidget.h +++ b/libqf/libqfgui/src/framework/datadialogwidget.h @@ -26,7 +26,7 @@ class QFGUI_DECL_EXPORT DataDialogWidget : public DialogWidget qf::gui::model::DataDocument* dataDocument(bool throw_exc = qf::core::Exception::Throw); - Q_SLOT virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); + virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); bool acceptDialogDone(int result) Q_DECL_OVERRIDE; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b6218790e..13549e64f 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -273,6 +273,14 @@ qt6_add_lupdate(quickevent TS_FILES target_sources(quickevent PRIVATE ${QM_FILES}) target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) +if (QF_WITH_LIBSHV) + target_link_libraries(quickevent PUBLIC libshviotqt) + target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) +endif() + +foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) + install(DIRECTORY plugins/${plugin}/qml/reports DESTINATION ${CMAKE_INSTALL_BINDIR}/reports/${plugin}/qml) +endforeach() # Extract version from appversion.h file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/appversion.h" APP_VERSION_H) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp index 502483522..7a9acb8fd 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp @@ -9,6 +9,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -24,6 +28,10 @@ #include #include +using namespace shv::chainpack; +using namespace shv::iotqt::rpc; +using namespace shv::iotqt::node; + using namespace qf::core; using namespace qf::gui; using namespace qf::gui::dialogs; @@ -66,28 +74,41 @@ QString QxClientService::serviceId() } void QxClientService::run() { + using namespace shv::iotqt::rpc; + auto ss = settings(); - auto *reply = getRemoteEventInfo(ss.exchangeServerUrl(), apiToken()); - connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); - m_eventId = event_info.id(); - connectToSSE(m_eventId); - if (!m_pollChangesTimer) { - m_pollChangesTimer = new QTimer(this); - connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); - } - pollQxChanges(); - m_pollChangesTimer->start(10000); - Super::run(); - } - else { - qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); - } - }); + + delete m_rpcConnection; + m_rpcConnection = new ClientConnection(this); + m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); + + connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxClientService::onBrokerConnectedChanged); + connect(m_rpcConnection, &ClientConnection::socketError, this, &QxClientService::onBrokerSocketError); + connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxClientService::onBrokerLoginError); + connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxClientService::onRpcMessageReceived); + + m_rpcConnection->open(); + +// connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { +// if (reply->error() == QNetworkReply::NetworkError::NoError) { +// auto data = reply->readAll(); +// auto doc = QJsonDocument::fromJson(data); +// EventInfo event_info(doc.toVariant().toMap()); +// setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); +// m_eventId = event_info.id(); +// connectToSSE(m_eventId); +// if (!m_pollChangesTimer) { +// m_pollChangesTimer = new QTimer(this); +// connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); +// } +// pollQxChanges(); +// m_pollChangesTimer->start(10000); +// Super::run(); +// } +// else { +// qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); +// } +// }); } void QxClientService::stop() @@ -537,35 +558,115 @@ EventInfo QxClientService::eventInfo() const // qfInfo() << qf::core::Utils::qvariantToJson(ei, false); return ei; } -/* -namespace { -auto query_to_json_csv(QSqlQuery &q) +//namespace { +//auto query_to_json_csv(QSqlQuery &q) +//{ +// QVariantList csv; +// { +// // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; +// QStringList columns; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// columns << rec.field(i).name(); +// } +// csv.insert(csv.length(), columns); +// } +// while (q.next()) { +// QVariantList values; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// values << q.value(i); +// } +// csv.insert(csv.length(), values); +// } +// return csv; +//} +//} +int QxClientService::currentConnectionId() { - QVariantList csv; - { - // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; - QStringList columns; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - columns << rec.field(i).name(); - } - csv.insert(csv.length(), columns); + return qf::core::sql::Connection::forName().connectionId(); +} + +void QxClientService::onBrokerConnectedChanged(bool is_connected) +{ + if(is_connected) { + setStatus(Status::Running); + QTimer::singleShot(0, this, [this]() { + subscribeChanges(); +// testRpcCall(); + }); + } else { + setStatus(Status::Stopped); } - while (q.next()) { - QVariantList values; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - values << q.value(i); + +} + +void QxClientService::onBrokerSocketError(const QString &err) +{ + setStatusMessage(tr("Broker socket error: %1").arg(err)); +} + +void QxClientService::onBrokerLoginError(const shv::chainpack::RpcError &err) +{ + setStatusMessage(tr("Broker login error: %1").arg(err.toString())); +} + +void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) +{ +// shvLogFuncFrame() << msg.toCpon(); + if(msg.isRequest()) { + RpcRequest rq(msg); + qfMessage() << "RPC request received:" << rq.toPrettyString(); + if(m_shvTree->root()) { + m_shvTree->root()->handleRpcRequest(rq); } - csv.insert(csv.length(), values); } - return csv; + else if(msg.isResponse()) { + RpcResponse rp(msg); + qfMessage() << "RPC response received:" << rp.toPrettyString(); + } + else if(msg.isSignal()) { + RpcSignal nt(msg); + qfMessage() << "RPC signal received:" << nt.toPrettyString(); + } } + +void QxClientService::subscribeChanges() +{ + Q_ASSERT(m_rpcConnection); + QString shv_path = "test"; + QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; + auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); + connect(rpc_call, &RpcCall::maybeResult, this, [this, shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if(error.isValid()) { + qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); + } + else { + qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); + // generate data change without ret value check + m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 123); + QTimer::singleShot(500, this, [this, shv_path]() { + m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 321); + }); + } + }); + rpc_call->start(); } -*/ -int QxClientService::currentConnectionId() + +void QxClientService::testRpcCall() const { - return qf::core::sql::Connection::forName().connectionId(); + Q_ASSERT(m_rpcConnection); + auto *rpc_call = RpcCall::create(m_rpcConnection) + ->setShvPath("test") + ->setMethod("ls"); +// ->setTimeout(5000); + connect(rpc_call, &RpcCall::maybeResult, [](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if(error.isValid()) + qfError() << "RPC call error:" << error.toString(); + else + qfInfo() << "Got RPC response, result:" << result.toCpon(); + }); + rpc_call->start(); } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h index 636a54c4d..7e1f046be 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h @@ -7,6 +7,10 @@ class QNetworkReply; class QUrlQuery; class QTimer; +namespace shv::iotqt::node { class ShvNodeTree; } +namespace shv::iotqt::rpc { class ClientConnection; } +namespace shv::chainpack { class RpcMessage; class RpcError; } + namespace Event::services::qx { class QxClientServiceSettings : public ServiceSettings @@ -68,6 +72,14 @@ class QxClientService : public Service QUrl exchangeServerUrl() const; int eventId() const; +private: // shv + void onBrokerConnectedChanged(bool is_connected); + void onRpcMessageReceived(const shv::chainpack::RpcMessage &msg); + void onBrokerSocketError(const QString &err); + void onBrokerLoginError(const shv::chainpack::RpcError &err); + + void subscribeChanges(); + void testRpcCall() const; private: void loadSettings() override; qf::gui::framework::DialogWidget *createDetailWidget() override; @@ -84,6 +96,10 @@ class QxClientService : public Service void pollQxChanges(); EventInfo eventInfo() const; +private: // shv + shv::iotqt::rpc::ClientConnection *m_rpcConnection = nullptr; + shv::iotqt::node::ShvNodeTree *m_shvTree = nullptr; + private: QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp index e4c9a604a..324dd0424 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp @@ -35,12 +35,7 @@ void ServicesWidget::reload() Service *svc = Service::serviceAt(i); auto *sw = new ServiceWidget(); - sw->setStatus(svc->status()); - connect(svc, &Service::statusChanged, sw, &ServiceWidget::setStatus); - sw->setServiceId(svc->serviceId(), svc->serviceDisplayName()); - sw->setMessage(svc->statusMessage()); - connect(svc, &Service::statusMessageChanged, sw, &ServiceWidget::setMessage); - connect(sw, &ServiceWidget::setRunningRequest, svc, &Service::setRunning); + sw->setService(svc); ly2->addWidget(sw); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp index dd4bf8233..3069eb848 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp @@ -45,10 +45,15 @@ void ServiceWidget::setStatus(Service::Status st) } } -void ServiceWidget::setServiceId(const QString &id, const QString &display_name) +void ServiceWidget::setService(Service *service) { - m_serviceId = id; - ui->lblServiceName->setText(display_name.isEmpty()? id: display_name); + m_serviceId = service->serviceId(); + ui->lblServiceName->setText(service->serviceDisplayName()); + setStatus(service->status()); + connect(service, &Service::statusChanged, this, &ServiceWidget::setStatus); + setMessage(service->statusMessage()); + connect(service, &Service::statusMessageChanged, this, &ServiceWidget::setMessage); + connect(this, &ServiceWidget::setRunningRequest, service, &Service::setRunning); } QString ServiceWidget::serviceId() const diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h index f659cafde..4d589d2fe 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h @@ -21,7 +21,7 @@ class ServiceWidget : public QWidget ~ServiceWidget(); void setStatus(Service::Status st); - void setServiceId(const QString &id, const QString &display_name); + void setService(Service *service); QString serviceId() const; void setMessage(const QString &m); From dce19697a992c45780b2f869dcace7d2930cbdc9 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sat, 8 Nov 2025 11:47:27 +0100 Subject: [PATCH 02/52] Rename QxClientService to QxEventService --- quickevent/app/quickevent/CMakeLists.txt | 7 +- .../plugins/Event/src/eventplugin.cpp | 4 +- ...qxclientservice.cpp => qxeventservice.cpp} | 84 +++++++++---------- .../{qxclientservice.h => qxeventservice.h} | 10 +-- ...icewidget.cpp => qxeventservicewidget.cpp} | 46 +++++----- ...servicewidget.h => qxeventservicewidget.h} | 14 ++-- ...rvicewidget.ui => qxeventservicewidget.ui} | 4 +- .../services/qx/qxlateregistrationswidget.cpp | 6 +- .../services/qx/qxlateregistrationswidget.h | 4 +- .../Event/src/services/qx/runchangedialog.cpp | 8 +- .../Event/src/services/qx/runchangedialog.h | 4 +- 11 files changed, 94 insertions(+), 97 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservice.cpp => qxeventservice.cpp} (87%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservice.h => qxeventservice.h} (91%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.cpp => qxeventservicewidget.cpp} (76%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.h => qxeventservicewidget.h} (65%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.ui => qxeventservicewidget.ui} (97%) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 13549e64f..b502afa91 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -89,11 +89,8 @@ add_executable(quickevent plugins/Event/src/services/serviceswidget.cpp plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/qxclientservice.cpp - plugins/Event/src/services/qx/qxclientservice.h - plugins/Event/src/services/qx/qxclientservicewidget.cpp - plugins/Event/src/services/qx/qxclientservicewidget.h - plugins/Event/src/services/qx/qxclientservicewidget.ui + plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h + plugins/Event/src/services/qx/qxeventservicewidget.ui plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index 796c38221..6f7accfcc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -11,7 +11,7 @@ #include "services/serviceswidget.h" #include "services/emmaclient.h" -#include "services/qx/qxclientservice.h" +#include "services/qx/qxeventservice.h" #include #include @@ -380,7 +380,7 @@ void EventPlugin::onInstalled() auto *emma_client = new services::EmmaClient(this); services::Service::addService(emma_client); - auto shvapi_client = new services::qx::QxClientService(this); + auto shvapi_client = new services::qx::QxEventService(this); services::Service::addService(shvapi_client); { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp similarity index 87% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 7a9acb8fd..6b673328e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -1,5 +1,5 @@ -#include "qxclientservice.h" -#include "qxclientservicewidget.h" +#include "qxeventservice.h" +#include "qxeventservicewidget.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -56,24 +56,24 @@ namespace Event::services::qx { //=============================================== // QxClientService //=============================================== -QxClientService::QxClientService(QObject *parent) - : Super(QxClientService::serviceId(), parent) +QxEventService::QxEventService(QObject *parent) + : Super(QxEventService::serviceId(), parent) { auto *event_plugin = getPlugin(); - connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxClientService::onDbEventNotify, Qt::QueuedConnection); + connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); } -QString QxClientService::serviceDisplayName() const +QString QxEventService::serviceDisplayName() const { return tr("QE Exchange"); } -QString QxClientService::serviceId() +QString QxEventService::serviceId() { return QStringLiteral("qx"); } -void QxClientService::run() { +void QxEventService::run() { using namespace shv::iotqt::rpc; auto ss = settings(); @@ -82,10 +82,10 @@ void QxClientService::run() { m_rpcConnection = new ClientConnection(this); m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); - connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxClientService::onBrokerConnectedChanged); - connect(m_rpcConnection, &ClientConnection::socketError, this, &QxClientService::onBrokerSocketError); - connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxClientService::onBrokerLoginError); - connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxClientService::onRpcMessageReceived); + connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); + connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); + connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); + connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); m_rpcConnection->open(); @@ -111,7 +111,7 @@ void QxClientService::run() { // }); } -void QxClientService::stop() +void QxEventService::stop() { disconnectSSE(); if (m_pollChangesTimer) { @@ -120,13 +120,13 @@ void QxClientService::stop() Super::stop(); } -qf::gui::framework::DialogWidget *QxClientService::createDetailWidget() +qf::gui::framework::DialogWidget *QxEventService::createDetailWidget() { - auto *w = new QxClientServiceWidget(); + auto *w = new QxEventServiceWidget(); return w; } -void QxClientService::loadSettings() +void QxEventService::loadSettings() { Super::loadSettings(); auto ss = settings(); @@ -136,7 +136,7 @@ void QxClientService::loadSettings() m_settings = ss; } -void QxClientService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) +void QxEventService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) { Q_UNUSED(connection_id) Q_UNUSED(data) @@ -183,7 +183,7 @@ void QxClientService::onDbEventNotify(const QString &domain, int connection_id, } } -QNetworkAccessManager *QxClientService::networkManager() +QNetworkAccessManager *QxEventService::networkManager() { if (!m_networkManager) { m_networkManager = new QNetworkAccessManager(this); @@ -191,7 +191,7 @@ QNetworkAccessManager *QxClientService::networkManager() return m_networkManager; } -QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -202,7 +202,7 @@ QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, c return nm->get(request); } -QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::postEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -218,7 +218,7 @@ QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const return nm->post(request, data); } -void QxClientService::postStartListIofXml3(QObject *context, std::function call_back) +void QxEventService::postStartListIofXml3(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); @@ -229,7 +229,7 @@ void QxClientService::postStartListIofXml3(QObject *context, std::function call_back) +void QxEventService::postRuns(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); @@ -241,7 +241,7 @@ void QxClientService::postRuns(QObject *context, std::function c } } -void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) +void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) { auto url = exchangeServerUrl(); url.setPath(path); @@ -270,7 +270,7 @@ void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, Q }); } -QNetworkReply* QxClientService::getQxChangesReply(int from_id) +QNetworkReply* QxEventService::getQxChangesReply(int from_id) { auto url = exchangeServerUrl(); @@ -282,7 +282,7 @@ QNetworkReply* QxClientService::getQxChangesReply(int from_id) return networkManager()->get(request); } -int QxClientService::eventId() const +int QxEventService::eventId() const { if (m_eventId == 0) { throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); @@ -290,7 +290,7 @@ int QxClientService::eventId() const return m_eventId; } -QByteArray QxClientService::apiToken() const +QByteArray QxEventService::apiToken() const { // API token must not be cached to enable service point // always to current stage event on qxhttpd @@ -299,13 +299,13 @@ QByteArray QxClientService::apiToken() const return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); } -QUrl QxClientService::exchangeServerUrl() const +QUrl QxEventService::exchangeServerUrl() const { auto ss = settings(); return QUrl(ss.exchangeServerUrl()); } -void QxClientService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) +void QxEventService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) { auto url = exchangeServerUrl(); @@ -333,7 +333,7 @@ void QxClientService::postFileCompressed(std::optional path, std::optio }); } -void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) +void QxEventService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) { switch (file) { case SpecFile::StartListIofXml3: @@ -345,7 +345,7 @@ void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *co } } -QByteArray QxClientService::zlibCompress(QByteArray data) +QByteArray QxEventService::zlibCompress(QByteArray data) { QByteArray compressedData = qCompress(data); // strip the 4-byte length put on by qCompress @@ -354,7 +354,7 @@ QByteArray QxClientService::zlibCompress(QByteArray data) return compressedData; } -void QxClientService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) +void QxEventService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) { if (!isRunning()) { return; @@ -394,7 +394,7 @@ void QxClientService::httpPostJson(const QString &path, const QString &query, QV } } -void QxClientService::connectToSSE(int event_id) +void QxEventService::connectToSSE(int event_id) { Q_UNUSED(event_id); // auto url = exchangeServerUrl(); @@ -417,7 +417,7 @@ void QxClientService::connectToSSE(int event_id) // }); } -void QxClientService::disconnectSSE() +void QxEventService::disconnectSSE() { if (m_replySSE) { qfInfo() << "Disconnecting SSE:" << m_replySSE; @@ -426,7 +426,7 @@ void QxClientService::disconnectSSE() } } -void QxClientService::pollQxChanges() +void QxEventService::pollQxChanges() { auto event_plugin = getPlugin(); if(!getPlugin()->isEventOpen()) { @@ -501,7 +501,7 @@ void QxClientService::pollQxChanges() } } -EventInfo QxClientService::eventInfo() const +EventInfo QxEventService::eventInfo() const { auto *event_plugin = getPlugin(); auto *event_config = event_plugin->eventConfig(); @@ -582,12 +582,12 @@ EventInfo QxClientService::eventInfo() const // return csv; //} //} -int QxClientService::currentConnectionId() +int QxEventService::currentConnectionId() { return qf::core::sql::Connection::forName().connectionId(); } -void QxClientService::onBrokerConnectedChanged(bool is_connected) +void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { setStatus(Status::Running); @@ -601,17 +601,17 @@ void QxClientService::onBrokerConnectedChanged(bool is_connected) } -void QxClientService::onBrokerSocketError(const QString &err) +void QxEventService::onBrokerSocketError(const QString &err) { setStatusMessage(tr("Broker socket error: %1").arg(err)); } -void QxClientService::onBrokerLoginError(const shv::chainpack::RpcError &err) +void QxEventService::onBrokerLoginError(const shv::chainpack::RpcError &err) { setStatusMessage(tr("Broker login error: %1").arg(err.toString())); } -void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) +void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) { // shvLogFuncFrame() << msg.toCpon(); if(msg.isRequest()) { @@ -631,7 +631,7 @@ void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg } } -void QxClientService::subscribeChanges() +void QxEventService::subscribeChanges() { Q_ASSERT(m_rpcConnection); QString shv_path = "test"; @@ -653,7 +653,7 @@ void QxClientService::subscribeChanges() rpc_call->start(); } -void QxClientService::testRpcCall() const +void QxEventService::testRpcCall() const { Q_ASSERT(m_rpcConnection); auto *rpc_call = RpcCall::create(m_rpcConnection) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h similarity index 91% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 7e1f046be..1ab8d14b7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -13,13 +13,13 @@ namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { -class QxClientServiceSettings : public ServiceSettings +class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; QF_VARIANTMAP_FIELD2(QString, e, setE, xchangeServerUrl, "http://localhost:8000") public: - QxClientServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} + QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; class EventInfo : public QVariantMap @@ -38,7 +38,7 @@ class EventInfo : public QVariantMap EventInfo(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} }; -class QxClientService : public Service +class QxEventService : public Service { Q_OBJECT @@ -46,14 +46,14 @@ class QxClientService : public Service public: static constexpr auto QX_API_TOKEN = "qx-api-token"; public: - QxClientService(QObject *parent); + QxEventService(QObject *parent); static QString serviceId(); QString serviceDisplayName() const override; void run() override; void stop() override; - QxClientServiceSettings settings() const {return QxClientServiceSettings(m_settings);} + QxEventServiceSettings settings() const {return QxEventServiceSettings(m_settings);} void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); QNetworkAccessManager* networkManager(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp similarity index 76% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index e9a04902e..ede33ffb4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -1,6 +1,6 @@ -#include "qxclientservicewidget.h" -#include "ui_qxclientservicewidget.h" -#include "qxclientservice.h" +#include "qxeventservicewidget.h" +#include "ui_qxeventservicewidget.h" +#include "qxeventservice.h" #include @@ -19,14 +19,14 @@ using qf::gui::framework::getPlugin; namespace Event::services::qx { -QxClientServiceWidget::QxClientServiceWidget(QWidget *parent) +QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) : Super(parent) - , ui(new Ui::QxClientServiceWidget) + , ui(new Ui::QxEventServiceWidget) { setPersistentSettingsId("QxClientServiceWidget"); ui->setupUi(this); - connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); - connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); + connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); + connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); setMessage(""); @@ -38,18 +38,18 @@ QxClientServiceWidget::QxClientServiceWidget(QWidget *parent) ui->edServerUrl->setText(settings.exchangeServerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); - connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxClientServiceWidget::testConnection); - connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportEventInfo); - connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportStartList); - connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportRuns); + connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); + connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); + connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); + connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportRuns); } -QxClientServiceWidget::~QxClientServiceWidget() +QxEventServiceWidget::~QxEventServiceWidget() { delete ui; } -void QxClientServiceWidget::setMessage(const QString &msg, MessageType msg_type) +void QxEventServiceWidget::setMessage(const QString &msg, MessageType msg_type) { if (msg.isEmpty()) { ui->lblStatus->setStyleSheet({}); @@ -70,7 +70,7 @@ void QxClientServiceWidget::setMessage(const QString &msg, MessageType msg_type) ui->lblStatus->setText(msg); } -bool QxClientServiceWidget::acceptDialogDone(int result) +bool QxEventServiceWidget::acceptDialogDone(int result) { if(result == QDialog::Accepted) { if(!saveSettings()) { @@ -80,14 +80,14 @@ bool QxClientServiceWidget::acceptDialogDone(int result) return true; } -QxClientService *QxClientServiceWidget::service() +QxEventService *QxEventServiceWidget::service() { - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); - QF_ASSERT(svc, QxClientService::serviceId() + " doesn't exist", return nullptr); + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); + QF_ASSERT(svc, QxEventService::serviceId() + " doesn't exist", return nullptr); return svc; } -bool QxClientServiceWidget::saveSettings() +bool QxEventServiceWidget::saveSettings() { auto *svc = service(); if(svc) { @@ -104,14 +104,14 @@ bool QxClientServiceWidget::saveSettings() return true; } -void QxClientServiceWidget::updateOCheckListPostUrl() +void QxEventServiceWidget::updateOCheckListPostUrl() { auto url = QStringLiteral("%1/api/event/current/oc").arg(ui->edServerUrl->text()); ui->edOChecklistUrl->setText(url); ui->edOChecklistUrlHeader->setText(QStringLiteral("qx-api-token=%1").arg(ui->edApiToken->text())); } -void QxClientServiceWidget::testConnection() +void QxEventServiceWidget::testConnection() { auto *svc = service(); Q_ASSERT(svc); @@ -131,7 +131,7 @@ void QxClientServiceWidget::testConnection() }); } -void QxClientServiceWidget::exportEventInfo() +void QxEventServiceWidget::exportEventInfo() { auto *svc = service(); Q_ASSERT(svc); @@ -151,7 +151,7 @@ void QxClientServiceWidget::exportEventInfo() }); } -void QxClientServiceWidget::exportStartList() +void QxEventServiceWidget::exportStartList() { auto *svc = service(); Q_ASSERT(svc); @@ -167,7 +167,7 @@ void QxClientServiceWidget::exportStartList() }); } -void QxClientServiceWidget::exportRuns() +void QxEventServiceWidget::exportRuns() { auto *svc = service(); Q_ASSERT(svc); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h similarity index 65% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index f1b108a30..19454ad86 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -5,23 +5,23 @@ namespace Event::services::qx { namespace Ui { -class QxClientServiceWidget; +class QxEventServiceWidget; } -class QxClientService; +class QxEventService; -class QxClientServiceWidget : public qf::gui::framework::DialogWidget +class QxEventServiceWidget : public qf::gui::framework::DialogWidget { Q_OBJECT using Super = qf::gui::framework::DialogWidget; public: - explicit QxClientServiceWidget(QWidget *parent = nullptr); - ~QxClientServiceWidget() override; + explicit QxEventServiceWidget(QWidget *parent = nullptr); + ~QxEventServiceWidget() override; private: enum class MessageType { Ok, Error, Progress }; void setMessage(const QString &msg = {}, MessageType msg_type = MessageType::Ok); - QxClientService* service(); + QxEventService* service(); bool saveSettings(); void updateOCheckListPostUrl(); void testConnection(); @@ -29,7 +29,7 @@ class QxClientServiceWidget : public qf::gui::framework::DialogWidget void exportStartList(); void exportRuns(); private: - Ui::QxClientServiceWidget *ui; + Ui::QxEventServiceWidget *ui; bool acceptDialogDone(int result) override; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui similarity index 97% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index d8824707e..261a65af9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxClientServiceWidget - + Event::services::qx::QxEventServiceWidget + 0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp index f0747e77a..083af5d69 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp @@ -1,7 +1,7 @@ #include "qxlateregistrationswidget.h" #include "ui_qxlateregistrationswidget.h" -#include "qxclientservice.h" +#include "qxeventservice.h" #include "runchangedialog.h" #include "runchange.h" @@ -152,9 +152,9 @@ void QxLateRegistrationsWidget::onVisibleChanged(bool is_visible) } } -QxClientService *QxLateRegistrationsWidget::service() +QxEventService *QxLateRegistrationsWidget::service() { - auto *svc = qobject_cast(Event::services::Service::serviceByName(QxClientService::serviceId())); + auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h index 6c972c739..ea906b649 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h @@ -10,7 +10,7 @@ namespace Ui { class QxLateRegistrationsWidget; } -class QxClientService; +class QxEventService; class QxLateRegistrationsWidget : public QWidget { @@ -23,7 +23,7 @@ class QxLateRegistrationsWidget : public QWidget void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: - QxClientService* service(); + QxEventService* service(); void reload(); void addQxChangeRow(int sql_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index c44c38a94..789cb44e0 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -1,7 +1,7 @@ #include "runchangedialog.h" #include "ui_runchangedialog.h" -#include "qxclientservice.h" +#include "qxeventservice.h" #include "runchange.h" #include @@ -72,9 +72,9 @@ RunChangeDialog::~RunChangeDialog() delete ui; } -QxClientService *RunChangeDialog::service() +QxEventService *RunChangeDialog::service() { - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } @@ -220,7 +220,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) url.setQuery(query); request.setUrl(url); - request.setRawHeader(QxClientService::QX_API_TOKEN, svc->apiToken()); + request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken()); auto *reply = nm->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NetworkError::NoError) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h index d704a1d48..15a9bf147 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h @@ -14,7 +14,7 @@ class RunChangeDialog; } struct RunChange; -class QxClientService; +class QxEventService; class RunChangeDialog : public QDialog { @@ -25,7 +25,7 @@ class RunChangeDialog : public QDialog ~RunChangeDialog() override; private: - QxClientService* service(); + QxEventService* service(); void setMessage(const QString &msg, bool error); void loadOrigValues(); From 2b49c7c82854a1915ffba38f814d1d0951bc3a10 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:34:22 +0100 Subject: [PATCH 03/52] Fix DesktopUtils::moveRectToVisibleDesktopScreen --- libqf/libqfgui/src/framework/dialogwidget.cpp | 15 ++++-------- libqf/libqfgui/src/framework/dialogwidget.h | 2 +- .../src/framework/ipersistentsettings.cpp | 23 ++++++++----------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/libqf/libqfgui/src/framework/dialogwidget.cpp b/libqf/libqfgui/src/framework/dialogwidget.cpp index f46cd8f23..8d5769997 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.cpp +++ b/libqf/libqfgui/src/framework/dialogwidget.cpp @@ -9,13 +9,13 @@ using namespace qf::gui::framework; -DialogWidget::DialogWidget(QWidget *parent) : - Super(parent), IPersistentSettings(this) +DialogWidget::DialogWidget(QWidget *parent) + : Super(parent) + , IPersistentSettings(this) { } -DialogWidget::~DialogWidget() -= default; +DialogWidget::~DialogWidget() = default; bool DialogWidget::acceptDialogDone(int result) { @@ -23,12 +23,7 @@ bool DialogWidget::acceptDialogDone(int result) Q_UNUSED(result); return true; } -/* -QVariant DialogWidget::acceptDialogDone_qml(const QVariant &result) -{ - return acceptDialogDone(result.toBool()); -} -*/ + void DialogWidget::settleDownInDialog_qml(const QVariant &dlg) { auto *o = dlg.value(); diff --git a/libqf/libqfgui/src/framework/dialogwidget.h b/libqf/libqfgui/src/framework/dialogwidget.h index 13893c8a0..611be6b95 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.h +++ b/libqf/libqfgui/src/framework/dialogwidget.h @@ -32,7 +32,7 @@ class QFGUI_DECL_EXPORT DialogWidget : public Frame, public IPersistentSettings typedef Frame Super; public: explicit DialogWidget(QWidget *parent = nullptr); - ~DialogWidget() Q_DECL_OVERRIDE; + ~DialogWidget() override; QF_PROPERTY_IMPL(QString, t, T, itle) QF_PROPERTY_IMPL(QString, i, I, conSource) diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.cpp b/libqf/libqfgui/src/framework/ipersistentsettings.cpp index c639c374c..7fff48174 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.cpp +++ b/libqf/libqfgui/src/framework/ipersistentsettings.cpp @@ -48,7 +48,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) QMetaMethod mm = obj->metaObject()->method(ix); mm.invoke(obj); } - Q_FOREACH(auto *o, obj->children()) { + for (auto *o : obj->children()) { //static int level = 0; //level++; //QString indent = QString(level, ' '); @@ -103,25 +103,20 @@ QString IPersistentSettings::rawPersistentSettingsPath() QString persistent_id = persistentSettingsId(); QStringList raw_path; if(!persistent_id.isEmpty()) { - for(QObject *obj=m_controlledObject->parent(); obj!=nullptr; obj=obj->parent()) { + for(QObject* obj = m_controlledObject->parent(); obj != nullptr; obj = obj->parent()) { auto *ps = dynamic_cast(obj); if(ps) { QString pp = ps->rawPersistentSettingsPath(); - if(!pp.isEmpty()) + if(!pp.isEmpty()) { raw_path.insert(0, pp); - //qfWarning() << "reading property 'persistentSettingsId' error" << obj << "casted to IPersistentSettings" << ps; - //qfWarning() << "\tcorrect value should be:" << parent_id; + } break; } - QVariant vid = obj->property("persistentSettingsId"); - QString parent_id = vid.toString(); - if(!parent_id.isEmpty()) { - raw_path.insert(0, parent_id); - } - - // reading property using QQmlProperty is crashing my app Qt 5.3.1 commit a83826dad0f62d7a96f5a6093240e4c8f7f2e06e - //QQmlProperty p(obj, "persistentSettingsId"); - //QVariant v2 = p.read(); + QVariant vid = obj->property("persistentSettingsId"); + QString parent_id = vid.toString(); + if(!parent_id.isEmpty()) { + raw_path.insert(0, parent_id); + } } raw_path.append(persistent_id); } From 4b1f96f3e85cb7f92002ca2c6558735ee0236c0b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:34:34 +0100 Subject: [PATCH 04/52] Update libshv --- 3rdparty/libshv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index 3cbe496ea..40f0b3cf2 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 3cbe496ea4d2c48695994850a09c2cc794c3f80b +Subproject commit 40f0b3cf213ab799fb17bf6a31ce527a8851ee30 From 317424d02edd070e67ae6fde98f0db3e13c053c9 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:35:51 +0100 Subject: [PATCH 05/52] Add QX event SHV nodes --- .../src/reportoptionsdialog.h | 8 +- quickevent/app/quickevent/CMakeLists.txt | 17 +- .../plugins/Event/src/services/qx/nodes.cpp | 300 ++++++++++++++++++ .../plugins/Event/src/services/qx/nodes.h | 103 ++++++ .../Event/src/services/qx/qxeventservice.cpp | 74 +++-- .../Event/src/services/qx/qxeventservice.h | 14 +- .../src/services/qx/qxeventservicewidget.cpp | 6 +- .../src/services/qx/qxeventservicewidget.ui | 2 +- .../plugins/Event/src/services/qx/qxnode.cpp | 36 +++ .../plugins/Event/src/services/qx/qxnode.h | 23 ++ .../Event/src/services/qx/runchangedialog.cpp | 2 +- .../Event/src/services/qx/sqlapinode.cpp | 119 +++++++ .../Event/src/services/qx/sqlapinode.h | 27 ++ 13 files changed, 682 insertions(+), 49 deletions(-) create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.h b/libquickevent/libquickeventgui/src/reportoptionsdialog.h index e4b22c6f9..bdccb558b 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.h +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.h @@ -86,13 +86,13 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: explicit ReportOptionsDialog(QWidget *parent = nullptr); ~ReportOptionsDialog() override; - int exec() Q_DECL_OVERRIDE; + int exec() override; void setStartListPrintVacantsVisible(bool b); void setStartListForRelays(); - QString persistentSettingsPath() Q_DECL_OVERRIDE; - bool setPersistentSettingsId(const QString &id) Q_DECL_OVERRIDE; + QString persistentSettingsPath() override; + bool setPersistentSettingsId(const QString &id) override; Q_SIGNAL void persistentSettingsIdChanged(const QString &id); void setOptions(const Options &options); @@ -127,7 +127,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: static QString sqlWhereExpression(const Options &opts, const int stage_id); static QString getClassesForStartNumber(const int number, const int stage_id); protected: - //void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + //void showEvent(QShowEvent *event) override; private: Ui::ReportOptionsDialog *ui; }; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b502afa91..33b7c3607 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(Qt6 REQUIRED COMPONENTS Core) + add_executable(quickevent plugins/CardReader/src/cardchecker.cpp plugins/CardReader/src/cardcheckerclassiccpp.cpp @@ -89,11 +91,13 @@ add_executable(quickevent plugins/Event/src/services/serviceswidget.cpp plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h - plugins/Event/src/services/qx/qxeventservicewidget.ui - plugins/Event/src/services/qx/qxlateregistrationswidget.h - plugins/Event/src/services/qx/qxlateregistrationswidget.cpp - plugins/Event/src/services/qx/qxlateregistrationswidget.ui + + plugins/Event/src/services/qx/qxnode.cpp + plugins/Event/src/services/qx/sqlapinode.cpp + plugins/Event/src/services/qx/nodes.cpp + plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h + plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui + plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp @@ -272,7 +276,8 @@ target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAK target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) if (QF_WITH_LIBSHV) target_link_libraries(quickevent PUBLIC libshviotqt) - target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) +target_link_libraries(quickevent PRIVATE Qt6::Core) +target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp new file mode 100644 index 000000000..07c57d53d --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp @@ -0,0 +1,300 @@ +#include "nodes.h" + +#include "sqlapinode.h" +#include "../../eventplugin.h" +#include "../../../../Runs/src/runsplugin.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qf::core::sql; +using namespace shv::chainpack; +using qf::gui::framework::getPlugin; +using Event::EventPlugin; +using Runs::RunsPlugin; + +namespace Event::services::qx { + +//========================================================= +// DotAppNode +//========================================================= +namespace { +auto METH_NAME = "name"; +} +const std::vector &DotAppNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {Rpc::METH_PING, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Browse}, + {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Browse}, + }; + return meta_methods; +} + +RpcValue DotAppNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == Rpc::METH_PING) { + return nullptr; + } + if(method == METH_NAME) { + return "QuickEvent"; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// EventNode +//========================================================= +static const auto METH_CURRENT_STAGE = "currentStage"; +static const auto METH_EVENT_CONFIG = "eventConfig"; + +EventNode::EventNode(shv::iotqt::node::ShvNode *parent) +: Super("event", parent) +{ +} + +const std::vector &EventNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + {METH_CURRENT_STAGE, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + {METH_EVENT_CONFIG, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + }; + return meta_methods; +} + +RpcValue EventNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + if(shv_path.empty()) { + if(method == METH_NAME) { + return getPlugin()->eventConfig()->eventName().toStdString(); + } + if(method == METH_CURRENT_STAGE) { + return getPlugin()->currentStageId(); + } + if(method == METH_EVENT_CONFIG) { + auto cfg = getPlugin()->eventConfig(); + return shv::coreqt::rpc::qVariantToRpcValue(cfg->values()); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// StartListNode +//========================================================= +static const auto METH_TABLE = "table"; +static const auto METH_RECORD = "record"; +//static const auto SIG_REC_CHNG = "recchng"; + +void SqlViewNode::setQueryBuilder(const qf::core::sql::QueryBuilder &qb) +{ + m_queryBuilder = qb; +} + +qf::core::sql::QueryBuilder SqlViewNode::effectiveQueryBuilder() +{ + return m_queryBuilder; +} + +const std::vector &SqlViewNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, + {METH_RECORD, MetaMethod::Flag::None, "RpcValue", "RpcValue", AccessLevel::Read }, + //{METH_SET_RECORD, MetaMethod::Flag::None, "RpcValue", {}, AccessLevel::Write}, + }; + return meta_methods; +} + +RpcValue SqlViewNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == METH_TABLE) { + const auto &m = params.asMap(); + auto qb = effectiveQueryBuilder(); + if (auto where = m.value("where").to(); !where.isEmpty()) { + qb.where(where); + } + if (auto order_by = m.value("orderBy").to(); !order_by.isEmpty()) { + qb.orderBy(order_by); + } + qf::core::sql::Query q; + QString qs = qb.toString(); + q.exec(qs, qf::core::Exception::Throw); + auto res = SqlApiNode::rpcSqlResultFromQuery(q); + return res.toRpcValue(); + } + if(method == METH_RECORD) { + auto id = params.toInt(); + auto qb = effectiveQueryBuilder(); + qb.where("runs.id = " + QString::number(id)); + qf::core::sql::Query q; + QString qs = qb.toString(); + qfDebug() << qs; + q.exec(qs, qf::core::Exception::Throw); + if (q.next()) { + return SqlApiNode::recordToMap(q.record()); + } + return RpcValue::Map{}; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// CurrentStageConfigNode +//========================================================= +const std::vector &CurrentStageConfigNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {Rpc::METH_GET, MetaMethod::Flag::IsGetter, {}, "Map", AccessLevel::Read}, + }; + return meta_methods; +} + +RpcValue CurrentStageConfigNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == Rpc::METH_GET) { + auto *event_plugin = getPlugin(); + QVariantMap data = event_plugin->stageData(event_plugin->currentStageId()); + data.remove("drawingConfig"); // remove internal key + return shv::coreqt::rpc::qVariantToRpcValue(data); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// CurrentStageStartListNode +//========================================================= +CurrentStageStartListNode::CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent) + : Super("startList", parent) +{ + auto qb = getPlugin()->startListQuery(); + //qb.orderBy("runs.startTimeMs"); + setQueryBuilder(qb); +} + +qf::core::sql::QueryBuilder CurrentStageStartListNode::effectiveQueryBuilder() +{ + auto qb = Super::effectiveQueryBuilder(); + auto *event_plugin = getPlugin(); + qb.where(QStringLiteral("runs.stageId=%1").arg(event_plugin->currentStageId())); + return qb; +} + +//========================================================= +// CurrentStageRunsNode +//========================================================= +CurrentStageRunsNode::CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent) + : Super("runs", parent) +{ +} + +QueryBuilder CurrentStageRunsNode::effectiveQueryBuilder() +{ + auto *event_plugin = getPlugin(); + auto qb = getPlugin()->runsQuery(event_plugin->currentStageId()); + return qb; +} + +static const auto METH_SET_RECORD = "setRecord"; +//static const auto METH_RUN_CHANGED = "runchng"; +static const auto SIG_RUN_CHANGED = "runchng"; + +const std::vector &CurrentStageRunsNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, + {METH_RECORD, MetaMethod::Flag::None, "Int", "Map", AccessLevel::Read, {{SIG_RUN_CHANGED, "[Int, RpcValue]"}} }, + {METH_SET_RECORD, MetaMethod::Flag::None, "[Int, RpcValue]", {}, AccessLevel::Write}, + }; + return meta_methods; + static auto s_meta_methods = [this]() { + auto mm = Super::metaMethods(); + mm.push_back({METH_SET_RECORD, MetaMethod::Flag::None, "[int, Map]", {}, AccessLevel::Write }); + return mm; + }(); + return s_meta_methods; +} + +RpcValue CurrentStageRunsNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + if(shv_path.empty()) { + if(method == METH_SET_RECORD) { + auto *plugin = getPlugin(); + const auto &lst = params.asList(); + plugin->setRunsRecord(lst.value(0).toInt(), shv::coreqt::rpc::rpcValueToQVariant(lst.value(1))); + return nullptr; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +void CurrentStageRunsNode::sendRunChangedSignal(const QVariant &qparam) +{ + auto param = shv::coreqt::rpc::qVariantToRpcValue(qparam); + Q_ASSERT(param.isList()); + Q_ASSERT(param.asList().value(0).toInt() > 0); // run.id + RpcSignal sig; + sig.setShvPath(shvPath().asString()); + sig.setMethod(SIG_RUN_CHANGED); + sig.setSource(METH_RECORD); + sig.setParams(param); + qfDebug() << "emit:" << sig.toPrettyString(); + emitSendRpcMessage(sig); +} + +//========================================================= +// CurrentStageClassesNode +//========================================================= +CurrentStageClassesNode::CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent) + : Super("classes", parent) +{ +} + +QueryBuilder CurrentStageClassesNode::effectiveQueryBuilder() +{ + auto *event_plugin = getPlugin(); + QueryBuilder qb; + qb.select2("classes", "*") + .select2("classdefs", "*") + .select2("courses", "id, name, length, climb") + .from("classes") + .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId=" QF_IARG(event_plugin->currentStageId())) + .join("classdefs.courseId", "courses.id") + .orderBy("classes.name");//.limit(10); + return qb; +} + + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h new file mode 100644 index 000000000..917a75f2c --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h @@ -0,0 +1,103 @@ +#pragma once + +#include "qxnode.h" + +#include + +namespace Event::services::qx { + +class DotAppNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit DotAppNode(shv::iotqt::node::ShvNode *parent) : Super(".app", parent) {} +private: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class EventNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit EventNode(shv::iotqt::node::ShvNode *parent); +private: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class SqlViewNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit SqlViewNode(const std::string &name, shv::iotqt::node::ShvNode *parent) + : Super(name, parent) + {} + void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); +protected: + virtual qf::core::sql::QueryBuilder effectiveQueryBuilder(); + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +private: + qf::core::sql::QueryBuilder m_queryBuilder; +}; + +class CurrentStageConfigNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit CurrentStageConfigNode(shv::iotqt::node::ShvNode *parent) + : Super("config", parent) + {} +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class CurrentStageStartListNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent); +protected: + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +class CurrentStageRunsNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent); + void sendRunChangedSignal(const QVariant &qparam); +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +class CurrentStageClassesNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent); + +protected: + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 6b673328e..e693f4b45 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -1,5 +1,7 @@ #include "qxeventservice.h" #include "qxeventservicewidget.h" +#include "nodes.h" +#include "sqlapinode.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -9,7 +11,7 @@ #include #include -#include +#include #include #include @@ -41,31 +43,39 @@ using Event::EventPlugin; using Runs::RunsPlugin; namespace Event::services::qx { -//=============================================== -// QxClientServiceSettings -//=============================================== -// QString QxClientServiceSettings::eventKey() const -// { -// auto *event_plugin = getPlugin(); -// auto *cfg = event_plugin->eventConfig(); -// auto key = cfg->apiKey(); -// auto current_stage = cfg->currentStageId(); -// return QStringLiteral("%1%2").arg(key).arg(current_stage); -// } //=============================================== // QxClientService //=============================================== QxEventService::QxEventService(QObject *parent) : Super(QxEventService::serviceId(), parent) + , m_rootNode(new shv::iotqt::node::ShvRootNode(this)) { auto *event_plugin = getPlugin(); - connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); + // connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); + + new DotAppNode(m_rootNode); + connect(event_plugin, &EventPlugin::eventOpenChanged, this, [this](bool is_open) { + if (is_open) { + new SqlApiNode(m_rootNode); + auto *event = new EventNode(m_rootNode); + auto *current_stage = new shv::iotqt::node::ShvNode("currentStage", event); + new CurrentStageConfigNode(current_stage); + //new CurrentStageStartListNode(current_stage); + new CurrentStageRunsNode(current_stage); + new CurrentStageClassesNode(current_stage); + } + else { + qDeleteAll(m_rootNode->findChildren()); + } + }); + + connect(m_rootNode, &shv::iotqt::node::ShvNode::sendRpcMessage, this, &QxEventService::sendRpcMessage); } QString QxEventService::serviceDisplayName() const { - return tr("QE Exchange"); + return tr("QX Event"); } QString QxEventService::serviceId() @@ -79,8 +89,13 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; - m_rpcConnection = new ClientConnection(this); - m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); + m_rpcConnection = new DeviceConnection(this); + m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); + RpcValue::Map opts; + RpcValue::Map device; + device["mountPoint"] = "test/quickbox"; + opts["device"] = device; + m_rpcConnection->setConnectionOptions(opts); connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); @@ -130,8 +145,8 @@ void QxEventService::loadSettings() { Super::loadSettings(); auto ss = settings(); - if (ss.exchangeServerUrl().isEmpty()) { - ss.setExchangeServerUrl("http://localhost:8000"); + if (ss.shvBrokerUrl().isEmpty()) { + ss.setShvBrokerUrl("tcp://localhost?user=test&password=test"); } m_settings = ss; } @@ -243,7 +258,7 @@ void QxEventService::postRuns(QObject *context, std::function ca void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); // qfInfo() << url.toString(); @@ -272,7 +287,7 @@ void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QO QNetworkReply* QxEventService::getQxChangesReply(int from_id) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(QStringLiteral("/api/event/%1/changes").arg(eventId())); url.setQuery(QStringLiteral("from_id=%1").arg(from_id)); @@ -299,15 +314,15 @@ QByteArray QxEventService::apiToken() const return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); } -QUrl QxEventService::exchangeServerUrl() const +QUrl QxEventService::shvBrokerUrl() const { auto ss = settings(); - return QUrl(ss.exchangeServerUrl()); + return QUrl(ss.shvBrokerUrl()); } void QxEventService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path.value_or("/api/event/current/file")); if (name.has_value()) { @@ -359,7 +374,7 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa if (!isRunning()) { return; } - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); @@ -617,9 +632,7 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) if(msg.isRequest()) { RpcRequest rq(msg); qfMessage() << "RPC request received:" << rq.toPrettyString(); - if(m_shvTree->root()) { - m_shvTree->root()->handleRpcRequest(rq); - } + m_rootNode->handleRpcRequest(rq); } else if(msg.isResponse()) { RpcResponse rp(msg); @@ -631,6 +644,13 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) } } +void QxEventService::sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg) +{ + if(m_rpcConnection && m_rpcConnection->isBrokerConnected()) { + m_rpcConnection->sendRpcMessage(rpc_msg); + } +} + void QxEventService::subscribeChanges() { Q_ASSERT(m_rpcConnection); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 1ab8d14b7..2a103a9d7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -7,8 +7,8 @@ class QNetworkReply; class QUrlQuery; class QTimer; -namespace shv::iotqt::node { class ShvNodeTree; } -namespace shv::iotqt::rpc { class ClientConnection; } +namespace shv::iotqt::node { class ShvNodeTree; class ShvRootNode; } +namespace shv::iotqt::rpc { class DeviceConnection; } namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { @@ -17,7 +17,7 @@ class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, e, setE, xchangeServerUrl, "http://localhost:8000") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "http://localhost:8000") public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; @@ -69,12 +69,13 @@ class QxEventService : public Service QByteArray apiToken() const; static int currentConnectionId(); - QUrl exchangeServerUrl() const; + QUrl shvBrokerUrl() const; int eventId() const; private: // shv void onBrokerConnectedChanged(bool is_connected); void onRpcMessageReceived(const shv::chainpack::RpcMessage &msg); + void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); @@ -97,9 +98,8 @@ class QxEventService : public Service EventInfo eventInfo() const; private: // shv - shv::iotqt::rpc::ClientConnection *m_rpcConnection = nullptr; - shv::iotqt::node::ShvNodeTree *m_shvTree = nullptr; - + shv::iotqt::rpc::DeviceConnection *m_rpcConnection = nullptr; + shv::iotqt::node::ShvRootNode *m_rootNode; private: QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index ede33ffb4..416bb25b4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -23,7 +23,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) : Super(parent) , ui(new Ui::QxEventServiceWidget) { - setPersistentSettingsId("QxClientServiceWidget"); + setPersistentSettingsId("QxEventServiceWidget"); ui->setupUi(this); connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); @@ -35,7 +35,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) auto *event_plugin = getPlugin(); auto current_stage = event_plugin->currentStageId(); auto settings = svc->settings(); - ui->edServerUrl->setText(settings.exchangeServerUrl()); + ui->edServerUrl->setText(settings.shvBrokerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); @@ -92,7 +92,7 @@ bool QxEventServiceWidget::saveSettings() auto *svc = service(); if(svc) { auto ss = svc->settings(); - ss.setExchangeServerUrl(ui->edServerUrl->text()); + ss.setShvBrokerUrl(ui->edServerUrl->text()); svc->setSettings(ss); auto *event_plugin = getPlugin(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index 261a65af9..5f4a41e1a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -19,7 +19,7 @@ - Exchange server url + SHV broker url diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp new file mode 100644 index 000000000..dfcea61fd --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp @@ -0,0 +1,36 @@ +#include "qxnode.h" + +#include +#include + +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +QxNode::QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent) + : Super(name, parent) +{ + +} + +size_t QxNode::methodCount(const StringViewList &shv_path) +{ + if(shv_path.empty()) { + return metaMethods().size(); + } + return Super::methodCount(shv_path); +} + +const MetaMethod *QxNode::metaMethod(const StringViewList &shv_path, size_t ix) +{ + if(shv_path.empty()) { + if(metaMethods().size() <= ix) + QF_EXCEPTION("Invalid method index: " + QString::number(ix) + " of: " + QString::number(metaMethods().size())); + return &(metaMethods()[ix]); + } + return Super::metaMethod(shv_path, ix); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h new file mode 100644 index 000000000..79e42c174 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Event::services::qx { + +class QxNode : public shv::iotqt::node::ShvNode +{ + Q_OBJECT + + using Super = shv::iotqt::node::ShvNode; +public: + explicit QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent); + + size_t methodCount(const StringViewList &shv_path) override; + const shv::chainpack::MetaMethod* metaMethod(const StringViewList &shv_path, size_t ix) override; +protected: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + //shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; + virtual const std::vector &metaMethods() = 0; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index 789cb44e0..1c16121ce 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -208,7 +208,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) auto *svc = service(); auto *nm = svc->networkManager(); QNetworkRequest request; - auto url = svc->exchangeServerUrl(); + auto url = svc->shvBrokerUrl(); // qfInfo() << "url " << url.toString(); url.setPath("/api/event/current/changes/resolve-change"); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp new file mode 100644 index 000000000..ac90ce45f --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -0,0 +1,119 @@ +#include "sqlapinode.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace shv::chainpack; +using namespace shv::coreqt::data; + +namespace Event::services::qx { +namespace { + +/* + +RpcSqlResult RpcSqlResult::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + RpcSqlResult ret; + const RpcValue::Map &map = rv.asMap(); + const RpcValue::List &flds = map.value("fields").asList(); + if(flds.empty()) { + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + } + else { + + } + return ret; + +} +*/ +} + +SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) + : Super("sql", parent) +{ + +} + +shv::chainpack::RpcValue::Map SqlApiNode::recordToMap(const QSqlRecord &rec) +{ + RpcValue::Map record; + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + auto fld_name = fld.name(); + fld_name.replace("__", "."); + record[fld_name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(fld.value()); + } + return record; +} + +RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) +{ + RpcSqlResult ret; + if(q.isSelect()) { + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + RpcSqlField rfld; + rfld.name = fld.name(); + rfld.name.replace("__", "."); + rfld.type = fld.metaType().id(); + ret.fields.append(rfld); + } + while(q.next()) { + RpcSqlResult::Row row; + for (int i = 0; i < rec.count(); ++i) { + const QVariant v = q.value(i); + if (v.isNull()) + row.append(QVariant()); + else + row.append(v); + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); + } + ret.rows.insert(ret.rows.count(), row); + } + } + else { + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + } + return ret; +} + +static auto METH_EXEC_SQL = "execSql"; + +const std::vector &SqlApiNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_EXEC_SQL, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Write}, + }; + return meta_methods; +} + +RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == METH_EXEC_SQL) { + qf::core::sql::Query q; + QString qs = params.to(); + q.exec(qs, qf::core::Exception::Throw); + auto res = rpcSqlResultFromQuery(q); + return res.toRpcValue(); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h new file mode 100644 index 000000000..c2e112f26 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -0,0 +1,27 @@ +#pragma once + +#include "qxnode.h" + +class QSqlQuery; +class QSqlRecord; + +namespace shv::coreqt::data { class RpcSqlResult; } + +namespace Event::services::qx { + +class SqlApiNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit SqlApiNode(shv::iotqt::node::ShvNode *parent); + + static shv::coreqt::data::RpcSqlResult rpcSqlResultFromQuery(QSqlQuery &q); + static shv::chainpack::RpcValue::Map recordToMap(const QSqlRecord &rec); +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +} From 8972a3ea4ce6a65fc9db08372a5de972020884b8 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:43:07 +0100 Subject: [PATCH 06/52] Remove unused shv odes --- .../plugins/Event/src/services/qx/nodes.cpp | 249 ------------------ .../plugins/Event/src/services/qx/nodes.h | 81 ------ .../Event/src/services/qx/qxeventservice.cpp | 18 +- 3 files changed, 1 insertion(+), 347 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp index 07c57d53d..6a4db4533 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp @@ -1,9 +1,5 @@ #include "nodes.h" -#include "sqlapinode.h" -#include "../../eventplugin.h" -#include "../../../../Runs/src/runsplugin.h" - #include #include #include @@ -17,9 +13,6 @@ using namespace qf::core::sql; using namespace shv::chainpack; -using qf::gui::framework::getPlugin; -using Event::EventPlugin; -using Runs::RunsPlugin; namespace Event::services::qx { @@ -55,246 +48,4 @@ RpcValue DotAppNode::callMethod(const StringViewList &shv_path, const std::strin return Super::callMethod(shv_path, method, params, user_id); } -//========================================================= -// EventNode -//========================================================= -static const auto METH_CURRENT_STAGE = "currentStage"; -static const auto METH_EVENT_CONFIG = "eventConfig"; - -EventNode::EventNode(shv::iotqt::node::ShvNode *parent) -: Super("event", parent) -{ -} - -const std::vector &EventNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - {METH_CURRENT_STAGE, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - {METH_EVENT_CONFIG, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - }; - return meta_methods; -} - -RpcValue EventNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - if(shv_path.empty()) { - if(method == METH_NAME) { - return getPlugin()->eventConfig()->eventName().toStdString(); - } - if(method == METH_CURRENT_STAGE) { - return getPlugin()->currentStageId(); - } - if(method == METH_EVENT_CONFIG) { - auto cfg = getPlugin()->eventConfig(); - return shv::coreqt::rpc::qVariantToRpcValue(cfg->values()); - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// StartListNode -//========================================================= -static const auto METH_TABLE = "table"; -static const auto METH_RECORD = "record"; -//static const auto SIG_REC_CHNG = "recchng"; - -void SqlViewNode::setQueryBuilder(const qf::core::sql::QueryBuilder &qb) -{ - m_queryBuilder = qb; -} - -qf::core::sql::QueryBuilder SqlViewNode::effectiveQueryBuilder() -{ - return m_queryBuilder; -} - -const std::vector &SqlViewNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, - {METH_RECORD, MetaMethod::Flag::None, "RpcValue", "RpcValue", AccessLevel::Read }, - //{METH_SET_RECORD, MetaMethod::Flag::None, "RpcValue", {}, AccessLevel::Write}, - }; - return meta_methods; -} - -RpcValue SqlViewNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); - if(shv_path.empty()) { - if(method == METH_TABLE) { - const auto &m = params.asMap(); - auto qb = effectiveQueryBuilder(); - if (auto where = m.value("where").to(); !where.isEmpty()) { - qb.where(where); - } - if (auto order_by = m.value("orderBy").to(); !order_by.isEmpty()) { - qb.orderBy(order_by); - } - qf::core::sql::Query q; - QString qs = qb.toString(); - q.exec(qs, qf::core::Exception::Throw); - auto res = SqlApiNode::rpcSqlResultFromQuery(q); - return res.toRpcValue(); - } - if(method == METH_RECORD) { - auto id = params.toInt(); - auto qb = effectiveQueryBuilder(); - qb.where("runs.id = " + QString::number(id)); - qf::core::sql::Query q; - QString qs = qb.toString(); - qfDebug() << qs; - q.exec(qs, qf::core::Exception::Throw); - if (q.next()) { - return SqlApiNode::recordToMap(q.record()); - } - return RpcValue::Map{}; - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// CurrentStageConfigNode -//========================================================= -const std::vector &CurrentStageConfigNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {Rpc::METH_GET, MetaMethod::Flag::IsGetter, {}, "Map", AccessLevel::Read}, - }; - return meta_methods; -} - -RpcValue CurrentStageConfigNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); - if(shv_path.empty()) { - if(method == Rpc::METH_GET) { - auto *event_plugin = getPlugin(); - QVariantMap data = event_plugin->stageData(event_plugin->currentStageId()); - data.remove("drawingConfig"); // remove internal key - return shv::coreqt::rpc::qVariantToRpcValue(data); - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// CurrentStageStartListNode -//========================================================= -CurrentStageStartListNode::CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent) - : Super("startList", parent) -{ - auto qb = getPlugin()->startListQuery(); - //qb.orderBy("runs.startTimeMs"); - setQueryBuilder(qb); -} - -qf::core::sql::QueryBuilder CurrentStageStartListNode::effectiveQueryBuilder() -{ - auto qb = Super::effectiveQueryBuilder(); - auto *event_plugin = getPlugin(); - qb.where(QStringLiteral("runs.stageId=%1").arg(event_plugin->currentStageId())); - return qb; -} - -//========================================================= -// CurrentStageRunsNode -//========================================================= -CurrentStageRunsNode::CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent) - : Super("runs", parent) -{ -} - -QueryBuilder CurrentStageRunsNode::effectiveQueryBuilder() -{ - auto *event_plugin = getPlugin(); - auto qb = getPlugin()->runsQuery(event_plugin->currentStageId()); - return qb; -} - -static const auto METH_SET_RECORD = "setRecord"; -//static const auto METH_RUN_CHANGED = "runchng"; -static const auto SIG_RUN_CHANGED = "runchng"; - -const std::vector &CurrentStageRunsNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, - {METH_RECORD, MetaMethod::Flag::None, "Int", "Map", AccessLevel::Read, {{SIG_RUN_CHANGED, "[Int, RpcValue]"}} }, - {METH_SET_RECORD, MetaMethod::Flag::None, "[Int, RpcValue]", {}, AccessLevel::Write}, - }; - return meta_methods; - static auto s_meta_methods = [this]() { - auto mm = Super::metaMethods(); - mm.push_back({METH_SET_RECORD, MetaMethod::Flag::None, "[int, Map]", {}, AccessLevel::Write }); - return mm; - }(); - return s_meta_methods; -} - -RpcValue CurrentStageRunsNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - if(shv_path.empty()) { - if(method == METH_SET_RECORD) { - auto *plugin = getPlugin(); - const auto &lst = params.asList(); - plugin->setRunsRecord(lst.value(0).toInt(), shv::coreqt::rpc::rpcValueToQVariant(lst.value(1))); - return nullptr; - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -void CurrentStageRunsNode::sendRunChangedSignal(const QVariant &qparam) -{ - auto param = shv::coreqt::rpc::qVariantToRpcValue(qparam); - Q_ASSERT(param.isList()); - Q_ASSERT(param.asList().value(0).toInt() > 0); // run.id - RpcSignal sig; - sig.setShvPath(shvPath().asString()); - sig.setMethod(SIG_RUN_CHANGED); - sig.setSource(METH_RECORD); - sig.setParams(param); - qfDebug() << "emit:" << sig.toPrettyString(); - emitSendRpcMessage(sig); -} - -//========================================================= -// CurrentStageClassesNode -//========================================================= -CurrentStageClassesNode::CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent) - : Super("classes", parent) -{ -} - -QueryBuilder CurrentStageClassesNode::effectiveQueryBuilder() -{ - auto *event_plugin = getPlugin(); - QueryBuilder qb; - qb.select2("classes", "*") - .select2("classdefs", "*") - .select2("courses", "id, name, length, climb") - .from("classes") - .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId=" QF_IARG(event_plugin->currentStageId())) - .join("classdefs.courseId", "courses.id") - .orderBy("classes.name");//.limit(10); - return qb; -} - - } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h index 917a75f2c..58cf1125e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h @@ -19,85 +19,4 @@ class DotAppNode : public QxNode shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; }; -class EventNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit EventNode(shv::iotqt::node::ShvNode *parent); -private: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -}; - -class SqlViewNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit SqlViewNode(const std::string &name, shv::iotqt::node::ShvNode *parent) - : Super(name, parent) - {} - void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); -protected: - virtual qf::core::sql::QueryBuilder effectiveQueryBuilder(); - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -private: - qf::core::sql::QueryBuilder m_queryBuilder; -}; - -class CurrentStageConfigNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit CurrentStageConfigNode(shv::iotqt::node::ShvNode *parent) - : Super("config", parent) - {} -protected: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -}; - -class CurrentStageStartListNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent); -protected: - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - -class CurrentStageRunsNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent); - void sendRunChangedSignal(const QVariant &qparam); -protected: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - -class CurrentStageClassesNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent); - -protected: - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e693f4b45..8aa7f5642 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -51,24 +51,8 @@ QxEventService::QxEventService(QObject *parent) : Super(QxEventService::serviceId(), parent) , m_rootNode(new shv::iotqt::node::ShvRootNode(this)) { - auto *event_plugin = getPlugin(); - // connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); - new DotAppNode(m_rootNode); - connect(event_plugin, &EventPlugin::eventOpenChanged, this, [this](bool is_open) { - if (is_open) { - new SqlApiNode(m_rootNode); - auto *event = new EventNode(m_rootNode); - auto *current_stage = new shv::iotqt::node::ShvNode("currentStage", event); - new CurrentStageConfigNode(current_stage); - //new CurrentStageStartListNode(current_stage); - new CurrentStageRunsNode(current_stage); - new CurrentStageClassesNode(current_stage); - } - else { - qDeleteAll(m_rootNode->findChildren()); - } - }); + new SqlApiNode(m_rootNode); connect(m_rootNode, &shv::iotqt::node::ShvNode::sendRpcMessage, this, &QxEventService::sendRpcMessage); } From f0d2a693f53c254b27d6c11db2357eae0d05be8c Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 22:10:51 +0100 Subject: [PATCH 07/52] QX SQL API node implemented --- .../Event/src/services/qx/sqlapinode.cpp | 244 +++++++++++++++--- .../Event/src/services/qx/sqlapinode.h | 3 - 2 files changed, 203 insertions(+), 44 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index ac90ce45f..846e6c7e6 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,32 +11,12 @@ #include #include +#include using namespace shv::chainpack; using namespace shv::coreqt::data; namespace Event::services::qx { -namespace { - -/* - -RpcSqlResult RpcSqlResult::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - RpcSqlResult ret; - const RpcValue::Map &map = rv.asMap(); - const RpcValue::List &flds = map.value("fields").asList(); - if(flds.empty()) { - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); - } - else { - - } - return ret; - -} -*/ -} SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) @@ -43,20 +24,60 @@ SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) } -shv::chainpack::RpcValue::Map SqlApiNode::recordToMap(const QSqlRecord &rec) +namespace { +class Transaction { - RpcValue::Map record; - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - auto fld_name = fld.name(); - fld_name.replace("__", "."); - record[fld_name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(fld.value()); +public: + Transaction(QSqlDatabase db) : m_db(db) { } - return record; -} + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void begin() { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + m_inTransaction = true; + } + void commit() { + if (m_inTransaction) { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } + } +private: + QSqlDatabase m_db; + bool m_inTransaction = false; +}; -RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) +RpcSqlResult rpcSqlQuery(const QString &query, const RpcValue ¶ms, bool in_transaction = false) { + auto conn = qf::core::sql::Connection::forName(); + Transaction tranaction(conn); + if (in_transaction) { + tranaction.begin(); + } + qf::core::sql::Query q(conn); + q.prepare(query, qf::core::Exception::Throw); + for (const auto &[k, v] : params.asMap()) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + if (in_transaction) { + tranaction.commit(); + } + RpcSqlResult ret; if(q.isSelect()) { QSqlRecord rec = q.record(); @@ -72,10 +93,12 @@ RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) RpcSqlResult::Row row; for (int i = 0; i < rec.count(); ++i) { const QVariant v = q.value(i); - if (v.isNull()) - row.append(QVariant()); - else + if (v.isNull()) { + row.append(QVariant::fromValue(nullptr)); + } + else { row.append(v); + } //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); } ret.rows.insert(ret.rows.count(), row); @@ -88,14 +111,29 @@ RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) return ret; } -static auto METH_EXEC_SQL = "execSql"; +auto METH_QUERY = "query"; +auto METH_EXEC = "exec"; +auto METH_TRANSACTION = "transaction"; +auto METH_LIST = "list"; +auto METH_CREATE = "create"; +auto METH_READ = "read"; +auto METH_UPDATE = "update"; +auto METH_DELETE = "delete"; +} const std::vector &SqlApiNode::metaMethods() { static std::vector meta_methods { methods::DIR, methods::LS, - {METH_EXEC_SQL, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Write}, + {METH_QUERY, MetaMethod::Flag::LargeResultHint, "[s:query,{s|i|b|t|n}:params]", "{{s:name}:fields,[[s|i|b|t|n]]:rows}", AccessLevel::Read}, + {METH_EXEC, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "{i:rows_affected,i|n:insert_id}", AccessLevel::Write}, + {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "n", AccessLevel::Write}, + {METH_LIST, MetaMethod::Flag::LargeResultHint, "{s:table,[s]|n:fields,i|n:ids_above,i|n:limit}", "[{s|i|b|t|n}]", AccessLevel::Write}, + {METH_CREATE, MetaMethod::Flag::None, "{s:table,{s|i|b|t|n}:record}", "i", AccessLevel::Write}, + {METH_READ, MetaMethod::Flag::None, "{s:table,i:id,{s}|n:fields}", "{s|i|b|t|n}|n", AccessLevel::Read}, + {METH_UPDATE, MetaMethod::Flag::None, "{s:table,i:id,{s|i|b|t|n}:record}", "b", AccessLevel::Write}, + {METH_DELETE, MetaMethod::Flag::None, "{s:table,i:id}", "b", AccessLevel::Write}, }; return meta_methods; } @@ -105,13 +143,137 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin qfLogFuncFrame() << shv_path.join('/') << method; //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { - if(method == METH_EXEC_SQL) { - qf::core::sql::Query q; - QString qs = params.to(); - q.exec(qs, qf::core::Exception::Throw); - auto res = rpcSqlResultFromQuery(q); + if(method == METH_EXEC) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params); + return res.toRpcValue(); + } + if(method == METH_QUERY) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params); return res.toRpcValue(); } + if(method == METH_TRANSACTION) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params, true); + return RpcValue(nullptr); + } + if(method == METH_LIST) { + const auto &map = params.asMap(); + QStringList fields; + for (const auto &fn : map.valref("fields").asList()) { + fields << fn.to(); + } + if (fields.isEmpty()) { + fields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(fields.join(',')).arg(map.value("table").to()); + if (auto ids_above = map.value("ids_above"); ids_above.isInt()) { + sql_query += " WHERE id > " + QString::number(ids_above.toInt()); + } + if (auto limit = map.value("limit"); limit.isInt()) { + sql_query += " LIMIT " + QString::number(limit.toInt()); + } + auto res = rpcSqlQuery(sql_query, {}); + if (res.rows.isEmpty()) { + return RpcValue(nullptr); + } + RpcValue::List ret; + for (const auto &row : res.rows) { + auto cells = row.toList(); + RpcValue::Map rec; + int n = 0; + for (const auto &field : res.fields) { + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + } + ret.push_back(rec); + } + return ret; + } + if(method == METH_CREATE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + const auto &record = map.valref("record").asMap(); + QStringList fields; + QStringList placeholders; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name; + placeholders << ':' + name; + } + QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") + .arg(table) + .arg(fields.join(',')) + .arg(placeholders.join(',')); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + return q.lastInsertId().toInt(); + } + if(method == METH_READ) { + const auto &map = params.asMap(); + QStringList fields; + for (const auto &fn : map.valref("fields").asList()) { + fields << fn.to(); + } + if (fields.isEmpty()) { + fields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") + .arg(fields.join(',')) + .arg(map.value("table").to()) + .arg(map.value("id").toInt()) ; + auto res = rpcSqlQuery(sql_query, {}); + if (res.rows.isEmpty()) { + return RpcValue(nullptr); + } + RpcValue::Map rec; + auto cells = res.rows[0].toList(); + int n = 0; + for (const auto &field : res.fields) { + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + } + return rec; + } + if(method == METH_UPDATE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + auto id = map.valref("id").toInt(); + const auto &record = map.valref("record").asMap(); + QStringList fields; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name + " = :" + name; + } + QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") + .arg(table) + .arg(fields.join(',')) + .arg(id); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + return q.numRowsAffected() == 1; + } + if(method == METH_DELETE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + auto id = map.valref("id").toInt(); + QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") + .arg(table) + .arg(id); + qf::core::sql::Query q; + q.exec(sql_query, qf::core::Exception::Throw); + return q.numRowsAffected() == 1; + } } return Super::callMethod(shv_path, method, params, user_id); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h index c2e112f26..a36f7ed8c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -16,9 +16,6 @@ class SqlApiNode : public QxNode using Super = QxNode; public: explicit SqlApiNode(shv::iotqt::node::ShvNode *parent); - - static shv::coreqt::data::RpcSqlResult rpcSqlResultFromQuery(QSqlQuery &q); - static shv::chainpack::RpcValue::Map recordToMap(const QSqlRecord &rec); protected: const std::vector &metaMethods() override; shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; From 9e325a7563bbb8988f7c7ec7f2c9fa9b081a0027 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 10 Nov 2025 23:10:32 +0100 Subject: [PATCH 08/52] QxSQL API in QE --- 3rdparty/libshv | 2 +- libqf/libqfgui/src/tableview.cpp | 2 +- quickevent/app/quickevent/CMakeLists.txt | 6 +- .../Event/src/services/qx/qxeventservice.cpp | 54 +-- .../Event/src/services/qx/qxeventservice.h | 6 +- .../src/services/qx/qxeventservicewidget.cpp | 34 +- .../plugins/Event/src/services/qx/sqlapi.cpp | 406 ++++++++++++++++++ .../plugins/Event/src/services/qx/sqlapi.h | 88 ++++ .../Event/src/services/qx/sqlapinode.cpp | 217 ++-------- .../plugins/Runs/src/runstablewidget.cpp | 28 ++ .../plugins/Runs/src/runstablewidget.h | 3 + .../quickevent/plugins/Runs/src/runswidget.h | 15 +- 12 files changed, 601 insertions(+), 260 deletions(-) create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h diff --git a/3rdparty/libshv b/3rdparty/libshv index 40f0b3cf2..f108e5d72 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 40f0b3cf213ab799fb17bf6a31ce527a8851ee30 +Subproject commit f108e5d72729c16ee5b27ee8a9096182a5ceb7fa diff --git a/libqf/libqfgui/src/tableview.cpp b/libqf/libqfgui/src/tableview.cpp index 72cae3801..bd3183e59 100644 --- a/libqf/libqfgui/src/tableview.cpp +++ b/libqf/libqfgui/src/tableview.cpp @@ -1146,7 +1146,7 @@ void TableView::rowExternallySaved(const QVariant &id) qf::core::sql::Query q; bool ok = q.exec(query_str); if (!ok) { - qfInfo() << "Query:" << query_str; + qfMessage() << "Query:" << query_str; qfWarning() << "SQL error:" << q.lastErrorText(); return; } diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 33b7c3607..b7c6e2e75 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(Qt6 REQUIRED COMPONENTS Core) - add_executable(quickevent plugins/CardReader/src/cardchecker.cpp plugins/CardReader/src/cardcheckerclassiccpp.cpp @@ -92,6 +90,7 @@ add_executable(quickevent plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui + plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp plugins/Event/src/services/qx/nodes.cpp @@ -276,8 +275,7 @@ target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAK target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) if (QF_WITH_LIBSHV) target_link_libraries(quickevent PUBLIC libshviotqt) -target_link_libraries(quickevent PRIVATE Qt6::Core) -target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) + target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 8aa7f5642..320548969 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,6 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" +#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -77,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/quickbox"; + device["mountPoint"] = "test/quickevent"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -86,28 +87,9 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - m_rpcConnection->open(); + connect(SqlApi::instance(), &SqlApi::recchng, this, &QxEventService::onRecchg); -// connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { -// if (reply->error() == QNetworkReply::NetworkError::NoError) { -// auto data = reply->readAll(); -// auto doc = QJsonDocument::fromJson(data); -// EventInfo event_info(doc.toVariant().toMap()); -// setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); -// m_eventId = event_info.id(); -// connectToSSE(m_eventId); -// if (!m_pollChangesTimer) { -// m_pollChangesTimer = new QTimer(this); -// connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); -// } -// pollQxChanges(); -// m_pollChangesTimer->start(10000); -// Super::run(); -// } -// else { -// qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); -// } -// }); + m_rpcConnection->open(); } void QxEventService::stop() @@ -615,6 +597,10 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) // shvLogFuncFrame() << msg.toCpon(); if(msg.isRequest()) { RpcRequest rq(msg); + if (rq.shvPath().asString().starts_with(".broker/")) { + // ignore broker discovery messages + return; + } qfMessage() << "RPC request received:" << rq.toPrettyString(); m_rootNode->handleRpcRequest(rq); } @@ -641,36 +627,22 @@ void QxEventService::subscribeChanges() QString shv_path = "test"; QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); - connect(rpc_call, &RpcCall::maybeResult, this, [this, shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { if(error.isValid()) { qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); } else { qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); - // generate data change without ret value check - m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 123); - QTimer::singleShot(500, this, [this, shv_path]() { - m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 321); - }); } }); rpc_call->start(); } -void QxEventService::testRpcCall() const +void QxEventService::onRecchg(const QxRecChng &chng) { - Q_ASSERT(m_rpcConnection); - auto *rpc_call = RpcCall::create(m_rpcConnection) - ->setShvPath("test") - ->setMethod("ls"); -// ->setTimeout(5000); - connect(rpc_call, &RpcCall::maybeResult, [](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { - if(error.isValid()) - qfError() << "RPC call error:" << error.toString(); - else - qfInfo() << "Got RPC response, result:" << result.toCpon(); - }); - rpc_call->start(); + if (isRunning()) { + m_rpcConnection->sendShvSignal("sql", "recchng", chng.toRpcValue()); + } } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 2a103a9d7..b25076626 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -13,11 +13,13 @@ namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { +struct QxRecChng; + class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "http://localhost:8000") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=test&password=test") public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; @@ -78,9 +80,9 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); + void onRecchg(const QxRecChng &chng); void subscribeChanges(); - void testRpcCall() const; private: void loadSettings() override; qf::gui::framework::DialogWidget *createDetailWidget() override; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 416bb25b4..125d70f16 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -1,5 +1,6 @@ #include "qxeventservicewidget.h" #include "ui_qxeventservicewidget.h" + #include "qxeventservice.h" #include @@ -8,6 +9,8 @@ #include #include +#include + #include #include #include @@ -113,22 +116,27 @@ void QxEventServiceWidget::updateOCheckListPostUrl() void QxEventServiceWidget::testConnection() { - auto *svc = service(); - Q_ASSERT(svc); - auto *reply = svc->getRemoteEventInfo(ui->edServerUrl->text(), ui->edApiToken->text()); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - ui->edEventId->setValue(event_info.id()); + using namespace shv::iotqt::rpc; + using namespace shv::chainpack; + + auto *rpc = new DeviceConnection(this); + rpc->setConnectionString(ui->edServerUrl->text()); + + connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { + if (is_connected) { + rpc->deleteLater(); setMessage(tr("Connected OK")); } - else { - setMessage(tr("Connection error: %1").arg(reply->errorString()), MessageType::Error); - } - reply->deleteLater(); }); + connect(rpc, &ClientConnection::socketError, this, [this, rpc](const QString &error) { + rpc->deleteLater(); + setMessage(tr("Connection error: %1").arg(error), MessageType::Error); + }); + connect(rpc, &ClientConnection::brokerLoginError, this, [this, rpc](const auto &error) { + rpc->deleteLater(); + setMessage(tr("Login error: %1").arg(QString::fromStdString(error.toString())), MessageType::Error); + }); + rpc->open(); } void QxEventServiceWidget::exportEventInfo() diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp new file mode 100644 index 000000000..fde4ae512 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -0,0 +1,406 @@ +#include "sqlapi.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +//============================================== +// RpcSqlField +//============================================== +RpcValue RpcSqlField::toRpcValue() const +{ + RpcValue::Map ret; + ret["name"] = name; + return RpcValue(std::move(ret)); +} + +RpcSqlField RpcSqlField::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + RpcSqlField ret; + const RpcValue::Map &map = rv.asMap(); + ret.name = map.value("name").asString(); + return ret; +} + +//============================================== +// RpcSqlResult +//============================================== +const RpcValue &RpcSqlResult::value(size_t row, size_t col) const +{ + if (row < rows.size()) { + const auto &cells = rows[row]; + if (col < cells.size()) { + return cells[col]; + } + } + static RpcValue s; + return s; +} + +const RpcValue& RpcSqlResult::value(size_t row, const std::string &name) const +{ + if (auto ix = columnIndex(name); ix.has_value()) { + return value(row, ix.value()); + } + static RpcValue s; + return s; +} + +void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) +{ + if (row < rows.size()) { + auto &r = rows[row]; + if (col < r.size()) { + r[col] = val; + } + } +} + +void RpcSqlResult::setValue(size_t row, const std::string &name, const RpcValue &val) +{ + if (auto ix = columnIndex(name); ix.has_value()) { + setValue(row, ix.value(), val); + } +} + +RpcValue::List RpcSqlResult::toRecordList() const +{ + RpcValue::List ret; + for (const auto &row : rows) { + SqlRecord rec; + int n = 0; + for (const auto &field : fields) { + rec[field.name] = row[n++]; + } + ret.push_back(rec); + } + return ret; +} + +std::optional RpcSqlResult::columnIndex(const std::string &name) const +{ + for (size_t col = 0; col < fields.size(); ++col) { + const auto &fld = fields[col]; + if (fld.name == name) { + return col; + } + } + return {}; +} + +RpcValue RpcSqlResult::toRpcValue() const +{ + RpcValue::Map ret; + if(isSelect()) { + RpcValue::List flds; + for(const auto &fld : this->fields) + flds.push_back(fld.toRpcValue()); + ret["fields"] = flds; + ret["rows"] = rows; + } + else { + ret["numRowsAffected"] = numRowsAffected; + ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + } + return ret; +} + +RpcSqlResult RpcSqlResult::fromRpcValue(const RpcValue &rv) +{ + RpcSqlResult ret; + const auto &map = rv.asMap(); + const auto &flds = map.valref("fields").asList(); + if(flds.empty()) { + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + } + else { + for(const auto &fv : flds) { + ret.fields.push_back(RpcSqlField::fromRpcValue(fv)); + } + for (const auto &row : map.value("rows").asList()) { + ret.rows.push_back(row.asList()); + } + } + return ret; +} + +SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + auto sql_query = rv.asList().valref(0).asString(); + const auto &sql_params = rv.asList().valref(0); + return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; +} + +RpcValue QxRecChng::toRpcValue() const +{ + RpcValue::Map ret; + ret["table"] = table.toStdString(); + ret["id"] = id; + ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); + auto rec_op_string = [](RecOp op) { + switch (op) { + case RecOp::Insert: return "Insert"; + case RecOp::Update: return "Update"; + case RecOp::Delete: return "Delete"; + } + return ""; + }; + ret["op"] = rec_op_string(op); + return ret; +} + +//============================================== +// SqlApi +//============================================== +SqlApi::SqlApi(QObject *parent) + : QObject{parent} +{ + +} + +SqlApi *SqlApi::instance() +{ + static auto *api = new SqlApi(QCoreApplication::instance()); + return api; +} + +namespace { + +class Transaction +{ +public: + Transaction(QSqlDatabase db) : m_db(db) { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + } + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void commit() { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } +private: + QSqlDatabase m_db; + bool m_inTransaction = true; +}; + +RpcSqlResult rpcSqlQuery(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(QString::fromUtf8(params.query), qf::core::Exception::Throw); + for (const auto &[k, v] : params.params) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(':' + QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + RpcSqlResult ret; + if(q.isSelect()) { + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + RpcSqlField rfld; + rfld.name = fld.name().toStdString(); + // rfld.name.replace("__", "."); + ret.fields.push_back(rfld); + } + while(q.next()) { + RpcSqlResult::Row row; + for (int i = 0; i < rec.count(); ++i) { + const QVariant v = q.value(i); + if (v.isNull()) { + row.push_back(RpcValue(nullptr)); + } + else { + row.push_back(shv::coreqt::rpc::qVariantToRpcValue(v)); + } + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); + } + ret.rows.push_back(row); + } + } + else { + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + } + return ret; +} + +} + +RpcSqlResult SqlApi::exec(const SqlQueryAndParams ¶ms) +{ + return rpcSqlQuery(params); +} + +RpcSqlResult SqlApi::query(const SqlQueryAndParams ¶ms) +{ + return rpcSqlQuery(params); +} + +void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) +{ + auto conn = qf::core::sql::Connection::forName(); + Transaction tranaction(conn); + qf::core::sql::Query q(conn); + q.prepare(QString::fromUtf8(query), qf::core::Exception::Throw); + for (const auto ¶m : params) { + for (const auto &[k, v] : param.asMap()) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(':' + QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + } + tranaction.commit(); +} + +RpcSqlResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +{ + QStringList qfields; + for (const auto &fn : fields) { + qfields << QString::fromStdString(fn); + } + if (qfields.isEmpty()) { + qfields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(qfields.join(',')).arg(QString::fromStdString(table)); + if (ids_above.has_value()) { + sql_query += " WHERE id > " + QString::number(ids_above.value()); + } + if (limit.has_value()) { + sql_query += " LIMIT " + QString::number(limit.value()); + } + auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); + return res; +} + +int64_t SqlApi::create(const std::string &table, const SqlRecord &record) +{ + QStringList fields; + QStringList placeholders; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name; + placeholders << ':' + name; + } + QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") + .arg(table) + .arg(fields.join(',')) + .arg(placeholders.join(',')); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + auto id = q.lastInsertId().toInt(); + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = shv::coreqt::rpc::rpcValueToQVariant(record), + .op = RecOp::Insert + }); + return id; +} + +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +{ + QStringList qfields; + for (const auto &fn : fields) { + qfields << QString::fromStdString(fn); + } + if (qfields.isEmpty()) { + qfields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") + .arg(qfields.join(',')) + .arg(QString::fromStdString(table)) + .arg(id) ; + auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); + auto lst = res.toRecordList(); + if (lst.empty()) { + return {}; + } + return lst[0].asMap(); +} + +bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &record) +{ + QStringList fields; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name + " = :" + name; + } + QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") + .arg(table) + .arg(fields.join(',')) + .arg(id); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + auto qv = shv::coreqt::rpc::rpcValueToQVariant(v); + q.bindValue(':' + QString::fromStdString(k), qv); + } + q.exec(qf::core::Exception::Throw); + bool updated = q.numRowsAffected() == 1; + if (updated) { + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = shv::coreqt::rpc::rpcValueToQVariant(record), + .op = RecOp::Update + }); + } + return updated; +} + +bool SqlApi::drop(const std::string &table, int64_t id) +{ + QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") + .arg(table) + .arg(id); + qf::core::sql::Query q; + q.exec(sql_query, qf::core::Exception::Throw); + bool is_drop = q.numRowsAffected() == 1; + if (is_drop) { + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = {}, + .op = RecOp::Delete + }); + } + return is_drop; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h new file mode 100644 index 000000000..3594ca510 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include + +namespace Event::services::qx { + +struct RpcSqlField +{ + std::string name; + + //explicit RpcSqlField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} + shv::chainpack::RpcValue toRpcValue() const; + // QVariant toVariant() const; + static RpcSqlField fromRpcValue(const shv::chainpack::RpcValue &rv); + // static RpcSqlField fromVariant(const QVariant &v); +}; + +struct RpcSqlResult +{ + int numRowsAffected = 0; + std::optional lastInsertId = 0; + std::vector fields; + using Row = shv::chainpack::RpcValue::List; + std::vector rows; + + RpcSqlResult() = default; + // explicit RpcSqlResult(const QSqlQuery &q); + + std::optional columnIndex(const std::string &name) const; + const shv::chainpack::RpcValue& value(size_t row, size_t col) const; + const shv::chainpack::RpcValue& value(size_t row, const std::string &name) const; + void setValue(size_t row, size_t col, const shv::chainpack::RpcValue &val); + void setValue(size_t row, const std::string &name, const shv::chainpack::RpcValue &val); + + bool isSelect() const {return !fields.empty();} + shv::chainpack::RpcValue toRpcValue() const; + shv::chainpack::RpcValue::List toRecordList() const; + // QVariant toVariant() const; + // static RpcSqlResult fromVariant(const QVariant &v); + static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +using SqlRecord = shv::chainpack::RpcValue::Map; + +struct SqlQueryAndParams +{ + std::string query; + SqlRecord params; + + static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +enum class RecOp { Insert, Update, Delete, }; + +struct QxRecChng +{ + QString table; + int64_t id; + QVariant record; + RecOp op; + + shv::chainpack::RpcValue toRpcValue() const; +}; + +class SqlApi : public QObject +{ + Q_OBJECT +public: + static SqlApi* instance(); + + Q_SIGNAL void recchng(const QxRecChng &chng); + + static RpcSqlResult exec(const SqlQueryAndParams ¶ms); + static RpcSqlResult query(const SqlQueryAndParams ¶ms); + static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); + static RpcSqlResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + static int64_t create(const std::string &table, const SqlRecord &record); + static std::optional read(const std::string &table, int64_t id, const std::vector &fields); + static bool update(const std::string &table, int64_t id, const SqlRecord &record); + static bool drop(const std::string &table, int64_t id); +private: + explicit SqlApi(QObject *parent = nullptr); +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 846e6c7e6..d12474363 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,4 +1,5 @@ #include "sqlapinode.h" +#include "sqlapi.h" #include #include @@ -21,96 +22,9 @@ namespace Event::services::qx { SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) { - } namespace { -class Transaction -{ -public: - Transaction(QSqlDatabase db) : m_db(db) { - } - ~Transaction() { - if (m_inTransaction) { - m_db.rollback(); - } - } - void begin() { - if (!m_db.transaction()) { - qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); - throw std::runtime_error("BEGIN transaction error"); - } - m_inTransaction = true; - } - void commit() { - if (m_inTransaction) { - if (!m_db.commit()) { - qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); - throw std::runtime_error("COMMIT transaction error"); - } - m_inTransaction = false; - } - } -private: - QSqlDatabase m_db; - bool m_inTransaction = false; -}; - -RpcSqlResult rpcSqlQuery(const QString &query, const RpcValue ¶ms, bool in_transaction = false) -{ - auto conn = qf::core::sql::Connection::forName(); - Transaction tranaction(conn); - if (in_transaction) { - tranaction.begin(); - } - qf::core::sql::Query q(conn); - q.prepare(query, qf::core::Exception::Throw); - for (const auto &[k, v] : params.asMap()) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(QString::fromStdString(k), val); - } - q.exec(qf::core::Exception::Throw); - if (in_transaction) { - tranaction.commit(); - } - - RpcSqlResult ret; - if(q.isSelect()) { - QSqlRecord rec = q.record(); - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - RpcSqlField rfld; - rfld.name = fld.name(); - rfld.name.replace("__", "."); - rfld.type = fld.metaType().id(); - ret.fields.append(rfld); - } - while(q.next()) { - RpcSqlResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - const QVariant v = q.value(i); - if (v.isNull()) { - row.append(QVariant::fromValue(nullptr)); - } - else { - row.append(v); - } - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.insert(ret.rows.count(), row); - } - } - else { - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); - } - return ret; -} - auto METH_QUERY = "query"; auto METH_EXEC = "exec"; auto METH_TRANSACTION = "transaction"; @@ -128,7 +42,7 @@ const std::vector &SqlApiNode::metaMethods() methods::LS, {METH_QUERY, MetaMethod::Flag::LargeResultHint, "[s:query,{s|i|b|t|n}:params]", "{{s:name}:fields,[[s|i|b|t|n]]:rows}", AccessLevel::Read}, {METH_EXEC, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "{i:rows_affected,i|n:insert_id}", AccessLevel::Write}, - {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "n", AccessLevel::Write}, + {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,[{s|i|b|t|n}]:params]", "n", AccessLevel::Write}, {METH_LIST, MetaMethod::Flag::LargeResultHint, "{s:table,[s]|n:fields,i|n:ids_above,i|n:limit}", "[{s|i|b|t|n}]", AccessLevel::Write}, {METH_CREATE, MetaMethod::Flag::None, "{s:table,{s|i|b|t|n}:record}", "i", AccessLevel::Write}, {METH_READ, MetaMethod::Flag::None, "{s:table,i:id,{s}|n:fields}", "{s|i|b|t|n}|n", AccessLevel::Read}, @@ -144,135 +58,66 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto sql_query = params.asList().valref(0).to(); - const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params); + auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto sql_query = params.asList().valref(0).to(); - const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params); + auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { - auto sql_query = params.asList().valref(0).to(); + auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params, true); + SqlApi::transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { const auto &map = params.asMap(); - QStringList fields; + const auto &table = map.value("table").asString(); + std::vector fields; for (const auto &fn : map.valref("fields").asList()) { - fields << fn.to(); - } - if (fields.isEmpty()) { - fields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(fields.join(',')).arg(map.value("table").to()); - if (auto ids_above = map.value("ids_above"); ids_above.isInt()) { - sql_query += " WHERE id > " + QString::number(ids_above.toInt()); - } - if (auto limit = map.value("limit"); limit.isInt()) { - sql_query += " LIMIT " + QString::number(limit.toInt()); - } - auto res = rpcSqlQuery(sql_query, {}); - if (res.rows.isEmpty()) { - return RpcValue(nullptr); + fields.push_back(fn.asString()); } - RpcValue::List ret; - for (const auto &row : res.rows) { - auto cells = row.toList(); - RpcValue::Map rec; - int n = 0; - for (const auto &field : res.fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); - } - ret.push_back(rec); - } - return ret; + auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); + auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); + auto res = SqlApi::list(table, fields, ids_above, limit); + return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); + const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - QStringList fields; - QStringList placeholders; - for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name; - placeholders << ':' + name; - } - QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") - .arg(table) - .arg(fields.join(',')) - .arg(placeholders.join(',')); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - return q.lastInsertId().toInt(); + auto res = SqlApi::create(table, record); + return res; } if(method == METH_READ) { const auto &map = params.asMap(); - QStringList fields; + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); + std::vector fields; for (const auto &fn : map.valref("fields").asList()) { - fields << fn.to(); - } - if (fields.isEmpty()) { - fields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") - .arg(fields.join(',')) - .arg(map.value("table").to()) - .arg(map.value("id").toInt()) ; - auto res = rpcSqlQuery(sql_query, {}); - if (res.rows.isEmpty()) { - return RpcValue(nullptr); + fields.push_back(fn.asString()); } - RpcValue::Map rec; - auto cells = res.rows[0].toList(); - int n = 0; - for (const auto &field : res.fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + auto res = SqlApi::read(table, id, fields); + if (res.has_value()) { + return res.value(); } - return rec; + return RpcValue(nullptr); } if(method == METH_UPDATE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); - auto id = map.valref("id").toInt(); + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - QStringList fields; - for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name + " = :" + name; - } - QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(table) - .arg(fields.join(',')) - .arg(id); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - return q.numRowsAffected() == 1; + auto res = SqlApi::update(table, id, record); + return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); + const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(table) - .arg(id); - qf::core::sql::Query q; - q.exec(sql_query, qf::core::Exception::Throw); - return q.numRowsAffected() == 1; + auto res = SqlApi::drop(table, id); + return res; } } return Super::callMethod(shv_path, method, params, user_id); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index a226ec7f0..4250dd0fe 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -20,6 +20,12 @@ #include #include +#include +#include +#include +#include +#include + #include #include #include @@ -130,6 +136,8 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } } }, Qt::QueuedConnection); + + connect(Event::services::qx::SqlApi::instance(), &Event::services::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -431,4 +439,24 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } +void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) +{ + std::optional run_id; + if (chng.table == "competitors") { + auto *m = m_runsModel; + for (auto i=0; irowCount(); ++i) { + if (m->table().row(i).value("competitors.id").toInt() == chng.id) { + run_id = m->value(i, "runs.id").toInt(); + break; + } + } + } + else if (chng.table == "runs") { + run_id = chng.id; + } + if (run_id.has_value()) { + ui->tblRuns->rowExternallySaved(run_id.value()); + } +} + diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index 8ba2e0964..a5379f689 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -7,6 +7,7 @@ class RunsTableItemDelegate; class CourseItemDelegate; namespace qf::gui { class TableView; } +namespace Event::services::qx { struct QxRecChng; } namespace Ui { class RunsTableWidget; @@ -36,6 +37,8 @@ class RunsTableWidget : public QWidget void onCustomContextMenuRequest(const QPoint &pos); void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); + + void onQxRecChng(const Event::services::qx::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index 9be9da658..9335be37d 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -11,19 +11,11 @@ class QTextStream; class QLabel; namespace qf { -namespace core { -namespace model { -class SqlTableModel; -} -} -namespace gui { -class ForeignKeyComboBox; -} +namespace core { namespace model { class SqlTableModel; } } +namespace gui { class ForeignKeyComboBox; } } -namespace Event { -class EventPlugin; -} +namespace Event { class EventPlugin; } namespace Ui { class RunsWidget; @@ -69,7 +61,6 @@ class RunsWidget : public QFrame void onDrawRemoveClicked(); void onCbxStageCurrentIndexChanged(); private: - /** * @brief runnersInClubsHistogram * @return list of runs.id for each club sorted by their count, longest list of runners is first From ee2002f29fc06af35531e4248e56c60e72c52086 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 12 Nov 2025 21:15:09 +0100 Subject: [PATCH 09/52] Do not reload whole row on qxSqlApi update recchng --- libqf/libqfcore/src/utils/table.cpp | 4 +++- .../plugins/Event/src/services/qx/sqlapi.cpp | 14 +++++++------- .../plugins/Event/src/services/qx/sqlapi.h | 4 ++-- .../plugins/Runs/src/runstablewidget.cpp | 18 +++++++++++++++++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/libqf/libqfcore/src/utils/table.cpp b/libqf/libqfcore/src/utils/table.cpp index 78d073307..c4210506b 100644 --- a/libqf/libqfcore/src/utils/table.cpp +++ b/libqf/libqfcore/src/utils/table.cpp @@ -1310,7 +1310,8 @@ QVariant Table::sumValue(int field_ix) const return ret; } -static void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString &str) +namespace { +void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString &str) { QDomNode nd = el.firstChild(); QDomText el_txt = nd.toText(); @@ -1322,6 +1323,7 @@ static void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QS el_txt.setData(str); } } +} QDomElement Table::toHtmlElement(QDomDocument &owner_doc, const QString & col_names, TextExportOptions opts) const { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index fde4ae512..2c58823bf 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -152,11 +152,11 @@ RpcValue QxRecChng::toRpcValue() const ret["table"] = table.toStdString(); ret["id"] = id; ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); - auto rec_op_string = [](RecOp op) { + auto rec_op_string = [](QxRecOp op) { switch (op) { - case RecOp::Insert: return "Insert"; - case RecOp::Update: return "Update"; - case RecOp::Delete: return "Delete"; + case QxRecOp::Insert: return "Insert"; + case QxRecOp::Update: return "Update"; + case QxRecOp::Delete: return "Delete"; } return ""; }; @@ -328,7 +328,7 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) .table = QString::fromStdString(table), .id = id, .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = RecOp::Insert + .op = QxRecOp::Insert }); return id; } @@ -378,7 +378,7 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor .table = QString::fromStdString(table), .id = id, .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = RecOp::Update + .op = QxRecOp::Update }); } return updated; @@ -397,7 +397,7 @@ bool SqlApi::drop(const std::string &table, int64_t id) .table = QString::fromStdString(table), .id = id, .record = {}, - .op = RecOp::Delete + .op = QxRecOp::Delete }); } return is_drop; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index 3594ca510..b8a8f64b9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -53,14 +53,14 @@ struct SqlQueryAndParams static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; -enum class RecOp { Insert, Update, Delete, }; +enum class QxRecOp { Insert, Update, Delete, }; struct QxRecChng { QString table; int64_t id; QVariant record; - RecOp op; + QxRecOp op; shv::chainpack::RpcValue toRpcValue() const; }; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 4250dd0fe..0011c3190 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -442,20 +442,36 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) { std::optional run_id; + int row = 0; if (chng.table == "competitors") { auto *m = m_runsModel; for (auto i=0; irowCount(); ++i) { if (m->table().row(i).value("competitors.id").toInt() == chng.id) { run_id = m->value(i, "runs.id").toInt(); + row = i; break; } } } else if (chng.table == "runs") { run_id = chng.id; + auto *m = m_runsModel; + for (auto i=0; irowCount(); ++i) { + if (m->table().row(i).value("runs.id").toInt() == chng.id) { + row = i; + break; + } + } } if (run_id.has_value()) { - ui->tblRuns->rowExternallySaved(run_id.value()); + if (chng.op == Event::services::qx::QxRecOp::Update) { + for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { + m_runsModel->setValue(row, k, v); + m_runsModel->setDirty(row, k, false); + } + } else { + ui->tblRuns->rowExternallySaved(run_id.value()); + } } } From 46e5ff01866497ca18cbf935e890f133d3d02135 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 17 Nov 2025 22:05:33 +0100 Subject: [PATCH 10/52] Recchng is emitted on SqlTableModel::postRow --- .../libqfcore/include/qf/core/sql/qxrecchng.h | 1 + libqf/libqfcore/src/sql/qxrecchng.h | 17 +++++ libqf/libqfgui/src/model/sqldatadocument.h | 8 +- libqf/libqfgui/src/model/sqltablemodel.cpp | 73 ++++++++++++------- libqf/libqfgui/src/model/sqltablemodel.h | 14 ++-- quickevent/app/quickevent/CMakeLists.txt | 5 +- .../Competitors/src/competitordocument.h | 8 +- .../Event/src/services/qx/qxeventservice.cpp | 10 +-- .../Event/src/services/qx/qxeventservice.h | 5 +- .../Event/src/services/qx/sqlapinode.cpp | 18 ++--- .../plugins/Runs/src/runsplugin.cpp | 2 +- .../plugins/Runs/src/runstablemodel.cpp | 10 ++- .../plugins/Runs/src/runstablemodel.h | 6 +- .../plugins/Runs/src/runstablewidget.cpp | 29 +++++--- .../plugins/Runs/src/runstablewidget.h | 4 +- .../Event/src/services => src}/qx/sqlapi.cpp | 64 +++++++++++----- .../Event/src/services => src}/qx/sqlapi.h | 23 ++---- .../app/quickevent/src/qx/sqldatadocument.cpp | 17 +++++ .../app/quickevent/src/qx/sqldatadocument.h | 21 ++++++ .../app/quickevent/src/qx/sqltablemodel.cpp | 13 ++++ .../app/quickevent/src/qx/sqltablemodel.h | 17 +++++ 21 files changed, 252 insertions(+), 113 deletions(-) create mode 100644 libqf/libqfcore/include/qf/core/sql/qxrecchng.h create mode 100644 libqf/libqfcore/src/sql/qxrecchng.h rename quickevent/app/quickevent/{plugins/Event/src/services => src}/qx/sqlapi.cpp (86%) rename quickevent/app/quickevent/{plugins/Event/src/services => src}/qx/sqlapi.h (85%) create mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.cpp create mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.h create mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.cpp create mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.h diff --git a/libqf/libqfcore/include/qf/core/sql/qxrecchng.h b/libqf/libqfcore/include/qf/core/sql/qxrecchng.h new file mode 100644 index 000000000..7f444660c --- /dev/null +++ b/libqf/libqfcore/include/qf/core/sql/qxrecchng.h @@ -0,0 +1 @@ +#include "../../../../src/sql/qxrecchng.h" diff --git a/libqf/libqfcore/src/sql/qxrecchng.h b/libqf/libqfcore/src/sql/qxrecchng.h new file mode 100644 index 000000000..5ec42a70e --- /dev/null +++ b/libqf/libqfcore/src/sql/qxrecchng.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../core/coreglobal.h" + +namespace qf::core::sql { + +enum class QxRecOp { Insert, Update, Delete, }; + +struct QFCORE_DECL_EXPORT QxRecChng +{ + QString table; + int64_t id; + QVariant record; + QxRecOp op; +}; + +} diff --git a/libqf/libqfgui/src/model/sqldatadocument.h b/libqf/libqfgui/src/model/sqldatadocument.h index ee56ac5b5..2a2b755e1 100644 --- a/libqf/libqfgui/src/model/sqldatadocument.h +++ b/libqf/libqfgui/src/model/sqldatadocument.h @@ -3,9 +3,7 @@ #include "datadocument.h" #include "sqltablemodel.h" -namespace qf { -namespace gui { -namespace model { +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument { @@ -21,7 +19,7 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument qf::core::sql::QueryBuilder queryBuilder(); void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); protected: - SqlTableModel* createModel(QObject *parent) Q_DECL_OVERRIDE; + SqlTableModel* createModel(QObject *parent) override; ///! load model persistent storage via model bool loadData() Q_DECL_OVERRIDE; @@ -35,5 +33,5 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument */ }; -}}} +} diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index 11075a550..a12705623 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -150,8 +151,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) int serial_ix = -1; bool serial_ix_explicitly_set = false; int primary_ix = -1; - //QSqlIndex pri_ix = ti.primaryIndex(); - //bool has_blob_field = false; + QVariantMap qx_record; Q_FOREACH(const qf::core::utils::Table::Field &fld, row_ref.fields()) { i++; if(fld.tableId() != table_id) @@ -186,6 +186,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) new_fld.setValue(v); //qfInfo() << "\t\t" << "val is QString:" << (v.metaType().id() == QMetaType::QString); rec.append(new_fld); + qx_record[fld.shortName().toLower()] = v; } } @@ -201,40 +202,23 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) } else { qs = sqldrv->sqlStatement(QSqlDriver::InsertStatement, table, rec, false); - //qs = fixSerialDefaultValue(qs, serial_ix, rec); } - if(qs.isEmpty()) + if(qs.isEmpty()) { continue; - /* - qfDebug() << "\texecuting prepared query:" << qs; - bool ok = q.prepare(qs); - if(!ok) { - qfError() << "Cannot prepare query:" << qs; } - else { - for(int i=0; i= 0 && !serial_ix_explicitly_set) { - QVariant v = q.lastInsertId(); - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; - if(v.isValid()) { - row_ref.setValue(serial_ix, v); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; + if(insert_id.isValid()) { + row_ref.setValue(serial_ix, insert_id); row_ref.setDirty(serial_ix, false); } else { @@ -251,6 +235,15 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) qfDebug() << "\tsetting value of foreign key" << slave_key << "to value of master key:" << row_ref.value(master_key).toString(); row_ref.setValue(slave_key, row_ref.value(master_key)); } + if (!qx_record.isEmpty() && master_key == "id") { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = insert_id.toInt(), + .record = qx_record, + .op = qf::core::sql::QxRecOp::Insert, + }; + emit qxRecChng(chng); + } } } else { @@ -268,7 +261,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) QSqlDriver *sqldrv = sql_conn.driver(); Q_FOREACH(QString table_id, tableIds(m_table.fields())) { qfDebug() << "\ttableid:" << table_id; - //table = conn.fullTableNameToQtDriverTableName(table); + QVariantMap qx_record; QSqlRecord edit_rec; int i = -1; Q_FOREACH(qfu::Table::Field fld, row_ref.fields()) { @@ -286,12 +279,13 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) //qfDebug() << "\ttableid:" << tableid << "fullTableName:" << fld.fullTableName(); qfDebug() << "\tdirty field" << fld.name() << "type:" << fld.type().id() << "orig val:" << row_ref.origValue(i).toString() << "new val:" << v.toString(); //qfDebug().noSpace() << "\tdirty value: '" << v.toString() << "' isNull(): " << v.isNull() << " type(): " << v.type(); - QSqlField sqlfld(fld.shortName(), fld.type()); + QSqlField sqlfld(fld.shortName().toLower(), fld.type()); sqlfld.setValue(v); //if(sqlfld.type() == QVariant::ByteArray) // has_blob_field = true; qfDebug() << "\tfield is null: " << sqlfld.isNull(); edit_rec.append(sqlfld); + qx_record[fld.shortName()] = v; } if(!edit_rec.isEmpty()) { qfDebug() << "updating table edits:" << table_id; @@ -300,7 +294,9 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) query_str += " "; QSqlRecord where_rec; qfDebug() << "looking for primary index of table:" << table_id; - Q_FOREACH(auto fld_name, sql_conn.primaryIndexFieldNames(table_id)) { + std::optional id_pri_key_value; + auto pri_keys = sql_conn.primaryIndexFieldNames(table_id); + for (const auto &fld_name : pri_keys) { QString full_fld_name = table_id + '.' + fld_name; qfDebug() << "\t checking value of field:" << full_fld_name; int fld_ix = m_table.fields().fieldIndex(full_fld_name); @@ -313,6 +309,9 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) sqlfld.setValue(row_ref.origValue(fld_ix)); qfDebug() << "\tpri index field" << full_fld_name << "type:" << sqlfld.metaType().id() << "orig val:" << row_ref.origValue(fld_ix) << "current val:" << row_ref.value(fld_ix); where_rec.append(sqlfld); + if (auto id = sqlfld.value().toInt(); id > 0) { + id_pri_key_value = id; + } } QF_ASSERT(!where_rec.isEmpty(), QString("pri keys values not generated for table '%1'").arg(table_id), @@ -335,6 +334,15 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) ret = false; break; } + if (!qx_record.isEmpty() && pri_keys.size() == 1 && pri_keys[0] == "id" && id_pri_key_value.has_value()) { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = id_pri_key_value.value(), + .record = qx_record, + .op = qf::core::sql::QxRecOp::Update, + }; + emit qxRecChng(chng); + } } } } @@ -437,6 +445,17 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) ret = false; break; } + if (where_rec.count() == 1 && where_rec.field(0).name() == "id") { + if (auto id = where_rec.field(0).value().toInt(); id < 0) { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = id, + .record = {}, + .op = qf::core::sql::QxRecOp::Delete, + }; + emit qxRecChng(chng); + } + } } } if(ret) { diff --git a/libqf/libqfgui/src/model/sqltablemodel.h b/libqf/libqfgui/src/model/sqltablemodel.h index 95a2cbebd..e055385f6 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.h +++ b/libqf/libqfgui/src/model/sqltablemodel.h @@ -10,12 +10,10 @@ #include #include -namespace qf { -namespace gui { -namespace sql { -class Connection; -} -namespace model { +namespace qf::core::sql { struct QxRecChng; } +namespace qf::gui::sql { class Connection; } + +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlTableModel : public TableModel { @@ -51,6 +49,8 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel int reloadRow(int row_no) Q_DECL_OVERRIDE; int reloadInserts(const QString &id_column_name) Q_DECL_OVERRIDE; QString reloadRowQuery(const QVariant &record_id); + + Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng); public: void setQueryBuilder(const qf::core::sql::QueryBuilder &qb, bool clear_columns = false); const qf::core::sql::QueryBuilder& queryBuilder() const; @@ -100,5 +100,5 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel QMap m_foreignKeyDependencies; }; -}}} +} diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b7c6e2e75..989371c71 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -90,7 +90,6 @@ add_executable(quickevent plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp plugins/Event/src/services/qx/nodes.cpp @@ -174,6 +173,10 @@ add_executable(quickevent plugins/Runs/Runs.qrc plugins/CardReader/CardReader.qrc + src/qx/sqlapi.cpp src/qx/sqlapi.h + src/qx/sqltablemodel.h src/qx/sqltablemodel.cpp + src/qx/sqldatadocument.h src/qx/sqldatadocument.cpp + src/appclioptions.cpp src/application.cpp src/loggerwidget.cpp diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index a394edbe4..ebca84986 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -1,24 +1,22 @@ #ifndef COMPETITORS_COMPETITORDOCUMENT_H #define COMPETITORS_COMPETITORDOCUMENT_H -#include +#include "src/qx/sqldatadocument.h" #include namespace Competitors { -class CompetitorDocument : public qf::gui::model::SqlDataDocument +class CompetitorDocument : public qx::SqlDataDocument { Q_OBJECT private: - typedef qf::gui::model::SqlDataDocument Super; + typedef qx::SqlDataDocument Super; public: CompetitorDocument(QObject *parent = nullptr); - //bool isSaveSiidToRuns() const {return m_saveSiidToRuns;} void setEmitDbEventsOnSave(bool b) {m_isEmitDbEventsOnSave = b;} - //void setSiid(const QVariant &siid, bool save_siid_to_runs); void setSiid(const QVariant &siid); QVariant siid() const; const QVector& runsIds() const {return m_runsIds;} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 320548969..1b8b1d7ec 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "sqlapi.h" +#include "src/qx/sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -78,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/quickevent"; + device["mountPoint"] = "test/qx/event/1"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -87,7 +87,7 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - connect(SqlApi::instance(), &SqlApi::recchng, this, &QxEventService::onRecchg); + connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); m_rpcConnection->open(); } @@ -638,10 +638,10 @@ void QxEventService::subscribeChanges() rpc_call->start(); } -void QxEventService::onRecchg(const QxRecChng &chng) +void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) { if (isRunning()) { - m_rpcConnection->sendShvSignal("sql", "recchng", chng.toRpcValue()); + m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index b25076626..2b5e2e1de 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -10,11 +10,10 @@ class QTimer; namespace shv::iotqt::node { class ShvNodeTree; class ShvRootNode; } namespace shv::iotqt::rpc { class DeviceConnection; } namespace shv::chainpack { class RpcMessage; class RpcError; } +namespace qf::core::sql { struct QxRecChng; } namespace Event::services::qx { -struct QxRecChng; - class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; @@ -80,7 +79,7 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - void onRecchg(const QxRecChng &chng); + void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index d12474363..bbeeb3b08 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,5 +1,5 @@ #include "sqlapinode.h" -#include "sqlapi.h" +#include "src/qx/sqlapi.h" #include #include @@ -58,17 +58,17 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - SqlApi::transaction(sql_query, sql_params.asList()); + ::qx::SqlApi::transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { @@ -80,14 +80,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); - auto res = SqlApi::list(table, fields, ids_above, limit); + auto res = ::qx::SqlApi::list(table, fields, ids_above, limit); return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - auto res = SqlApi::create(table, record); + auto res = ::qx::SqlApi::create(table, record); return res; } if(method == METH_READ) { @@ -98,7 +98,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin for (const auto &fn : map.valref("fields").asList()) { fields.push_back(fn.asString()); } - auto res = SqlApi::read(table, id, fields); + auto res = ::qx::SqlApi::read(table, id, fields); if (res.has_value()) { return res.value(); } @@ -109,14 +109,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin const auto &table = map.value("table").asString(); auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - auto res = SqlApi::update(table, id, record); + auto res = ::qx::SqlApi::update(table, id, record); return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - auto res = SqlApi::drop(table, id); + auto res = ::qx::SqlApi::drop(table, id); return res; } } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index da276191f..6163682c3 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1300,7 +1300,7 @@ qf::core::sql::QueryBuilder RunsPlugin::runsQuery(int stage_id, int class_id, bo qfs::QueryBuilder qb; qb.select2("runs", "*") .select2("classes", "name") - .select2("competitors", "id, iofId, registration, licence, ranking, siId, note") + .select2("competitors", "id, firstName, lastName, iofId, registration, licence, ranking, siId, note") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .select("lentcards.siid IS NOT NULL AS cardInLentTable") diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index fe183294e..5f2dfccda 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -1,8 +1,10 @@ #include "runstablemodel.h" +#include "../../Event/src/eventplugin.h" +#include "src/qx/sqlapi.h" + #include #include -#include "../../Event/src/eventplugin.h" #include #include @@ -81,6 +83,12 @@ QVariant RunsTableModel::data(const QModelIndex &index, int role) const QVariant RunsTableModel::value(int row_ix, int column_ix) const { + if(column_ix == col_competitorName) { + qf::core::utils::TableRow row = tableRow(row_ix); + auto first_name = row.value(QStringLiteral("firstName")).toString(); + auto last_name = row.value(QStringLiteral("lastName")).toString(); + return last_name + ' ' + first_name; + } if(column_ix == col_runFlags) { qf::core::utils::TableRow row = tableRow(row_ix); bool mis_punch = row.value(QStringLiteral("runs.misPunch")).toBool(); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h index 9379bfebd..94b1ca389 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h @@ -1,13 +1,13 @@ #ifndef RUNSTABLEMODEL_H #define RUNSTABLEMODEL_H -#include +#include "src/qx/sqltablemodel.h" -class RunsTableModel : public quickevent::gui::og::SqlTableModel +class RunsTableModel : public ::qx::SqlTableModel { Q_OBJECT private: - using Super = quickevent::gui::og::SqlTableModel; + using Super = ::qx::SqlTableModel; public: enum Columns { col_runs_isRunning = 0, diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 0011c3190..fd3ad352d 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -7,10 +7,14 @@ #include "runflagsdialog.h" #include "cardflagsdialog.h" +#include "src/qx/sqlapi.h" + #include -#include #include #include +#include +#include +#include #include #include @@ -20,11 +24,7 @@ #include #include -#include -#include -#include -#include -#include +#include #include #include @@ -137,7 +137,7 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } }, Qt::QueuedConnection); - connect(Event::services::qx::SqlApi::instance(), &Event::services::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); + connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -439,7 +439,7 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } -void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) +void RunsTableWidget::onQxRecChng(const qf::core::sql::QxRecChng &chng) { std::optional run_id; int row = 0; @@ -464,10 +464,17 @@ void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) } } if (run_id.has_value()) { - if (chng.op == Event::services::qx::QxRecOp::Update) { + if (chng.op == qf::core::sql::QxRecOp::Update) { for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { - m_runsModel->setValue(row, k, v); - m_runsModel->setDirty(row, k, false); + if (k == "firstname" || k == "lastname") { + auto &r = m_runsModel->tableRowRef(row); + r.setValue("competitors." + k, v); + auto ix = m_runsModel->index(row, RunsTableModel::col_competitorName); + m_runsModel->dataChanged(ix, ix); + } else { + m_runsModel->setValue(row, k, v); + m_runsModel->setDirty(row, k, false); + } } } else { ui->tblRuns->rowExternallySaved(run_id.value()); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index a5379f689..d7ed07e6e 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -7,7 +7,7 @@ class RunsTableItemDelegate; class CourseItemDelegate; namespace qf::gui { class TableView; } -namespace Event::services::qx { struct QxRecChng; } +namespace qf::core::sql { struct QxRecChng; } namespace Ui { class RunsTableWidget; @@ -38,7 +38,7 @@ class RunsTableWidget : public QWidget void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); - void onQxRecChng(const Event::services::qx::QxRecChng &chng); + void onQxRecChng(const qf::core::sql::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp similarity index 86% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp rename to quickevent/app/quickevent/src/qx/sqlapi.cpp index 2c58823bf..2fd65b9d8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -16,7 +17,7 @@ using namespace shv::chainpack; -namespace Event::services::qx { +namespace qx { //============================================== // RpcSqlField @@ -146,21 +147,22 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; } -RpcValue QxRecChng::toRpcValue() const + +RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { RpcValue::Map ret; - ret["table"] = table.toStdString(); - ret["id"] = id; - ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); - auto rec_op_string = [](QxRecOp op) { + ret["table"] = chng.table.toStdString(); + ret["id"] = chng.id; + ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(chng.record); + auto rec_op_string = [](qf::core::sql::QxRecOp op) { switch (op) { - case QxRecOp::Insert: return "Insert"; - case QxRecOp::Update: return "Update"; - case QxRecOp::Delete: return "Delete"; + case qf::core::sql::QxRecOp::Insert: return "Insert"; + case qf::core::sql::QxRecOp::Update: return "Update"; + case qf::core::sql::QxRecOp::Delete: return "Delete"; } return ""; }; - ret["op"] = rec_op_string(op); + ret["op"] = rec_op_string(chng.op); return ret; } @@ -179,6 +181,12 @@ SqlApi *SqlApi::instance() return api; } +void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) +{ + qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); + emit recchng(chng); +} + namespace { class Transaction @@ -303,7 +311,27 @@ RpcSqlResult SqlApi::list(const std::string &table, const std::vectorrecchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = QxRecOp::Insert + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), + .op = qf::core::sql::QxRecOp::Insert }); return id; } @@ -374,11 +402,11 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; if (updated) { - emit SqlApi::instance()->recchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = QxRecOp::Update + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), + .op = qf::core::sql::QxRecOp::Update }); } return updated; @@ -393,11 +421,11 @@ bool SqlApi::drop(const std::string &table, int64_t id) q.exec(sql_query, qf::core::Exception::Throw); bool is_drop = q.numRowsAffected() == 1; if (is_drop) { - emit SqlApi::instance()->recchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, .record = {}, - .op = QxRecOp::Delete + .op = qf::core::sql::QxRecOp::Delete }); } return is_drop; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h similarity index 85% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h rename to quickevent/app/quickevent/src/qx/sqlapi.h index b8a8f64b9..80bee09b2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -1,11 +1,15 @@ #pragma once #include +// #include +#include #include #include -namespace Event::services::qx { +namespace qf::core::sql { struct QxRecChng; } + +namespace qx { struct RpcSqlField { @@ -27,7 +31,6 @@ struct RpcSqlResult std::vector rows; RpcSqlResult() = default; - // explicit RpcSqlResult(const QSqlQuery &q); std::optional columnIndex(const std::string &name) const; const shv::chainpack::RpcValue& value(size_t row, size_t col) const; @@ -38,8 +41,6 @@ struct RpcSqlResult bool isSelect() const {return !fields.empty();} shv::chainpack::RpcValue toRpcValue() const; shv::chainpack::RpcValue::List toRecordList() const; - // QVariant toVariant() const; - // static RpcSqlResult fromVariant(const QVariant &v); static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); }; @@ -53,17 +54,8 @@ struct SqlQueryAndParams static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; -enum class QxRecOp { Insert, Update, Delete, }; - -struct QxRecChng -{ - QString table; - int64_t id; - QVariant record; - QxRecOp op; +shv::chainpack::RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng); - shv::chainpack::RpcValue toRpcValue() const; -}; class SqlApi : public QObject { @@ -71,7 +63,8 @@ class SqlApi : public QObject public: static SqlApi* instance(); - Q_SIGNAL void recchng(const QxRecChng &chng); + Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); + void emitRecChng(const qf::core::sql::QxRecChng &chng); static RpcSqlResult exec(const SqlQueryAndParams ¶ms); static RpcSqlResult query(const SqlQueryAndParams ¶ms); diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp new file mode 100644 index 000000000..1f7040921 --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp @@ -0,0 +1,17 @@ +#include "sqldatadocument.h" + + +namespace qx { + +SqlDataDocument::SqlDataDocument(QObject *parent) + : Super{parent} +{ + +} + +SqlTableModel *SqlDataDocument::createModel(QObject *parent) +{ + return new ::qx::SqlTableModel(parent); +} + +} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.h b/quickevent/app/quickevent/src/qx/sqldatadocument.h new file mode 100644 index 000000000..cea298076 --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqldatadocument.h @@ -0,0 +1,21 @@ +#pragma once + +#include "sqltablemodel.h" + +#include + +namespace qx { + +class SqlDataDocument : public qf::gui::model::SqlDataDocument +{ + Q_OBJECT + + using Super = qf::gui::model::SqlDataDocument; +public: + explicit SqlDataDocument(QObject *parent = nullptr); +protected: + ::qx::SqlTableModel* createModel(QObject *parent) override; +}; + +} // namespace qx + diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp new file mode 100644 index 000000000..0bad5b1fc --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp @@ -0,0 +1,13 @@ +#include "sqltablemodel.h" +#include "sqlapi.h" + +namespace qx { + +SqlTableModel::SqlTableModel(QObject *parent) + : Super{parent} +{ + auto *sql_api = SqlApi::instance(); + connect(this, &qf::gui::model::SqlTableModel::qxRecChng, sql_api, &SqlApi::emitRecChng); +} + +} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.h b/quickevent/app/quickevent/src/qx/sqltablemodel.h new file mode 100644 index 000000000..1581be19e --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqltablemodel.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace qx { + +class SqlTableModel : public quickevent::gui::og::SqlTableModel +{ + Q_OBJECT + + using Super = quickevent::gui::og::SqlTableModel; +public: + explicit SqlTableModel(QObject *parent = nullptr); +}; + +} // namespace qx + From 3581d8a42eb6ce42212f758e843500eaa74b5f47 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 23 Nov 2025 14:17:39 +0100 Subject: [PATCH 11/52] Fix CardReader model id column name --- .../app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 9fd066be2..f9a6a90fa 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -112,6 +112,7 @@ class Model : public quickevent::gui::og::SqlTableModel Model::Model(QObject *parent) : Super(parent) { + setIdColumnName("cards.id"); clearColumns(col_COUNT); setColumn(col_cards_id, ColumnDefinition("cards.id", "id").setReadOnly(true)); setColumn(col_cards_siId, ColumnDefinition("cards.siId", tr("SI")).setReadOnly(true).setCastType(qMetaTypeId())); From e780bf8d2604700cce6291302548c4ae95dd7d4b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 30 Nov 2025 20:36:51 +0100 Subject: [PATCH 12/52] Change default SHV mount point --- .../quickevent/plugins/Event/src/services/qx/qxeventservice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 1b8b1d7ec..e098be777 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -78,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/qx/event/1"; + device["mountPoint"] = "test/hsh2025"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); From 7904e27dc8819fffbde4cd4b7fbabcdd1040a9a2 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 19 Dec 2025 11:09:53 +0100 Subject: [PATCH 13/52] Enable QF_WITH_LIBSHV option in CMake action --- .github/actions/cmake/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/cmake/action.yml b/.github/actions/cmake/action.yml index 4ff81aa9e..ac208298e 100644 --- a/.github/actions/cmake/action.yml +++ b/.github/actions/cmake/action.yml @@ -66,6 +66,7 @@ runs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DUSE_QT6=${{ inputs.use_qt6 }} \ From c91655c22a2a6882905b46335225b6501c31d722 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 24 Dec 2025 00:08:01 +0100 Subject: [PATCH 14/52] Make service status text expandable --- .../plugins/Event/src/services/qx/qxeventservice.cpp | 2 ++ .../app/quickevent/plugins/Event/src/services/servicewidget.ui | 3 +++ 2 files changed, 5 insertions(+) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e098be777..de1c055b2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -584,11 +584,13 @@ void QxEventService::onBrokerConnectedChanged(bool is_connected) void QxEventService::onBrokerSocketError(const QString &err) { + qfWarning() << "onBrokerSocketError:" << err; setStatusMessage(tr("Broker socket error: %1").arg(err)); } void QxEventService::onBrokerLoginError(const shv::chainpack::RpcError &err) { + qfWarning() << "onBrokerLoginError:" << err.toString(); setStatusMessage(tr("Broker login error: %1").arg(err.toString())); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui index cdff60fa1..510d437a7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui @@ -87,6 +87,9 @@ neco neco + + true + From 603522be6eb7a0db2b0c18dcec2d0b72f82f822f Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 30 Jan 2026 23:51:09 +0100 Subject: [PATCH 15/52] Rebased on multi-course --- .clang-tidy | 2 +- 3rdparty/libshv | 2 +- .../quickevent/plugins/Classes/src/classeswidget.cpp | 10 +++++++--- .../plugins/Event/src/services/qx/qxeventservice.cpp | 2 +- .../Event/src/services/qx/qxeventservicewidget.cpp | 2 +- .../quickevent/plugins/Runs/src/runstablewidget.cpp | 1 - 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 64c3c28fa..543d5ca53 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -40,7 +40,7 @@ Checks: -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -cppcoreguidelines-use-enum-class + -cppcoreguidelines-use-enum-class, -hicpp-avoid-c-arrays, -hicpp-avoid-goto, -hicpp-braces-around-statements, diff --git a/3rdparty/libshv b/3rdparty/libshv index f108e5d72..f77c05674 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit f108e5d72729c16ee5b27ee8a9096182a5ceb7fa +Subproject commit f77c05674413e5bd5a1a98c894385e89805406d0 diff --git a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp index 8da479d89..c4750dadc 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp @@ -390,7 +390,8 @@ void ClassesWidget::importCourses(const QList &course_defs, con reload(); } -static QString normalize_course_name(const QString &course_name) +namespace { +QString normalize_course_name(const QString &course_name) { QString ret = qf::core::Collator::toAscii7(QLocale::Czech, course_name, false); ret.replace(' ', QString()); @@ -400,6 +401,7 @@ static QString normalize_course_name(const QString &course_name) ret.replace('-', '+'); return ret; } +} void ClassesWidget::import_ocad_txt() { @@ -566,7 +568,8 @@ void ClassesWidget::import_ocad_v8() } } -static QString element_text(const QDomElement &parent, const QString &tag_name) +namespace { +QString element_text(const QDomElement &parent, const QString &tag_name) { QDomElement el = parent.firstChildElement(tag_name); if(el.isNull()) @@ -574,13 +577,14 @@ static QString element_text(const QDomElement &parent, const QString &tag_name) return el.text(); } -static QString dump_element(const QDomElement &el) +QString dump_element(const QDomElement &el) { QString ret; QTextStream s(&ret); el.save(s, QDomNode::EncodingFromDocument); return ret; } +} void ClassesWidget::import_ocad_iofxml_2() { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index de1c055b2..e691cd9b8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -74,7 +74,7 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; - m_rpcConnection = new DeviceConnection(this); + m_rpcConnection = new DeviceConnection("QuickEvent", this); m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 125d70f16..4d0e0bcbb 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -119,7 +119,7 @@ void QxEventServiceWidget::testConnection() using namespace shv::iotqt::rpc; using namespace shv::chainpack; - auto *rpc = new DeviceConnection(this); + auto *rpc = new DeviceConnection("QuickEventTest", this); rpc->setConnectionString(ui->edServerUrl->text()); connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index fd3ad352d..217182124 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include From d542aaaf6248975cbe272de52bdeaf59745c1dea Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 14:49:36 +0100 Subject: [PATCH 16/52] Update CI linter ubuntu version --- .github/workflows/lint.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d41591f4c..312183ca7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,8 +8,8 @@ on: jobs: clang-tidy: - name: clang-tidy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clang-tidy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ @@ -26,8 +26,8 @@ jobs: exclude_files_pattern: ^quickevent/app/quickevent/plugins/Receipts/src/thirdparty/qrcodegen\.cpp$ clazy: - name: clazy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clazy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ From 74aa2d0ca818083e6210a1d81cfb3d882fb42ae4 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 14:50:26 +0100 Subject: [PATCH 17/52] Fix QxService login procedure --- .../Event/src/services/qx/qxeventservice.cpp | 48 ++++++++++++++----- .../Event/src/services/qx/qxeventservice.h | 3 +- .../src/services/qx/qxeventservicewidget.cpp | 35 +++++++++++--- .../Event/src/services/qx/runchangedialog.cpp | 2 +- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e691cd9b8..24f6bab19 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -74,11 +74,19 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; + m_eventId = 0; + + auto api_token = apiToken(); + if (api_token.isEmpty()) { + setStatus(Status::Stopped); + setStatusMessage(tr("API token is not set.")); + } + m_rpcConnection = new DeviceConnection("QuickEvent", this); m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/hsh2025"; + device["deviceId"] = api_token.toStdString(); opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -94,6 +102,7 @@ void QxEventService::run() { void QxEventService::stop() { + m_eventId = 0; disconnectSSE(); if (m_pollChangesTimer) { m_pollChangesTimer->stop(); @@ -265,19 +274,19 @@ QNetworkReply* QxEventService::getQxChangesReply(int from_id) int QxEventService::eventId() const { - if (m_eventId == 0) { - throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); - } + // if (m_eventId == 0) { + // throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); + // } return m_eventId; } -QByteArray QxEventService::apiToken() const +QString QxEventService::apiToken() const { - // API token must not be cached to enable service point + // API token must not be cached to enable service to point // always to current stage event on qxhttpd auto *event_plugin = getPlugin(); auto current_stage = event_plugin->currentStageId(); - return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); + return event_plugin->stageData(current_stage).qxApiToken(); } QUrl QxEventService::shvBrokerUrl() const @@ -296,7 +305,7 @@ void QxEventService::postFileCompressed(std::optional path, std::option } QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/zip")); auto zdata = zlibCompress(data); QNetworkReply *reply = networkManager()->post(request, zdata); @@ -347,7 +356,7 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); auto data = QJsonDocument::fromVariant(json).toJson(QJsonDocument::Compact); qfInfo() << "HTTP POST JSON:" << url.toString() << "data:" << QString::fromUtf8(data); @@ -571,11 +580,24 @@ int QxEventService::currentConnectionId() void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { - setStatus(Status::Running); - QTimer::singleShot(0, this, [this]() { - subscribeChanges(); -// testRpcCall(); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) + ->setShvPath(".broker/currentClient") + ->setMethod("info"); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setStatus(Status::Stopped); + setStatusMessage(tr("Client info discovery error: %1").arg(error.toString())); + } + else { + const auto &info = result.asMap(); + m_eventMountPoint = info.value("mountPoint").to(); + m_eventId = m_eventMountPoint.section('/', -1, -1).toInt(); + setStatus(Status::Running); + setStatusMessage(tr("Event ID: %1").arg(m_eventId)); + subscribeChanges(); + } }); + rpc_call->start(); } else { setStatus(Status::Stopped); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 2b5e2e1de..add24852e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -68,7 +68,7 @@ class QxEventService : public Service QNetworkReply* getQxChangesReply(int from_id); - QByteArray apiToken() const; + QString apiToken() const; static int currentConnectionId(); QUrl shvBrokerUrl() const; @@ -105,6 +105,7 @@ class QxEventService : public Service QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; int m_eventId = 0; + QString m_eventMountPoint; QTimer *m_pollChangesTimer = nullptr; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 4d0e0bcbb..d18cd0e38 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include @@ -41,6 +43,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) ui->edServerUrl->setText(settings.shvBrokerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); + ui->edEventId->setValue(svc->eventId()); connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); @@ -119,21 +122,41 @@ void QxEventServiceWidget::testConnection() using namespace shv::iotqt::rpc; using namespace shv::chainpack; + delete findChild(); + auto *rpc = new DeviceConnection("QuickEventTest", this); rpc->setConnectionString(ui->edServerUrl->text()); + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = ui->edApiToken->text().toStdString(); + opts["device"] = device; + rpc->setConnectionOptions(opts); connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { if (is_connected) { - rpc->deleteLater(); - setMessage(tr("Connected OK")); + setMessage(tr("Broker connected OK")); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(rpc) + ->setShvPath(".broker/currentClient") + ->setMethod("info"); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setMessage(tr("Client info discovery error: %1").arg(error.toString()), MessageType::Error); + } + else { + const auto &info = result.asMap(); + auto mount_point = info.value("mountPoint").to(); + auto event_id = mount_point.section('/', -1, -1).toInt(); + ui->edEventId->setValue(event_id); + setMessage(tr("Event mounted at: %1, event id: %2").arg(mount_point).arg(event_id)); + } + }); + rpc_call->start(); } }); - connect(rpc, &ClientConnection::socketError, this, [this, rpc](const QString &error) { - rpc->deleteLater(); + connect(rpc, &ClientConnection::socketError, this, [this](const QString &error) { setMessage(tr("Connection error: %1").arg(error), MessageType::Error); }); - connect(rpc, &ClientConnection::brokerLoginError, this, [this, rpc](const auto &error) { - rpc->deleteLater(); + connect(rpc, &ClientConnection::brokerLoginError, this, [this](const auto &error) { setMessage(tr("Login error: %1").arg(QString::fromStdString(error.toString())), MessageType::Error); }); rpc->open(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index 1c16121ce..e2c50edd5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -220,7 +220,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) url.setQuery(query); request.setUrl(url); - request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken()); + request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken().toUtf8()); auto *reply = nm->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NetworkError::NoError) { From e338eedc4ef95ffdd7aa950f994dfd31b2edb0e0 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 17:13:02 +0100 Subject: [PATCH 18/52] Fix CI linter --- .github/actions/run-linter/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/run-linter/action.yml b/.github/actions/run-linter/action.yml index ec63c078a..0d1ddaf1b 100644 --- a/.github/actions/run-linter/action.yml +++ b/.github/actions/run-linter/action.yml @@ -24,7 +24,7 @@ runs: - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8 if: runner.os == 'Linux' with: - os: ubuntu-latest + os: ubuntu-24.04 - name: Build autogenerated stuff shell: bash From 60c37b41d0a425b124b309b73aba0f8b81aa7e8b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 8 Apr 2026 22:20:22 +0200 Subject: [PATCH 19/52] Rebased to origin/main --- quickevent/app/quickevent/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 989371c71..3aae94906 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -114,10 +114,11 @@ add_executable(quickevent plugins/Receipts/src/receiptssettings.cpp plugins/Receipts/src/receiptssettingspage.cpp plugins/Receipts/src/receiptssettingspage.ui - plugins/Receipts/src/thirdparty/qrcodegen.cpp plugins/Receipts/src/receiptswidget.cpp plugins/Receipts/src/receiptswidget.ui + plugins/Receipts/src/thirdparty/qrcodegen.cpp + plugins/Relays/src/addlegdialogwidget.cpp plugins/Relays/src/addlegdialogwidget.ui plugins/Relays/src/relaydocument.cpp From 27fba468b2d278c6048e49a017073f2cc31bead6 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 8 Apr 2026 23:03:53 +0200 Subject: [PATCH 20/52] Remove duplicate include --- quickevent/app/quickevent/CMakeLists.txt | 4 ---- .../app/quickevent/plugins/Runs/src/runstablewidget.cpp | 1 - 2 files changed, 5 deletions(-) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 3aae94906..999a783d0 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -282,10 +282,6 @@ if (QF_WITH_LIBSHV) target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() -foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) - install(DIRECTORY plugins/${plugin}/qml/reports DESTINATION ${CMAKE_INSTALL_BINDIR}/reports/${plugin}/qml) -endforeach() - # Extract version from appversion.h file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/appversion.h" APP_VERSION_H) string(REGEX MATCH "#define APP_VERSION \"([0-9.]+)\"" _ ${APP_VERSION_H}) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 217182124..80e5cf2a3 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include From a3a1a13ad2b27054e324546d654bbd1c957660af Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Apr 2026 09:15:40 +0200 Subject: [PATCH 21/52] Fix clang-tidy warnings --- libqf/libqfgui/src/framework/ipersistentsettings.cpp | 7 +++++-- libqf/libqfgui/src/framework/ipersistentsettings.h | 6 ++---- .../plugins/CardReader/src/cardreaderwidget.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.cpp b/libqf/libqfgui/src/framework/ipersistentsettings.cpp index 7fff48174..2d66f0235 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.cpp +++ b/libqf/libqfgui/src/framework/ipersistentsettings.cpp @@ -5,7 +5,7 @@ #include #include -using namespace qf::gui::framework; +namespace qf::gui::framework { IPersistentSettings::IPersistentSettings(QObject *controlled_object) : m_controlledObject(controlled_object) @@ -39,7 +39,8 @@ QString IPersistentSettings::persistentSettingsPath() return m_persistentSettingsPath; } -static void callMethodRecursively(QObject *obj, const char *method_name) +namespace { +void callMethodRecursively(QObject *obj, const char *method_name) { if(!obj) return; @@ -57,6 +58,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) //level--; } } +} void IPersistentSettings::loadPersistentSettingsRecursively() { @@ -123,3 +125,4 @@ QString IPersistentSettings::rawPersistentSettingsPath() return raw_path.join('/'); } +} diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.h b/libqf/libqfgui/src/framework/ipersistentsettings.h index 239dae588..272bd7efd 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.h +++ b/libqf/libqfgui/src/framework/ipersistentsettings.h @@ -7,9 +7,7 @@ class QObject; -namespace qf { -namespace gui { -namespace framework { +namespace qf::gui::framework { class QFGUI_DECL_EXPORT IPersistentSettings { @@ -38,6 +36,6 @@ class QFGUI_DECL_EXPORT IPersistentSettings QString m_persistentSettingsPath; }; -}}} +} #endif diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index f9a6a90fa..c5f3ac33a 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -842,13 +842,14 @@ CardReaderSettings::ReaderMode CardReaderWidget::currentReaderMode() const return s.readerModeEnum(); } -static int msecToSISec(int msec) +namespace { +int msecToSISec(int msec) { //static constexpr int secs_to_noon = 12 * 60 * 60; return (msec / 1000);// % secs_to_noon; } -static int obStringTosec(const QString &time_str) +int obStringTosec(const QString &time_str) { bool ok; int min = time_str.section('.', 0, 0).toInt(&ok); @@ -864,7 +865,7 @@ static int obStringTosec(const QString &time_str) return (60 * min + sec) * 1000; } -static QList codesForClassName(const QString &class_name, int stage_id) +QList codesForClassName(const QString &class_name, int stage_id) { QList ret; int course_id = 0; @@ -899,6 +900,7 @@ static QList codesForClassName(const QString &class_name, int stage_id) QF_ASSERT_EX(ret.count() > 0, QString("Cannot load codes for class %1 and stage %2").arg(class_name).arg(stage_id)); return ret; } +} void CardReaderWidget::importCards_lapsOnlyCsv() { From a6d3fbaecbcda97785b2c8206af977fc904db7f0 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Apr 2026 12:01:53 +0200 Subject: [PATCH 22/52] Add workaround for clazy in action.yml Added a workaround step to remove gcc-14 for clazy. --- .github/actions/run-linter/action.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/actions/run-linter/action.yml b/.github/actions/run-linter/action.yml index 0d1ddaf1b..fe0e71558 100644 --- a/.github/actions/run-linter/action.yml +++ b/.github/actions/run-linter/action.yml @@ -20,6 +20,13 @@ runs: modules: qtserialport qtmultimedia additional_cmake_args: -DCMAKE_GLOBAL_AUTOGEN_TARGET=ON -DCMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF + # https://bugs.launchpad.net/ubuntu/+source/clazy/+bug/2086665/comments/4 + - name: Workaround for clazy + shell: bash + run: | + sudo apt-get remove gcc-14 + sudo apt-get autoremove + # workaround for clang-tidy false positive - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8 if: runner.os == 'Linux' From 833dc773973279d245c6492454016b15cc0f012d Mon Sep 17 00:00:00 2001 From: lukaskett Date: Tue, 21 Apr 2026 22:28:32 +0200 Subject: [PATCH 23/52] fix: add missing -DQF_WITH_LIBSHV=ON for macOS --- .github/workflows/macos.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 204e971dc..56a5bf5af 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,6 +37,7 @@ jobs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DUSE_QT6=ON \ -DCOMMIT_SHA=${{ env.COMMIT_SHA }} \ From 98f57f26f25e069b43db1cf6d8e2441acb08eb88 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:44:11 +0200 Subject: [PATCH 24/52] Update libshv --- 3rdparty/libshv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index f77c05674..6d4367ff6 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit f77c05674413e5bd5a1a98c894385e89805406d0 +Subproject commit 6d4367ff6d06de670ea1f49fe534730c3605fbf5 From b0f2e39b5a534ce499aa12753dcf0c9e551d224b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:44:42 +0200 Subject: [PATCH 25/52] Fix broken build --- libqf/libqfcore/src/utils/treetable.cpp | 8 ++-- libqf/libqfgui/src/model/sqltablemodel.cpp | 36 ++------------- libqf/libqfgui/src/model/sqltablemodel.h | 2 - quickevent/app/quickevent/CMakeLists.txt | 2 - .../Competitors/src/competitordocument.h | 10 ++-- .../plugins/Runs/src/runstablemodel.h | 10 ++-- .../plugins/Runs/src/runstablewidget.cpp | 46 ------------------- .../plugins/Runs/src/runstablewidget.h | 2 - quickevent/app/quickevent/src/qx/sqlapi.cpp | 29 ++++-------- .../app/quickevent/src/qx/sqldatadocument.cpp | 17 ------- .../app/quickevent/src/qx/sqldatadocument.h | 21 --------- .../app/quickevent/src/qx/sqltablemodel.cpp | 13 ------ .../app/quickevent/src/qx/sqltablemodel.h | 17 ------- 13 files changed, 26 insertions(+), 187 deletions(-) delete mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.cpp delete mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.h delete mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.cpp delete mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.h diff --git a/libqf/libqfcore/src/utils/treetable.cpp b/libqf/libqfcore/src/utils/treetable.cpp index c18b2fdee..5641dea4f 100644 --- a/libqf/libqfcore/src/utils/treetable.cpp +++ b/libqf/libqfcore/src/utils/treetable.cpp @@ -581,8 +581,8 @@ QVariantMap TreeTable::keyvals(int row_ix) const { return rows().value(row_ix).toMap().value(KEY_KEYVALS).toMap(); } - -static inline QString line_indent(const QString &ind, int level) +namespace { +QString line_indent(const QString &ind, int level) { QString s; for(int i=0; i= 0 && !serial_ix_explicitly_set) { - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; - if(insert_id.isValid()) { - row_ref.setValue(serial_ix, insert_id); + QVariant v = q.lastInsertId(); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; + if(v.isValid()) { + row_ref.setValue(serial_ix, v); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { app->emitDbRecInserted(table_id, v.value(), record_to_map(rec), this); @@ -250,15 +251,6 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) qfDebug() << "\tsetting value of foreign key" << slave_key << "to value of master key:" << row_ref.value(master_key).toString(); row_ref.setValue(slave_key, row_ref.value(master_key)); } - if (!qx_record.isEmpty() && master_key == "id") { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = insert_id.toInt(), - .record = qx_record, - .op = qf::core::sql::QxRecOp::Insert, - }; - emit qxRecChng(chng); - } } } else { @@ -355,15 +347,6 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) ret = false; break; } - if (!qx_record.isEmpty() && pri_keys.size() == 1 && pri_keys[0] == "id" && id_pri_key_value.has_value()) { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = id_pri_key_value.value(), - .record = qx_record, - .op = qf::core::sql::QxRecOp::Update, - }; - emit qxRecChng(chng); - } } } } @@ -472,17 +455,6 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) ret = false; break; } - if (where_rec.count() == 1 && where_rec.field(0).name() == "id") { - if (auto id = where_rec.field(0).value().toInt(); id < 0) { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = id, - .record = {}, - .op = qf::core::sql::QxRecOp::Delete, - }; - emit qxRecChng(chng); - } - } } } if(ret) { diff --git a/libqf/libqfgui/src/model/sqltablemodel.h b/libqf/libqfgui/src/model/sqltablemodel.h index dc2f93082..ce9e2313f 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.h +++ b/libqf/libqfgui/src/model/sqltablemodel.h @@ -49,8 +49,6 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel int reloadRow(int row_no) override; int reloadInserts(const QString &id_column_name) override; QString reloadRowQuery(const QVariant &record_id); - - Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng); public: void setQueryBuilder(const qf::core::sql::QueryBuilder &qb, bool clear_columns = false); const qf::core::sql::QueryBuilder& queryBuilder() const; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index cc071641a..0d89b41dc 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -178,8 +178,6 @@ add_executable(quickevent plugins/CardReader/CardReader.qrc src/qx/sqlapi.cpp src/qx/sqlapi.h - src/qx/sqltablemodel.h src/qx/sqltablemodel.cpp - src/qx/sqldatadocument.h src/qx/sqldatadocument.cpp src/appclioptions.cpp src/application.cpp diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index ebca84986..b282d4ebb 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -1,17 +1,16 @@ -#ifndef COMPETITORS_COMPETITORDOCUMENT_H -#define COMPETITORS_COMPETITORDOCUMENT_H +#pragma once -#include "src/qx/sqldatadocument.h" +#include #include namespace Competitors { -class CompetitorDocument : public qx::SqlDataDocument +class CompetitorDocument : public qf::gui::model::SqlDataDocument { Q_OBJECT private: - typedef qx::SqlDataDocument Super; + typedef qf::gui::model::SqlDataDocument Super; public: CompetitorDocument(QObject *parent = nullptr); @@ -31,4 +30,3 @@ class CompetitorDocument : public qx::SqlDataDocument } -#endif // COMPETITORS_COMPETITORDOCUMENT_H diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h index ffdcdfea9..08bc5a67c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h @@ -1,15 +1,14 @@ -#ifndef RUNSTABLEMODEL_H -#define RUNSTABLEMODEL_H +#pragma once -#include "src/qx/sqltablemodel.h" +#include namespace qf::core::sql { struct QxRecChng; } -class RunsTableModel : public ::qx::SqlTableModel +class RunsTableModel : public quickevent::gui::og::SqlTableModel { Q_OBJECT private: - using Super = ::qx::SqlTableModel; + using Super = quickevent::gui::og::SqlTableModel; public: enum Columns { col_runs_isRunning = 0, @@ -67,4 +66,3 @@ class RunsTableModel : public ::qx::SqlTableModel void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); }; -#endif // RUNSTABLEMODEL_H diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 80e5cf2a3..1b8604a65 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -134,8 +134,6 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } } }, Qt::QueuedConnection); - - connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -437,47 +435,3 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } -void RunsTableWidget::onQxRecChng(const qf::core::sql::QxRecChng &chng) -{ - std::optional run_id; - int row = 0; - if (chng.table == "competitors") { - auto *m = m_runsModel; - for (auto i=0; irowCount(); ++i) { - if (m->table().row(i).value("competitors.id").toInt() == chng.id) { - run_id = m->value(i, "runs.id").toInt(); - row = i; - break; - } - } - } - else if (chng.table == "runs") { - run_id = chng.id; - auto *m = m_runsModel; - for (auto i=0; irowCount(); ++i) { - if (m->table().row(i).value("runs.id").toInt() == chng.id) { - row = i; - break; - } - } - } - if (run_id.has_value()) { - if (chng.op == qf::core::sql::QxRecOp::Update) { - for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { - if (k == "firstname" || k == "lastname") { - auto &r = m_runsModel->tableRowRef(row); - r.setValue("competitors." + k, v); - auto ix = m_runsModel->index(row, RunsTableModel::col_competitorName); - m_runsModel->dataChanged(ix, ix); - } else { - m_runsModel->setValue(row, k, v); - m_runsModel->setDirty(row, k, false); - } - } - } else { - ui->tblRuns->rowExternallySaved(run_id.value()); - } - } -} - - diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index d7ed07e6e..263ddbd5c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -37,8 +37,6 @@ class RunsTableWidget : public QWidget void onCustomContextMenuRequest(const QPoint &pos); void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); - - void onQxRecChng(const qf::core::sql::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 2fd65b9d8..4782b259c 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -150,20 +150,8 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { - RpcValue::Map ret; - ret["table"] = chng.table.toStdString(); - ret["id"] = chng.id; - ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(chng.record); - auto rec_op_string = [](qf::core::sql::QxRecOp op) { - switch (op) { - case qf::core::sql::QxRecOp::Insert: return "Insert"; - case qf::core::sql::QxRecOp::Update: return "Update"; - case qf::core::sql::QxRecOp::Delete: return "Delete"; - } - return ""; - }; - ret["op"] = rec_op_string(chng.op); - return ret; + auto m = chng.toVariantMap(); + return shv::coreqt::rpc::qVariantToRpcValue(m); } //============================================== @@ -355,8 +343,9 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), - .op = qf::core::sql::QxRecOp::Insert + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + .op = qf::core::sql::RecOp::Insert, + .issuer = {} }); return id; } @@ -405,8 +394,9 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), - .op = qf::core::sql::QxRecOp::Update + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + .op = qf::core::sql::RecOp::Update, + .issuer = {} }); } return updated; @@ -425,7 +415,8 @@ bool SqlApi::drop(const std::string &table, int64_t id) .table = QString::fromStdString(table), .id = id, .record = {}, - .op = qf::core::sql::QxRecOp::Delete + .op = qf::core::sql::RecOp::Delete, + .issuer = {} }); } return is_drop; diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp deleted file mode 100644 index 1f7040921..000000000 --- a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "sqldatadocument.h" - - -namespace qx { - -SqlDataDocument::SqlDataDocument(QObject *parent) - : Super{parent} -{ - -} - -SqlTableModel *SqlDataDocument::createModel(QObject *parent) -{ - return new ::qx::SqlTableModel(parent); -} - -} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.h b/quickevent/app/quickevent/src/qx/sqldatadocument.h deleted file mode 100644 index cea298076..000000000 --- a/quickevent/app/quickevent/src/qx/sqldatadocument.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "sqltablemodel.h" - -#include - -namespace qx { - -class SqlDataDocument : public qf::gui::model::SqlDataDocument -{ - Q_OBJECT - - using Super = qf::gui::model::SqlDataDocument; -public: - explicit SqlDataDocument(QObject *parent = nullptr); -protected: - ::qx::SqlTableModel* createModel(QObject *parent) override; -}; - -} // namespace qx - diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp deleted file mode 100644 index 0bad5b1fc..000000000 --- a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "sqltablemodel.h" -#include "sqlapi.h" - -namespace qx { - -SqlTableModel::SqlTableModel(QObject *parent) - : Super{parent} -{ - auto *sql_api = SqlApi::instance(); - connect(this, &qf::gui::model::SqlTableModel::qxRecChng, sql_api, &SqlApi::emitRecChng); -} - -} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.h b/quickevent/app/quickevent/src/qx/sqltablemodel.h deleted file mode 100644 index 1581be19e..000000000 --- a/quickevent/app/quickevent/src/qx/sqltablemodel.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace qx { - -class SqlTableModel : public quickevent::gui::og::SqlTableModel -{ - Q_OBJECT - - using Super = quickevent::gui::og::SqlTableModel; -public: - explicit SqlTableModel(QObject *parent = nullptr); -}; - -} // namespace qx - From c6072f3a8682ae92153428fd489c1e7945e28b18 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:54:47 +0200 Subject: [PATCH 26/52] Fix linter warnings --- quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp | 6 +++--- quickevent/app/quickevent/src/qx/sqlapi.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index c4e14910b..5eeb13cd8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -1362,8 +1362,8 @@ void EventPlugin::deleteEvent(const QString &event_name) .arg(event_name, q.lastErrorText())); } } - -static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) +namespace { +void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) { dst.setHostName(src.hostName()); dst.setPort(src.port()); @@ -1371,7 +1371,7 @@ static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) dst.setPassword(src.password()); dst.setDatabaseName(src.databaseName()); } - +} bool EventPlugin::importEventFromFile(const QString &src_file, const QString &dest_event_name) { qfLogFuncFrame(); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 4782b259c..0581d77e0 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace shv::chainpack; @@ -305,7 +306,7 @@ std::string to_lower(const std::string &s) std::string result; result.reserve(s.size()); - std::transform(s.begin(), s.end(), std::back_inserter(result), + std::ranges::transform(s, std::back_inserter(result), [](unsigned char c) { return std::tolower(c); }); return result; From 0d20550dd22c5901aca7bceb87b24ee12c80dd45 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 22:11:12 +0200 Subject: [PATCH 27/52] Fix broken build --- 3rdparty/libshv | 2 +- .../Event/src/services/qx/qxeventservice.cpp | 2 +- quickevent/app/quickevent/src/qx/sqlapi.cpp | 55 ++++++++++--------- quickevent/app/quickevent/src/qx/sqlapi.h | 4 +- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index 6d4367ff6..b1c3721d5 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 6d4367ff6d06de670ea1f49fe534730c3605fbf5 +Subproject commit b1c3721d53f7390598e8f4fd8e5a3b225b58e5d0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 24f6bab19..92eb101c5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -95,7 +95,7 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); + // connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); m_rpcConnection->open(); } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 0581d77e0..a3329ef51 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -161,7 +162,7 @@ RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) SqlApi::SqlApi(QObject *parent) : QObject{parent} { - + // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) } SqlApi *SqlApi::instance() @@ -170,11 +171,11 @@ SqlApi *SqlApi::instance() return api; } -void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) -{ - qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); - emit recchng(chng); -} +// void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) +// { +// qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); +// emit recchng(chng); +// } namespace { @@ -341,13 +342,13 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) } q.exec(qf::core::Exception::Throw); auto id = q.lastInsertId().toInt(); - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - .op = qf::core::sql::RecOp::Insert, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + // .op = qf::core::sql::RecOp::Insert, + // .issuer = {} + // }); return id; } @@ -392,13 +393,13 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; if (updated) { - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - .op = qf::core::sql::RecOp::Update, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + // .op = qf::core::sql::RecOp::Update, + // .issuer = {} + // }); } return updated; } @@ -412,13 +413,13 @@ bool SqlApi::drop(const std::string &table, int64_t id) q.exec(sql_query, qf::core::Exception::Throw); bool is_drop = q.numRowsAffected() == 1; if (is_drop) { - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = {}, - .op = qf::core::sql::RecOp::Delete, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = {}, + // .op = qf::core::sql::RecOp::Delete, + // .issuer = {} + // }); } return is_drop; } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h index 80bee09b2..413b302dd 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -63,8 +63,8 @@ class SqlApi : public QObject public: static SqlApi* instance(); - Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); - void emitRecChng(const qf::core::sql::QxRecChng &chng); + // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); + // void emitRecChng(const qf::core::sql::QxRecChng &chng); static RpcSqlResult exec(const SqlQueryAndParams ¶ms); static RpcSqlResult query(const SqlQueryAndParams ¶ms); From efe865e374b2948548a8c10eee88594537fa2938 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 22:45:07 +0200 Subject: [PATCH 28/52] SqlApi rewrite --- .../Event/src/services/qx/sqlapinode.cpp | 2 +- quickevent/app/quickevent/src/qx/sqlapi.cpp | 242 ++++++++++-------- quickevent/app/quickevent/src/qx/sqlapi.h | 56 ++-- 3 files changed, 165 insertions(+), 135 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index bbeeb3b08..037061733 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -100,7 +100,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto res = ::qx::SqlApi::read(table, id, fields); if (res.has_value()) { - return res.value(); + return shv::coreqt::rpc::qVariantToRpcValue(res.value()); } return RpcValue(nullptr); } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index a3329ef51..d70dfc1a3 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -24,25 +24,45 @@ namespace qx { //============================================== // RpcSqlField //============================================== -RpcValue RpcSqlField::toRpcValue() const +RpcValue DbField::toRpcValue() const { RpcValue::Map ret; - ret["name"] = name; + ret["name"] = name.toStdString(); return RpcValue(std::move(ret)); } -RpcSqlField RpcSqlField::fromRpcValue(const shv::chainpack::RpcValue &rv) +DbField DbField::fromRpcValue(const shv::chainpack::RpcValue &rv) { - RpcSqlField ret; + DbField ret; const RpcValue::Map &map = rv.asMap(); - ret.name = map.value("name").asString(); + ret.name = QString::fromStdString(map.value("name").asString()); return ret; } //============================================== -// RpcSqlResult +// ExecResult //============================================== -const RpcValue &RpcSqlResult::value(size_t row, size_t col) const +RpcValue ExecResult::toRpcValue() const +{ + RpcValue::Map ret; + ret["numRowsAffected"] = numRowsAffected; + ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + return ret; +} + +ExecResult ExecResult::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + ExecResult ret; + const auto &map = rv.asMap(); + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + return ret; +} + +//============================================== +// QueryResult +//============================================== +QVariant QueryResult::value(qsizetype row, qsizetype col) const { if (row < rows.size()) { const auto &cells = rows[row]; @@ -50,20 +70,18 @@ const RpcValue &RpcSqlResult::value(size_t row, size_t col) const return cells[col]; } } - static RpcValue s; - return s; + return {}; } -const RpcValue& RpcSqlResult::value(size_t row, const std::string &name) const +QVariant QueryResult::value(qsizetype row, const std::string &name) const { if (auto ix = columnIndex(name); ix.has_value()) { return value(row, ix.value()); } - static RpcValue s; - return s; + return {}; } -void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) +void QueryResult::setValue(qsizetype row, qsizetype col, const QVariant &val) { if (row < rows.size()) { auto &r = rows[row]; @@ -73,80 +91,81 @@ void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) } } -void RpcSqlResult::setValue(size_t row, const std::string &name, const RpcValue &val) +void QueryResult::setValue(qsizetype row, const std::string &name, const QVariant &val) { if (auto ix = columnIndex(name); ix.has_value()) { setValue(row, ix.value(), val); } } -RpcValue::List RpcSqlResult::toRecordList() const +RpcValue::List QueryResult::toRecordList() const { RpcValue::List ret; for (const auto &row : rows) { - SqlRecord rec; + RpcValue::Map rec; int n = 0; for (const auto &field : fields) { - rec[field.name] = row[n++]; + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(row.value(n++)); } ret.push_back(rec); } return ret; } -std::optional RpcSqlResult::columnIndex(const std::string &name) const +std::optional QueryResult::columnIndex(const std::string &name) const { for (size_t col = 0; col < fields.size(); ++col) { const auto &fld = fields[col]; - if (fld.name == name) { + if (fld.name.toStdString() == name) { return col; } } return {}; } -RpcValue RpcSqlResult::toRpcValue() const +RpcValue QueryResult::toRpcValue() const { RpcValue::Map ret; - if(isSelect()) { - RpcValue::List flds; - for(const auto &fld : this->fields) - flds.push_back(fld.toRpcValue()); - ret["fields"] = flds; - ret["rows"] = rows; - } - else { - ret["numRowsAffected"] = numRowsAffected; - ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + RpcValue::List flds; + for(const auto &fld : this->fields) + flds.push_back(fld.toRpcValue()); + RpcValue::List rpc_rows; + for (const auto &row : rows) { + RpcValue::List rpc_row; + for (const auto &cell : row) { + rpc_row.push_back(shv::coreqt::rpc::qVariantToRpcValue(cell)); + } + rpc_rows.push_back(std::move(rpc_row)); } + ret["fields"] = flds; + ret["rows"] = std::move(rpc_rows); return ret; } -RpcSqlResult RpcSqlResult::fromRpcValue(const RpcValue &rv) +QueryResult QueryResult::fromRpcValue(const RpcValue &rv) { - RpcSqlResult ret; + QueryResult ret; const auto &map = rv.asMap(); const auto &flds = map.valref("fields").asList(); - if(flds.empty()) { - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); + for(const auto &fv : flds) { + ret.fields.push_back(DbField::fromRpcValue(fv)); } - else { - for(const auto &fv : flds) { - ret.fields.push_back(RpcSqlField::fromRpcValue(fv)); - } - for (const auto &row : map.value("rows").asList()) { - ret.rows.push_back(row.asList()); + for (const auto &rpc_row : map.value("rows").asList()) { + QueryResult::Row row; + for (const auto &cell : rpc_row.asList()) { + row.push_back(shv::coreqt::rpc::rpcValueToQVariant(cell)); } + ret.rows.push_back(row); } return ret; } SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) { - auto sql_query = rv.asList().valref(0).asString(); - const auto &sql_params = rv.asList().valref(0); - return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; + const auto &lst = rv.asList(); + auto sql_query = QString::fromStdString(lst.valref(0).asString()); + const auto &sql_params = lst.valref(1); + return SqlQueryAndParams { .query = sql_query, .params = shv::coreqt::rpc::rpcValueToQVariant(sql_params).toMap() }; } @@ -205,61 +224,63 @@ class Transaction bool m_inTransaction = true; }; -RpcSqlResult rpcSqlQuery(const SqlQueryAndParams ¶ms) +void bindParams(qf::core::sql::Query &q, const Record ¶ms) { - qf::core::sql::Query q; - q.prepare(QString::fromUtf8(params.query), qf::core::Exception::Throw); - for (const auto &[k, v] : params.params) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(':' + QString::fromStdString(k), val); + for (const auto &[k, v] : params.asKeyValueRange()) { + q.bindValue(':' + k, v); } +} + +QueryResult sqlQuery(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(params.query, qf::core::Exception::Throw); + bindParams(q, params.params); q.exec(qf::core::Exception::Throw); - RpcSqlResult ret; - if(q.isSelect()) { - QSqlRecord rec = q.record(); + + QueryResult ret; + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + DbField rfld; + rfld.name = fld.name(); + // rfld.name.replace("__", "."); + ret.fields.push_back(rfld); + } + while(q.next()) { + QueryResult::Row row; for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - RpcSqlField rfld; - rfld.name = fld.name().toStdString(); - // rfld.name.replace("__", "."); - ret.fields.push_back(rfld); + row.push_back(q.value(i)); + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); } - while(q.next()) { - RpcSqlResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - const QVariant v = q.value(i); - if (v.isNull()) { - row.push_back(RpcValue(nullptr)); - } - else { - row.push_back(shv::coreqt::rpc::qVariantToRpcValue(v)); - } - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.push_back(row); - } - } - else { - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); + ret.rows.insert(ret.rows.size(), row); } return ret; } +ExecResult sqlExec(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(params.query, qf::core::Exception::Throw); + bindParams(q, params.params); + q.exec(qf::core::Exception::Throw); + + ExecResult ret; + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + return ret; } -RpcSqlResult SqlApi::exec(const SqlQueryAndParams ¶ms) +} + +ExecResult SqlApi::exec(const SqlQueryAndParams ¶ms) { - return rpcSqlQuery(params); + return sqlExec(params); } -RpcSqlResult SqlApi::query(const SqlQueryAndParams ¶ms) +QueryResult SqlApi::query(const SqlQueryAndParams ¶ms) { - return rpcSqlQuery(params); + return sqlQuery(params); } void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) @@ -282,7 +303,7 @@ void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValu tranaction.commit(); } -RpcSqlResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +QueryResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) { QStringList qfields; for (const auto &fn : fields) { @@ -298,7 +319,7 @@ RpcSqlResult SqlApi::list(const std::string &table, const std::vector SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) { QStringList qfields; for (const auto &fn : fields) { @@ -365,30 +387,34 @@ std::optional SqlApi::read(const std::string &table, int64_t id, cons .arg(qfields.join(',')) .arg(QString::fromStdString(table)) .arg(id) ; - auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); - auto lst = res.toRecordList(); - if (lst.empty()) { + auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); + if (res.rows.empty()) { return {}; } - return lst[0].asMap(); + Record ret; + const auto &row = res.rows.first(); + for (qsizetype i = 0; i < std::ssize(res.fields); ++i) { + ret[res.fields[i].name] = row.value(i); + } + return ret; } -bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &record) +bool SqlApi::update(const std::string &table, int64_t id, const RpcValue::Map &record) { QStringList fields; for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name + " = :" + name; + Q_UNUSED(v) + auto qk = QString::fromStdString(k); + fields << qk + " = :" + qk; } QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(table) + .arg(QString::fromStdString(table)) .arg(fields.join(',')) .arg(id); qf::core::sql::Query q; q.prepare(sql_query, qf::core::Exception::Throw); for (const auto &[k, v] : record) { - auto qv = shv::coreqt::rpc::rpcValueToQVariant(v); - q.bindValue(':' + QString::fromStdString(k), qv); + q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); } q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; @@ -407,7 +433,7 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor bool SqlApi::drop(const std::string &table, int64_t id) { QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(table) + .arg(QString::fromStdString(table)) .arg(id); qf::core::sql::Query q; q.exec(sql_query, qf::core::Exception::Throw); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h index 413b302dd..fe15ed6f2 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -11,45 +11,49 @@ namespace qf::core::sql { struct QxRecChng; } namespace qx { -struct RpcSqlField +struct DbField { - std::string name; + QString name; - //explicit RpcSqlField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} + //explicit DbField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} shv::chainpack::RpcValue toRpcValue() const; // QVariant toVariant() const; - static RpcSqlField fromRpcValue(const shv::chainpack::RpcValue &rv); - // static RpcSqlField fromVariant(const QVariant &v); + static DbField fromRpcValue(const shv::chainpack::RpcValue &rv); + // static DbField fromVariant(const QVariant &v); }; -struct RpcSqlResult +struct ExecResult { int numRowsAffected = 0; std::optional lastInsertId = 0; - std::vector fields; - using Row = shv::chainpack::RpcValue::List; - std::vector rows; - RpcSqlResult() = default; + shv::chainpack::RpcValue toRpcValue() const; + static ExecResult fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +struct QueryResult +{ + std::vector fields; + using Row = QVariantList; + QList rows; - std::optional columnIndex(const std::string &name) const; - const shv::chainpack::RpcValue& value(size_t row, size_t col) const; - const shv::chainpack::RpcValue& value(size_t row, const std::string &name) const; - void setValue(size_t row, size_t col, const shv::chainpack::RpcValue &val); - void setValue(size_t row, const std::string &name, const shv::chainpack::RpcValue &val); + std::optional columnIndex(const std::string &name) const; + QVariant value(qsizetype row, qsizetype col) const; + QVariant value(qsizetype row, const std::string &name) const; + void setValue(qsizetype row, qsizetype col, const QVariant &val); + void setValue(qsizetype row, const std::string &name, const QVariant &val); - bool isSelect() const {return !fields.empty();} shv::chainpack::RpcValue toRpcValue() const; + static QueryResult fromRpcValue(const shv::chainpack::RpcValue &rv); shv::chainpack::RpcValue::List toRecordList() const; - static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); }; -using SqlRecord = shv::chainpack::RpcValue::Map; +using Record = QVariantMap; struct SqlQueryAndParams { - std::string query; - SqlRecord params; + QString query; + Record params; static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; @@ -66,13 +70,13 @@ class SqlApi : public QObject // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); // void emitRecChng(const qf::core::sql::QxRecChng &chng); - static RpcSqlResult exec(const SqlQueryAndParams ¶ms); - static RpcSqlResult query(const SqlQueryAndParams ¶ms); + static ExecResult exec(const SqlQueryAndParams ¶ms); + static QueryResult query(const SqlQueryAndParams ¶ms); static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - static RpcSqlResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); - static int64_t create(const std::string &table, const SqlRecord &record); - static std::optional read(const std::string &table, int64_t id, const std::vector &fields); - static bool update(const std::string &table, int64_t id, const SqlRecord &record); + static QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + static int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); + static std::optional read(const std::string &table, int64_t id, const std::vector &fields); + static bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); static bool drop(const std::string &table, int64_t id); private: explicit SqlApi(QObject *parent = nullptr); From 2b216441e15621b768e37ede64ed51f95a824ad3 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 3 Jun 2026 14:59:41 +0200 Subject: [PATCH 29/52] Move SqlApi to EventPlugin --- quickevent/app/quickevent/CMakeLists.txt | 3 +-- .../Event/src/services/qx/qxeventservice.cpp | 14 ++++++------- .../Event/src/services/qx/qxeventservice.h | 2 +- .../Event/src/services}/qx/sqlapi.cpp | 8 +------ .../Event/src/services}/qx/sqlapi.h | 21 +++++++++---------- .../Event/src/services/qx/sqlapinode.cpp | 19 +++++++++-------- .../Event/src/services/qx/sqlapinode.h | 4 ++++ .../plugins/Runs/src/runstablemodel.cpp | 1 - .../plugins/Runs/src/runstablewidget.cpp | 2 -- 9 files changed, 34 insertions(+), 40 deletions(-) rename quickevent/app/quickevent/{src => plugins/Event/src/services}/qx/sqlapi.cpp (98%) rename quickevent/app/quickevent/{src => plugins/Event/src/services}/qx/sqlapi.h (69%) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 0d89b41dc..94fbaec8c 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -95,6 +95,7 @@ add_executable(quickevent plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp + plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/nodes.cpp plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -177,8 +178,6 @@ add_executable(quickevent plugins/Runs/Runs.qrc plugins/CardReader/CardReader.qrc - src/qx/sqlapi.cpp src/qx/sqlapi.h - src/appclioptions.cpp src/application.cpp src/loggerwidget.cpp diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 92eb101c5..be06570b9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "src/qx/sqlapi.h" +#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -662,11 +662,11 @@ void QxEventService::subscribeChanges() rpc_call->start(); } -void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) -{ - if (isRunning()) { - m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); - } -} +// void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) +// { +// if (isRunning()) { +// m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); +// } +// } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index add24852e..fc5af4120 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -79,7 +79,7 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); + // void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp similarity index 98% rename from quickevent/app/quickevent/src/qx/sqlapi.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index d70dfc1a3..859e29c82 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -19,7 +19,7 @@ using namespace shv::chainpack; -namespace qx { +namespace Event::services::qx { //============================================== // RpcSqlField @@ -184,12 +184,6 @@ SqlApi::SqlApi(QObject *parent) // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) } -SqlApi *SqlApi::instance() -{ - static auto *api = new SqlApi(QCoreApplication::instance()); - return api; -} - // void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) // { // qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h similarity index 69% rename from quickevent/app/quickevent/src/qx/sqlapi.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index fe15ed6f2..d02966a7e 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -9,7 +9,7 @@ namespace qf::core::sql { struct QxRecChng; } -namespace qx { +namespace Event::services::qx { struct DbField { @@ -65,21 +65,20 @@ class SqlApi : public QObject { Q_OBJECT public: - static SqlApi* instance(); + explicit SqlApi(QObject *parent = nullptr); // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); // void emitRecChng(const qf::core::sql::QxRecChng &chng); - static ExecResult exec(const SqlQueryAndParams ¶ms); - static QueryResult query(const SqlQueryAndParams ¶ms); - static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - static QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); - static int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); - static std::optional read(const std::string &table, int64_t id, const std::vector &fields); - static bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); - static bool drop(const std::string &table, int64_t id); + ExecResult exec(const SqlQueryAndParams ¶ms); + QueryResult query(const SqlQueryAndParams ¶ms); + void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); + QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); + std::optional read(const std::string &table, int64_t id, const std::vector &fields); + bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); + bool drop(const std::string &table, int64_t id); private: - explicit SqlApi(QObject *parent = nullptr); }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 037061733..e18c89d97 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,5 +1,5 @@ #include "sqlapinode.h" -#include "src/qx/sqlapi.h" +#include "sqlapi.h" #include #include @@ -21,6 +21,7 @@ namespace Event::services::qx { SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) + , m_sqlApi(new SqlApi(this)) { } @@ -58,17 +59,17 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - ::qx::SqlApi::transaction(sql_query, sql_params.asList()); + m_sqlApi->transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { @@ -80,14 +81,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); - auto res = ::qx::SqlApi::list(table, fields, ids_above, limit); + auto res = m_sqlApi->list(table, fields, ids_above, limit); return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - auto res = ::qx::SqlApi::create(table, record); + auto res = m_sqlApi->create(table, record); return res; } if(method == METH_READ) { @@ -98,7 +99,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin for (const auto &fn : map.valref("fields").asList()) { fields.push_back(fn.asString()); } - auto res = ::qx::SqlApi::read(table, id, fields); + auto res = m_sqlApi->read(table, id, fields); if (res.has_value()) { return shv::coreqt::rpc::qVariantToRpcValue(res.value()); } @@ -109,14 +110,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin const auto &table = map.value("table").asString(); auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - auto res = ::qx::SqlApi::update(table, id, record); + auto res = m_sqlApi->update(table, id, record); return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - auto res = ::qx::SqlApi::drop(table, id); + auto res = m_sqlApi->drop(table, id); return res; } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h index a36f7ed8c..d461ae689 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -9,6 +9,8 @@ namespace shv::coreqt::data { class RpcSqlResult; } namespace Event::services::qx { +class SqlApi; + class SqlApiNode : public QxNode { Q_OBJECT @@ -19,6 +21,8 @@ class SqlApiNode : public QxNode protected: const std::vector &metaMethods() override; shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +private: + SqlApi *m_sqlApi = nullptr; }; } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 17e70cae1..52b93fad2 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -1,7 +1,6 @@ #include "runstablemodel.h" #include "../../Event/src/eventplugin.h" -#include "src/qx/sqlapi.h" #include #include diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 1b8604a65..9f7377b97 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -7,8 +7,6 @@ #include "runflagsdialog.h" #include "cardflagsdialog.h" -#include "src/qx/sqlapi.h" - #include #include #include From ec77db0a99b71b7939ba53a8d160c164496d3201 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 5 Jun 2026 22:44:59 +0200 Subject: [PATCH 30/52] Move QxSqlApi from qf::gui::framework::Application to SqlApi class --- libqf/libqfcore/src/sql/qxsql.cpp | 98 +++++++++++++---- libqf/libqfcore/src/sql/qxsql.h | 35 ++++-- libqf/libqfgui/src/framework/application.cpp | 104 +++++++----------- libqf/libqfgui/src/framework/application.h | 19 ++-- libqf/libqfgui/src/model/sqltablemodel.cpp | 7 +- .../CardReader/src/cardreaderplugin.cpp | 13 ++- .../CardReader/src/cardreaderwidget.cpp | 7 +- .../Competitors/src/competitordocument.cpp | 7 +- .../plugins/Event/src/eventplugin.cpp | 5 +- .../Event/src/services/qx/qxeventservice.cpp | 41 ++++--- .../Event/src/services/qx/sqlapinode.cpp | 2 +- .../plugins/Runs/src/runstablemodel.cpp | 3 +- quickevent/app/quickevent/src/main.cpp | 3 +- 13 files changed, 200 insertions(+), 144 deletions(-) diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index 844ffbf2d..11601fa32 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -106,15 +106,9 @@ QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::op } //================================================================ -// QxSql +// QxSqlApiImpl //================================================================ -QxSql::QxSql(const QSqlDatabase &db) - : m_db(db) -{ - -} - -QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); q.prepare(query); @@ -138,7 +132,7 @@ QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) return result; } -ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) +ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); q.prepare(query); @@ -153,40 +147,102 @@ ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) return result; } -qint64 QxSql::createRecord(const QString &table, const Record &record, const QString &issuer) +//================================================================ +// QxSql +//================================================================ +QxSql::QxSql(const QString &issuer, const QSqlDatabase &db, QObject *parent) + : QObject(parent) + , m_issuer(issuer) + , m_sqlApi(db) +{ +} + +QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +{ + return m_sqlApi.query(query, params); +} + +ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) { - auto id = QxSqlApi::createRecord(table, record, issuer); - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + return m_sqlApi.exec(query, params); +} + +qint64 QxSql::createRecord(const QString &table, const Record &record, QObject *source) +{ + auto id = m_sqlApi.createRecord(table, record, m_issuer); + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Insert, - .issuer = issuer}); + .issuer = m_issuer}, + source); return id; } -bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) +bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, QObject *source) { - auto ok = QxSqlApi::updateRecord(table, id, record, issuer); + auto ok = m_sqlApi.updateRecord(table, id, record, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Update, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } -bool QxSql::deleteRecord(const QString &table, qint64 id, const QString &issuer) +bool QxSql::deleteRecord(const QString &table, qint64 id, QObject *source) { - auto ok = QxSqlApi::deleteRecord(table, id, issuer); + auto ok = m_sqlApi.deleteRecord(table, id, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = {}, .op = qf::core::sql::RecOp::Delete, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } + +void QxSql::emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Insert, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Update, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecDeleted(const QString &table, qint64 id, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = {}, + .op = qf::core::sql::RecOp::Delete, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecChng(const core::sql::QxRecChng &recchng, QObject *source) +{ + emit recChng(recchng, source); +} + } diff --git a/libqf/libqfcore/src/sql/qxsql.h b/libqf/libqfcore/src/sql/qxsql.h index 51e7c6fd0..c1f6101f3 100644 --- a/libqf/libqfcore/src/sql/qxsql.h +++ b/libqf/libqfcore/src/sql/qxsql.h @@ -67,23 +67,40 @@ class QFCORE_DECL_EXPORT QxSqlApi const std::optional &limit); }; -class QFCORE_DECL_EXPORT QxSql : public QObject, public QxSqlApi +class QxSqlApiImpl : public QxSqlApi +{ +public: + QxSqlApiImpl(QSqlDatabase db) : m_db(db) {} + + QueryResult query(const QString &query, const QVariantMap ¶ms) override; + ExecResult exec(const QString &query, const QVariantMap ¶ms) override; +private: + QSqlDatabase m_db; +}; + +class QFCORE_DECL_EXPORT QxSql : public QObject { Q_OBJECT public: - QxSql(const QSqlDatabase &db = QSqlDatabase()); + QxSql(const QString &issuer, const QSqlDatabase &db = {}, QObject *parent = nullptr); ~QxSql() override = default; - Q_SIGNAL void dbRecChng(const qf::core::sql::QxRecChng &recchng); + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - QueryResult query(const QString &query, const QVariantMap ¶ms) override; - ExecResult exec(const QString &query, const QVariantMap ¶ms) override; + QueryResult query(const QString &query, const QVariantMap ¶ms); + ExecResult exec(const QString &query, const QVariantMap ¶ms); + + qint64 createRecord(const QString &table, const Record &record, QObject *source); + bool updateRecord(const QString &table, qint64 id, const Record &record, QObject *source); + bool deleteRecord(const QString &table, qint64 id, QObject *source); - qint64 createRecord(const QString &table, const Record &record, const QString &issuer) override; - bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) override; - bool deleteRecord(const QString &table, qint64 id, const QString &issuer) override; + void emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecDeleted(const QString &table, qint64 id, QObject *source); + void emitRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); private: - QSqlDatabase m_db; + QString m_issuer; + QxSqlApiImpl m_sqlApi; }; } // namespace qf::core::sql diff --git a/libqf/libqfgui/src/framework/application.cpp b/libqf/libqfgui/src/framework/application.cpp index 5f323c370..ea866c76b 100644 --- a/libqf/libqfgui/src/framework/application.cpp +++ b/libqf/libqfgui/src/framework/application.cpp @@ -19,80 +19,50 @@ Application::Application(int &argc, char **argv) : { } -qint64 Application::createDbRecord(const QString &table, const QVariantMap &record, QObject *source) +qf::core::sql::QxSql *Application::qxSql() { - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.createRecord(table, record, uuidString()); -} - -std::optional Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const -{ - using namespace qf::core::sql; - QxSql sql; - return sql.readRecord(table, id, fields); -} - -bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.updateRecord(table, id, record, uuidString()); -} - -bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.deleteRecord(table, id, uuidString()); -} + if (m_qxSql == nullptr) { + m_qxSql = new qf::core::sql::QxSql(uuidString(), {}, this); + } + return m_qxSql; +} + +// qint64 Application::createDbRecord(const QString &table, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.createRecord(table, record, uuidString()); +// } + +// std::optional Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const +// { +// using namespace qf::core::sql; +// QxSql sql; +// return sql.readRecord(table, id, fields); +// } + +// bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.updateRecord(table, id, record, uuidString()); +// } + +// bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.deleteRecord(table, id, uuidString()); +// } QString Application::versionString() const { return QCoreApplication::applicationVersion(); } -void Application::emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Insert, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Update, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecDeleted(const QString &table, qint64 id, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = {}, - .op = qf::core::sql::RecOp::Delete, - .issuer = uuidString() - }, source); -} - -void Application::emitQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) -{ - emit qxRecChng(recchng, source); -} - Application *Application::instance(bool must_exist) { auto *ret = qobject_cast(Super::instance()); diff --git a/libqf/libqfgui/src/framework/application.h b/libqf/libqfgui/src/framework/application.h index 6b3893a8c..513007e7d 100644 --- a/libqf/libqfgui/src/framework/application.h +++ b/libqf/libqfgui/src/framework/application.h @@ -12,6 +12,8 @@ class QQmlEngine; +namespace qf::core::sql { class QxSql; } + namespace qf { namespace gui { namespace framework { @@ -28,15 +30,13 @@ class QFGUI_DECL_EXPORT Application : public QApplication explicit Application(int & argc, char ** argv); ~Application() override = default; - Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); - std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; - bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - bool deleteDbRecord(const QString &table, qint64 id, QObject *source); - void emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecDeleted(const QString &table, qint64 id, QObject *source); - void emitQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + qf::core::sql::QxSql *qxSql(); + + // Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + // qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); + // std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; + // bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + // bool deleteDbRecord(const QString &table, qint64 id, QObject *source); Q_INVOKABLE QString versionString() const; public: @@ -53,6 +53,7 @@ class QFGUI_DECL_EXPORT Application : public QApplication static QString uuidString(); protected: MainWindow* m_frameWork = nullptr; + qf::core::sql::QxSql *m_qxSql = nullptr; }; }}} diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index 91ff50340..e61a1e0d9 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -234,7 +235,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) row_ref.setValue(serial_ix, v); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecInserted(table_id, v.value(), record_to_map(rec), this); + app->qxSql()->emitRecInserted(table_id, v.value(), record_to_map(rec), this); } } else { @@ -338,7 +339,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); + app->qxSql()->emitRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); } } } @@ -447,7 +448,7 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecDeleted(table_id, where_rec.value(0).value(), this); + app->qxSql()->emitRecDeleted(table_id, where_rec.value(0).value(), this); } } } else { diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp index 418ecf9dd..9ebf1820b 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -280,7 +281,7 @@ int CardReaderPlugin::saveCardToSql(const quickevent::core::si::ReadCard &read_c {"runIdAssignError", read_card.runIdAssignError()}, {"data", qf::core::Utils::qvariantToJson(read_card.data())}, }; - auto id = app->createDbRecord("cards", rec, this); + auto id = app->qxSql()->createRecord("cards", rec, this); return id; } catch (const std::exception &e) { @@ -333,7 +334,7 @@ int CardReaderPlugin::savePunchRecordToSql(const quickevent::core::si::PunchReco {"timeMs", punch.timems()}, {"runTimeMs", punch.runtimems_isset()? punch.runtimems(): QVariant()}, }; - auto id = app->createDbRecord("punches", rec, this); + auto id = app->qxSql()->createRecord("punches", rec, this); return id; } catch (const std::exception &e) { @@ -406,7 +407,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: {"notStart", false}, {"penaltyTimeMs", {}}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } if (auto missing_codes = checked_card.missingCodes(); !missing_codes.isEmpty()) { QStringList missing_str; @@ -417,7 +418,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: QVariantMap rec { {"runIdAssignError", missing_str.join(',')}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); } } catch (const std::exception &e) { @@ -463,7 +464,7 @@ bool CardReaderPlugin::saveCardAssignedRunnerIdSql(int card_id, int run_id) {"runId", run_id}, {"runIdAssignTS", QDateTime::currentDateTime()}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); return true; } catch (const std::exception &e) { @@ -540,7 +541,7 @@ void CardReaderPlugin::setStartTime(int relay_id, int leg, int start_time) { {"startTimeMs", start_time}, }; auto run_id = q.value(0).toInt(); - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } catch (const std::exception &e) { qfError() << "setStartTime(): Update runs error, query:" << e.what(); diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 9701c0e10..2c3db126d 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -262,7 +263,7 @@ CardReaderWidget::CardReaderWidget(QWidget *parent) } }, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); } CardReaderWidget::~CardReaderWidget() @@ -818,7 +819,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"isRunning", true}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); // QString qs = "UPDATE runs SET isRunning=true WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); // q.execThrow(qs); } @@ -835,7 +836,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"siId", si_id}, }; auto run_id = q.value(0).toInt(); if (n++ == 0 || use_si_in_next_stages) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } } // QString qs = "UPDATE runs SET siId=" QF_IARG(si_id) " WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp index 3c678da43..98ed30b64 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -92,7 +93,7 @@ bool CompetitorDocument::saveData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, rec}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecInserted( "runs", run_id, rec, this); + app->qxSql()->emitRecInserted( "runs", run_id, rec, this); } } } @@ -103,7 +104,7 @@ bool CompetitorDocument::saveData() auto *app = qf::gui::framework::Application::instance(); QVariantMap rec { {"siId", siid()}, }; for (const auto &[run_id, _] : old_records.asKeyValueRange()) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } // int competitor_id = dataId().toInt(); // qf::core::sql::Query q(sqlModel()->connectionName()); @@ -171,7 +172,7 @@ bool CompetitorDocument::dropData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, {}}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecDeleted("runs", run_id, this); + app->qxSql()->emitRecDeleted("runs", run_id, this); } } } diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index 5eeb13cd8..1a124970e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -194,7 +195,7 @@ EventPlugin::EventPlugin(QObject *parent) setEventOpen(!event_name.isEmpty()); }); connect(this, &Event::EventPlugin::dbEventNotify, this, &Event::EventPlugin::onDbEventNotify, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &EventPlugin::onRecChng); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &EventPlugin::onRecChng); } void EventPlugin::initEventConfig() @@ -669,7 +670,7 @@ void EventPlugin::onDbEvent(const QString &name, QSqlDriver::NotificationSource qfWarning() << "RecChng loopback detected, issuer:" << recchng.issuer; return; } - qf::gui::framework::Application::instance()->emitQxRecChng(recchng, this); + qf::gui::framework::Application::instance()->qxSql()->emitRecChng(recchng, this); return; } qfMessage() << "emitting domain:" << domain << "data:" << data; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index be06570b9..6d43c3b4c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -580,18 +580,23 @@ int QxEventService::currentConnectionId() void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { + auto *event_plugin = getPlugin(); + auto current_stage = event_plugin->currentStageId(); + auto stage_data = event_plugin->stageData(current_stage); + auto api_token = stage_data.qxApiToken().toStdString(); auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) - ->setShvPath(".broker/currentClient") - ->setMethod("info"); + ->setShvPath("test/qx/qxeventd/eventctl") + ->setMethod("openEventApiKey") + ->setParams(RpcValue(api_token)); connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { if (error.isValid()) { setStatus(Status::Stopped); setStatusMessage(tr("Client info discovery error: %1").arg(error.toString())); } else { - const auto &info = result.asMap(); - m_eventMountPoint = info.value("mountPoint").to(); - m_eventId = m_eventMountPoint.section('/', -1, -1).toInt(); + const auto &info = result.asList(); + m_eventId = info.value(0).toInt(); + m_eventMountPoint = info.value(1).to(); setStatus(Status::Running); setStatusMessage(tr("Event ID: %1").arg(m_eventId)); subscribeChanges(); @@ -647,19 +652,19 @@ void QxEventService::sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg) void QxEventService::subscribeChanges() { - Q_ASSERT(m_rpcConnection); - QString shv_path = "test"; - QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; - auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); - connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { - if(error.isValid()) { - qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); - } - else { - qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); - } - }); - rpc_call->start(); + // Q_ASSERT(m_rpcConnection); + // QString shv_path = "test"; + // QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; + // auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); + // connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + // if(error.isValid()) { + // qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); + // } + // else { + // qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); + // } + // }); + // rpc_call->start(); } // void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index e18c89d97..9e4b5845f 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -63,7 +63,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->query(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 52b93fad2..ab322c97c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -51,7 +52,7 @@ RunsTableModel::RunsTableModel(QObject *parent) setColumn(col_competitors_note, ColumnDefinition("competitors.note", tr("Note"))); connect(this, &RunsTableModel::dataChanged, this, &RunsTableModel::onDataChanged, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); } Qt::ItemFlags RunsTableModel::flags(const QModelIndex &index) const diff --git a/quickevent/app/quickevent/src/main.cpp b/quickevent/app/quickevent/src/main.cpp index 3e6036e3d..efb9704a4 100644 --- a/quickevent/app/quickevent/src/main.cpp +++ b/quickevent/app/quickevent/src/main.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -189,7 +190,7 @@ int main(int argc, char *argv[]) main_window.show(); emit main_window.applicationLaunched(); - QObject::connect(&app, &Application::qxRecChng, &app, [](const qf::core::sql::QxRecChng &recchng) { + QObject::connect(app.qxSql(), &qf::core::sql::QxSql::recChng, &app, [](const qf::core::sql::QxRecChng &recchng) { auto dump_map = [](const QVariantMap &m) { QStringList rows; for (const auto &[k, v] : m.asKeyValueRange()) { From 1dac94131922177ef3652062fd97cacc2cebefc5 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 8 Jun 2026 21:02:07 +0200 Subject: [PATCH 31/52] Change qxchanges table structure, QE 3.6.0 --- libqf/libqfcore/src/sql/qxsql.cpp | 77 +++- libqf/libqfcore/src/sql/qxsql.h | 31 +- quickevent/app/quickevent/CMakeLists.txt | 2 +- .../quickevent/plugins/Event/qml/DbSchema.qml | 14 +- .../plugins/Event/sql/create_db_psql.sql | 10 +- .../plugins/Event/sql/create_db_sqlite.sql | 10 +- ...ionswidget.cpp => qxlateentrieswidget.cpp} | 50 +- ...trationswidget.h => qxlateentrieswidget.h} | 10 +- ...ationswidget.ui => qxlateentrieswidget.ui} | 4 +- .../plugins/Event/src/services/qx/sqlapi.cpp | 428 +++++------------- .../plugins/Event/src/services/qx/sqlapi.h | 51 +-- .../Event/src/services/qx/sqlapinode.cpp | 8 +- .../plugins/Runs/src/runsplugin.cpp | 12 +- quickevent/app/quickevent/src/appversion.h | 2 +- 14 files changed, 289 insertions(+), 420 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.cpp => qxlateentrieswidget.cpp} (84%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.h => qxlateentrieswidget.h} (75%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.ui => qxlateentrieswidget.ui} (96%) diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index 11601fa32..dac711b02 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -1,6 +1,8 @@ #include "qxsql.h" #include +#include +#include #include #include @@ -11,6 +13,49 @@ namespace qf::core::sql { //================================================================ // QxSqlApi //================================================================ +namespace { + +class Transaction +{ +public: + Transaction(QSqlDatabase db) : m_db(db) { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + } + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void commit() { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } +private: + QSqlDatabase m_db; + bool m_inTransaction = true; +}; +} +void QxSqlApi::transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db) +{ + Transaction transaction(db); + qf::core::sql::Query q(db); + q.prepare(query, qf::core::Exception::Throw); + for (const auto ¶m : params) { + auto m = param.toMap(); + for (const auto &[k, v] : m.asKeyValueRange()) { + q.bindValue(':' + k, v); + } + q.exec(qf::core::Exception::Throw); + } + transaction.commit(); +} + qint64 QxSqlApi::createRecord(const QString &table, const Record &record, const QString &issuer) { Q_UNUSED(issuer) @@ -63,7 +108,7 @@ bool QxSqlApi::updateRecord(const QString &table, qint64 id, const Record &recor .arg(table, keyVals.join(", "), QString::number(id)); ExecResult res = exec(qs, record); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issuer) @@ -72,7 +117,7 @@ bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issu QString qs = QString("DELETE FROM %1 WHERE id = %2").arg(table, QString::number(id)); ExecResult res = exec(qs, {}); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::optional &fields, const std::optional &id, const std::optional &limit) @@ -110,6 +155,7 @@ QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::op //================================================================ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { + // qDebug() << query << params; QSqlQuery q(m_db); q.prepare(query); for (const auto &[key, val] : params.asKeyValueRange()) { @@ -120,7 +166,7 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) } QueryResult result; for (int i = 0; i < q.record().count(); ++i) { - result.columns.append(q.record().fieldName(i).toLower()); + result.fields.append(DbField{.name = q.record().fieldName(i).toLower()}); } while (q.next()) { QList row; @@ -143,10 +189,18 @@ ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) throw qf::core::Exception(q.lastError().text()); } ExecResult result; - result.rowsAffected = q.numRowsAffected(); + result.numRowsAffected = q.numRowsAffected(); + if (auto id = q.lastInsertId().toLongLong(); id > 0) { + result.lastInsertId = id; + } return result; } +void QxSqlApiImpl::transaction(const QString &query, const QVariantList ¶ms) +{ + QxSqlApi::transaction(query, params, m_db); +} + //================================================================ // QxSql //================================================================ @@ -167,6 +221,16 @@ ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) return m_sqlApi.exec(query, params); } +QList QxSql::listRecords(const QString &table, const std::optional &fields, const std::optional &fromId, const std::optional &limit) +{ + return m_sqlApi.listRecords(table, fields, fromId, limit); +} + +void QxSql::transaction(const QString &query, const QVariantList ¶ms) +{ + m_sqlApi.transaction(query, params); +} + qint64 QxSql::createRecord(const QString &table, const Record &record, QObject *source) { auto id = m_sqlApi.createRecord(table, record, m_issuer); @@ -179,6 +243,11 @@ qint64 QxSql::createRecord(const QString &table, const Record &record, QObject * return id; } +std::optional QxSql::readRecord(const QString &table, qint64 id, const std::optional &fields) +{ + return m_sqlApi.readRecord(table, id, fields); +} + bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, QObject *source) { auto ok = m_sqlApi.updateRecord(table, id, record, m_issuer); diff --git a/libqf/libqfcore/src/sql/qxsql.h b/libqf/libqfcore/src/sql/qxsql.h index c1f6101f3..04d493eeb 100644 --- a/libqf/libqfcore/src/sql/qxsql.h +++ b/libqf/libqfcore/src/sql/qxsql.h @@ -15,9 +15,14 @@ namespace qf::core::sql { using Record = QVariantMap; +struct DbField +{ + QString name; +}; + struct QFCORE_DECL_EXPORT QueryResult { - QStringList columns; + QList fields; QList> rows; std::optional record(int i) const @@ -27,8 +32,8 @@ struct QFCORE_DECL_EXPORT QueryResult } Record r; const auto &row = rows[i]; - for (int j = 0; j < columns.size(); ++j) { - r[columns[j]] = row.value(j); + for (int j = 0; j < fields.size(); ++j) { + r[fields[j].name] = row.value(j); } return r; } @@ -36,7 +41,8 @@ struct QFCORE_DECL_EXPORT QueryResult struct QFCORE_DECL_EXPORT ExecResult { - qint64 rowsAffected = 0; + qint64 numRowsAffected = 0; + std::optional lastInsertId = 0; }; class QFCORE_DECL_EXPORT QxSqlApi @@ -54,11 +60,12 @@ class QFCORE_DECL_EXPORT QxSqlApi { return listOneOrMoreRecords(table, fields, fromId, limit); } + void transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db); - virtual qint64 createRecord(const QString &table, const Record &record, const QString &issuer); - virtual std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); - virtual bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); - virtual bool deleteRecord(const QString &table, qint64 id, const QString &issuer); + qint64 createRecord(const QString &table, const Record &record, const QString &issuer); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); + bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); + bool deleteRecord(const QString &table, qint64 id, const QString &issuer); protected: QList listOneOrMoreRecords( const QString &table, @@ -74,6 +81,7 @@ class QxSqlApiImpl : public QxSqlApi QueryResult query(const QString &query, const QVariantMap ¶ms) override; ExecResult exec(const QString &query, const QVariantMap ¶ms) override; + void transaction(const QString &query, const QVariantList ¶ms); private: QSqlDatabase m_db; }; @@ -89,8 +97,15 @@ class QFCORE_DECL_EXPORT QxSql : public QObject QueryResult query(const QString &query, const QVariantMap ¶ms); ExecResult exec(const QString &query, const QVariantMap ¶ms); + QList listRecords( + const QString &table, + const std::optional &fields = std::nullopt, + const std::optional &fromId = std::nullopt, + const std::optional &limit = std::nullopt); + void transaction(const QString &query, const QVariantList ¶ms); qint64 createRecord(const QString &table, const Record &record, QObject *source); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); bool updateRecord(const QString &table, qint64 id, const Record &record, QObject *source); bool deleteRecord(const QString &table, qint64 id, QObject *source); diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 94fbaec8c..bd86ad635 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -99,7 +99,7 @@ add_executable(quickevent plugins/Event/src/services/qx/nodes.cpp plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui - plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui + plugins/Event/src/services/qx/qxlateentrieswidget.h plugins/Event/src/services/qx/qxlateentrieswidget.cpp plugins/Event/src/services/qx/qxlateentrieswidget.ui plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index c06846d00..55222da4a 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -494,22 +494,26 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - Field { name: 'change_id'; type: Int { } }, - Field { name: 'data_id'; type: Int { } }, + // Field { name: 'change_id'; type: Int { } }, // not used + Field { name: 'data_id'; type: Int { } }, // not used Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, Field { name: 'orig_data'; type: String { } comment: 'Store data overriden by change here to enable change rollback.' }, - Field { name: 'source'; type: String { } }, + Field { name: 'source'; type: String { } }, // issuer Field { name: 'user_id'; type: String { } }, Field { name: 'status'; type: String { } }, Field { name: 'status_message'; type: String { } }, - Field { name: 'created'; type: DateTime { } }, + Field { name: 'created'; type: DateTime { } + notNull: true + defaultValue: 'CURRENT_TIMESTAMP' + }, Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['stage_id', 'change_id']; unique: true } + Index {fields: ['data_type', 'data_id']; unique: false }, + Index {fields: ['status']; unique: false } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index 93a1837cb..f294be580 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -290,7 +290,6 @@ CREATE TABLE {{eventId}}.lentcards ( CREATE TABLE {{eventId}}.qxchanges ( id serial PRIMARY KEY, stage_id integer, - change_id integer, data_id integer, data_type character varying, data character varying, @@ -299,10 +298,11 @@ CREATE TABLE {{eventId}}.qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp with time zone, - lock_number integer, - CONSTRAINT qxchanges_unique0 UNIQUE (stage_id, change_id) + created timestamp with time zone NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + lock_number integer ); +CREATE INDEX qxchanges_ix0 ON {{eventId}}.qxchanges (data_type, data_id); +CREATE INDEX qxchanges_ix1 ON {{eventId}}.qxchanges (status); COMMENT ON COLUMN {{eventId}}.qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; ------------------------------------; @@ -311,4 +311,4 @@ COMMENT ON COLUMN {{eventId}}.qxchanges.orig_data IS 'Store data overriden by ch ; -- insert into table: {{eventId}}.config; INSERT INTO {{eventId}}.config (ckey, cname, cvalue, ctype) VALUES -('db.version', 'Data version', '30500', 'int'); +('db.version', 'Data version', '30600', 'int'); diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index f358a6663..f4f050f6d 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -317,7 +317,6 @@ CREATE TABLE lentcards ( CREATE TABLE qxchanges ( id integer PRIMARY KEY, stage_id integer, - change_id integer, data_id integer, data_type character varying, data character varying, @@ -326,10 +325,11 @@ CREATE TABLE qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp, - lock_number integer, - CONSTRAINT qxchanges_unique0 UNIQUE (stage_id, change_id) + created timestamp NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + lock_number integer ); +CREATE INDEX qxchanges_ix0 ON qxchanges (data_type, data_id); +CREATE INDEX qxchanges_ix1 ON qxchanges (status); -- comments not suported for driver: SQLITE -- COMMENT ON COLUMN qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; @@ -339,4 +339,4 @@ CREATE TABLE qxchanges ( ; -- insert into table: config; INSERT INTO config (ckey, cname, cvalue, ctype) VALUES -('db.version', 'Data version', '30500', 'int'); +('db.version', 'Data version', '30600', 'int'); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp similarity index 84% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 083af5d69..b596faa15 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -1,5 +1,5 @@ -#include "qxlateregistrationswidget.h" -#include "ui_qxlateregistrationswidget.h" +#include "qxlateentrieswidget.h" +#include "ui_qxlateentrieswidget.h" #include "qxeventservice.h" #include "runchangedialog.h" @@ -42,18 +42,18 @@ constexpr auto DATA_TYPE_RUN_UPDATE_REQUEST = "RunUpdateRequest"; constexpr auto SOURCE_WWW = "www"; -QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : +QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), - ui(new Ui::QxLateRegistrationsWidget) + ui(new Ui::QxLateEntriesWidget) { ui->setupUi(this); ui->tableView->setReadOnly(true); ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateRegistrationsWidget::onTableCustomContextMenuRequest); - connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateRegistrationsWidget::onTableDoubleClicked); + connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateEntriesWidget::onTableCustomContextMenuRequest); + connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateEntriesWidget::onTableDoubleClicked); - ui->tableView->setPersistentSettingsId("tblQxLateRegistrations"); + ui->tableView->setPersistentSettingsId("tblQxLateEntries"); ui->tableView->setInsertRowEnabled(false); ui->tableView->setCloneRowEnabled(false); ui->tableView->setRemoveRowEnabled(false); @@ -93,7 +93,7 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : } }); - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateRegistrationsWidget::onDbEventNotify, Qt::QueuedConnection); + connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateEntriesWidget::onDbEventNotify, Qt::QueuedConnection); { auto *lst = ui->lstType; @@ -104,13 +104,13 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : lst->addItem("RadioPunch"); lst->addItem("CardReadout"); lst->setCurrentIndex(0); - connect(lst, &QComboBox::currentIndexChanged, this, &QxLateRegistrationsWidget::reload); + connect(lst, &QComboBox::currentIndexChanged, this, &QxLateEntriesWidget::reload); } - connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); + connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); connect(ui->btAll, &QPushButton::clicked, this, [this]() { QSignalBlocker sb1(ui->lstType); @@ -131,12 +131,12 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : }); } -QxLateRegistrationsWidget::~QxLateRegistrationsWidget() +QxLateEntriesWidget::~QxLateEntriesWidget() { delete ui; } -void QxLateRegistrationsWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) +void QxLateEntriesWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) { Q_UNUSED(connection_id) if(domain == QLatin1String(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED)) { @@ -145,27 +145,27 @@ void QxLateRegistrationsWidget::onDbEventNotify(const QString &domain, int conne } } -void QxLateRegistrationsWidget::onVisibleChanged(bool is_visible) +void QxLateEntriesWidget::onVisibleChanged(bool is_visible) { if (is_visible && isEnabled()) { reload(); } } -QxEventService *QxLateRegistrationsWidget::service() +QxEventService *QxLateEntriesWidget::service() { auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } -void QxLateRegistrationsWidget::resizeColumns() +void QxLateEntriesWidget::resizeColumns() { auto *tv = ui->tableView; tv->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); } -void QxLateRegistrationsWidget::showMessage(const QString &msg, bool is_error) +void QxLateEntriesWidget::showMessage(const QString &msg, bool is_error) { if (msg.isEmpty()) { ui->lblErrorMsg->hide(); @@ -182,7 +182,7 @@ void QxLateRegistrationsWidget::showMessage(const QString &msg, bool is_error) ui->lblErrorMsg->setText(msg); } -void QxLateRegistrationsWidget::reload() +void QxLateEntriesWidget::reload() { if(!isEnabled()) { return; @@ -225,7 +225,7 @@ void QxLateRegistrationsWidget::reload() m_model->reload(); } -void QxLateRegistrationsWidget::addQxChangeRow(int sql_id) +void QxLateEntriesWidget::addQxChangeRow(int sql_id) { qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; if(sql_id <= 0) { @@ -251,7 +251,7 @@ void QxLateRegistrationsWidget::addQxChangeRow(int sql_id) } -void QxLateRegistrationsWidget::applyCurrentChange() +void QxLateEntriesWidget::applyCurrentChange() { auto row = ui->tableView->currentIndex().row(); if (row < 0) { @@ -267,7 +267,7 @@ void QxLateRegistrationsWidget::applyCurrentChange() } -void QxLateRegistrationsWidget::onTableCustomContextMenuRequest(const QPoint &pos) +void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) { QAction a_neco(tr("Neco"), nullptr); QList lst; @@ -278,7 +278,7 @@ void QxLateRegistrationsWidget::onTableCustomContextMenuRequest(const QPoint &po } } -void QxLateRegistrationsWidget::onTableDoubleClicked(const QModelIndex &ix) +void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); auto data_type = m_model->value(row, COL_DATA_TYPE).toString(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h similarity index 75% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index ea906b649..2caed12b8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -7,18 +7,18 @@ namespace qf::gui::model { class SqlTableModel; } namespace Event::services::qx { namespace Ui { -class QxLateRegistrationsWidget; +class QxLateEntriesWidget; } class QxEventService; -class QxLateRegistrationsWidget : public QWidget +class QxLateEntriesWidget : public QWidget { Q_OBJECT public: - explicit QxLateRegistrationsWidget(QWidget *parent = nullptr); - ~QxLateRegistrationsWidget() override; + explicit QxLateEntriesWidget(QWidget *parent = nullptr); + ~QxLateEntriesWidget() override; void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); @@ -35,7 +35,7 @@ class QxLateRegistrationsWidget : public QWidget void onTableDoubleClicked(const QModelIndex &ix); private: - Ui::QxLateRegistrationsWidget *ui; + Ui::QxLateEntriesWidget *ui; qf::gui::model::SqlTableModel *m_model; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui similarity index 96% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 07bf69ed4..70e933293 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxLateRegistrationsWidget - + Event::services::qx::QxLateEntriesWidget + 0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index 859e29c82..3e99c1e50 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -1,6 +1,7 @@ #include "sqlapi.h" #include +#include #include #include #include @@ -21,116 +22,30 @@ using namespace shv::chainpack; namespace Event::services::qx { -//============================================== -// RpcSqlField -//============================================== -RpcValue DbField::toRpcValue() const +RpcValue dbFieldToRpcValue(const DbField &fld) { RpcValue::Map ret; - ret["name"] = name.toStdString(); + ret["name"] = fld.name.toStdString(); return RpcValue(std::move(ret)); } -DbField DbField::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - DbField ret; - const RpcValue::Map &map = rv.asMap(); - ret.name = QString::fromStdString(map.value("name").asString()); - return ret; -} - -//============================================== -// ExecResult -//============================================== -RpcValue ExecResult::toRpcValue() const +RpcValue execResultToRpcValue(const ExecResult &res) { RpcValue::Map ret; - ret["numRowsAffected"] = numRowsAffected; - ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); - return ret; -} - -ExecResult ExecResult::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - ExecResult ret; - const auto &map = rv.asMap(); - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); - return ret; -} - -//============================================== -// QueryResult -//============================================== -QVariant QueryResult::value(qsizetype row, qsizetype col) const -{ - if (row < rows.size()) { - const auto &cells = rows[row]; - if (col < cells.size()) { - return cells[col]; - } - } - return {}; -} - -QVariant QueryResult::value(qsizetype row, const std::string &name) const -{ - if (auto ix = columnIndex(name); ix.has_value()) { - return value(row, ix.value()); - } - return {}; -} - -void QueryResult::setValue(qsizetype row, qsizetype col, const QVariant &val) -{ - if (row < rows.size()) { - auto &r = rows[row]; - if (col < r.size()) { - r[col] = val; - } - } -} - -void QueryResult::setValue(qsizetype row, const std::string &name, const QVariant &val) -{ - if (auto ix = columnIndex(name); ix.has_value()) { - setValue(row, ix.value(), val); - } -} - -RpcValue::List QueryResult::toRecordList() const -{ - RpcValue::List ret; - for (const auto &row : rows) { - RpcValue::Map rec; - int n = 0; - for (const auto &field : fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(row.value(n++)); - } - ret.push_back(rec); - } + ret["numRowsAffected"] = res.numRowsAffected; + ret["lastInsertId"] = res.lastInsertId.has_value()? RpcValue(res.lastInsertId.value()): RpcValue(nullptr); return ret; } -std::optional QueryResult::columnIndex(const std::string &name) const -{ - for (size_t col = 0; col < fields.size(); ++col) { - const auto &fld = fields[col]; - if (fld.name.toStdString() == name) { - return col; - } - } - return {}; -} - -RpcValue QueryResult::toRpcValue() const +RpcValue queryResultToRpcValue(const QueryResult &res) { RpcValue::Map ret; RpcValue::List flds; - for(const auto &fld : this->fields) - flds.push_back(fld.toRpcValue()); + for(const auto &fld : res.fields) { + flds.push_back(dbFieldToRpcValue(fld)); + } RpcValue::List rpc_rows; - for (const auto &row : rows) { + for (const auto &row : res.rows) { RpcValue::List rpc_row; for (const auto &cell : row) { rpc_row.push_back(shv::coreqt::rpc::qVariantToRpcValue(cell)); @@ -142,23 +57,24 @@ RpcValue QueryResult::toRpcValue() const return ret; } -QueryResult QueryResult::fromRpcValue(const RpcValue &rv) +namespace { +RpcValue::List toShvRecordList(const QList &records) { - QueryResult ret; - const auto &map = rv.asMap(); - const auto &flds = map.valref("fields").asList(); - for(const auto &fv : flds) { - ret.fields.push_back(DbField::fromRpcValue(fv)); - } - for (const auto &rpc_row : map.value("rows").asList()) { - QueryResult::Row row; - for (const auto &cell : rpc_row.asList()) { - row.push_back(shv::coreqt::rpc::rpcValueToQVariant(cell)); - } - ret.rows.push_back(row); + RpcValue::List ret; + for (const auto &rec : records) { + ret.push_back(shv::coreqt::rpc::qVariantToRpcValue(rec)); } return ret; } +QStringList toQStringList(const std::vector &sl) +{ + QStringList qsl; + for (const auto &s : sl) { + qsl << QString::fromStdString(s); + } + return qsl; +} +} SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) { @@ -168,7 +84,6 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue return SqlQueryAndParams { .query = sql_query, .params = shv::coreqt::rpc::rpcValueToQVariant(sql_params).toMap() }; } - RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { auto m = chng.toVariantMap(); @@ -190,131 +105,110 @@ SqlApi::SqlApi(QObject *parent) // emit recchng(chng); // } -namespace { - -class Transaction -{ -public: - Transaction(QSqlDatabase db) : m_db(db) { - if (!m_db.transaction()) { - qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); - throw std::runtime_error("BEGIN transaction error"); - } - } - ~Transaction() { - if (m_inTransaction) { - m_db.rollback(); - } - } - void commit() { - if (!m_db.commit()) { - qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); - throw std::runtime_error("COMMIT transaction error"); - } - m_inTransaction = false; - } -private: - QSqlDatabase m_db; - bool m_inTransaction = true; -}; +// namespace { -void bindParams(qf::core::sql::Query &q, const Record ¶ms) -{ - for (const auto &[k, v] : params.asKeyValueRange()) { - q.bindValue(':' + k, v); - } -} +// class Transaction +// { +// public: +// Transaction(QSqlDatabase db) : m_db(db) { +// if (!m_db.transaction()) { +// qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("BEGIN transaction error"); +// } +// } +// ~Transaction() { +// if (m_inTransaction) { +// m_db.rollback(); +// } +// } +// void commit() { +// if (!m_db.commit()) { +// qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("COMMIT transaction error"); +// } +// m_inTransaction = false; +// } +// private: +// QSqlDatabase m_db; +// bool m_inTransaction = true; +// }; + +// void bindParams(qf::core::sql::Query &q, const Record ¶ms) +// { +// for (const auto &[k, v] : params.asKeyValueRange()) { +// q.bindValue(':' + k, v); +// } +// } -QueryResult sqlQuery(const SqlQueryAndParams ¶ms) -{ - qf::core::sql::Query q; - q.prepare(params.query, qf::core::Exception::Throw); - bindParams(q, params.params); - q.exec(qf::core::Exception::Throw); - - QueryResult ret; - QSqlRecord rec = q.record(); - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - DbField rfld; - rfld.name = fld.name(); - // rfld.name.replace("__", "."); - ret.fields.push_back(rfld); - } - while(q.next()) { - QueryResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - row.push_back(q.value(i)); - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.insert(ret.rows.size(), row); - } - return ret; -} +// QueryResult sqlQuery(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// QueryResult ret; +// QSqlRecord rec = q.record(); +// for (int i = 0; i < rec.count(); ++i) { +// QSqlField fld = rec.field(i); +// DbField rfld; +// rfld.name = fld.name(); +// // rfld.name.replace("__", "."); +// ret.fields.push_back(rfld); +// } +// while(q.next()) { +// QueryResult::Row row; +// for (int i = 0; i < rec.count(); ++i) { +// row.push_back(q.value(i)); +// //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); +// } +// ret.rows.insert(ret.rows.size(), row); +// } +// return ret; +// } -ExecResult sqlExec(const SqlQueryAndParams ¶ms) -{ - qf::core::sql::Query q; - q.prepare(params.query, qf::core::Exception::Throw); - bindParams(q, params.params); - q.exec(qf::core::Exception::Throw); - - ExecResult ret; - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); - return ret; -} +// ExecResult sqlExec(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// ExecResult ret; +// ret.numRowsAffected = q.numRowsAffected(); +// ret.lastInsertId = q.lastInsertId().toInt(); +// return ret; +// } -} +// } ExecResult SqlApi::exec(const SqlQueryAndParams ¶ms) { - return sqlExec(params); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->exec(params.query, params.params); } QueryResult SqlApi::query(const SqlQueryAndParams ¶ms) { - return sqlQuery(params); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->query(params.query, params.params); } void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) { - auto conn = qf::core::sql::Connection::forName(); - Transaction tranaction(conn); - qf::core::sql::Query q(conn); - q.prepare(QString::fromUtf8(query), qf::core::Exception::Throw); - for (const auto ¶m : params) { - for (const auto &[k, v] : param.asMap()) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(':' + QString::fromStdString(k), val); - } - q.exec(qf::core::Exception::Throw); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + QVariantList qparams; + for (const auto &p : params) { + qparams << shv::coreqt::rpc::rpcValueToQVariant(p); } - tranaction.commit(); + return qxsql->transaction(QString::fromStdString(query), qparams); } -QueryResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +RpcValue::List SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) { - QStringList qfields; - for (const auto &fn : fields) { - qfields << QString::fromStdString(fn); - } - if (qfields.isEmpty()) { - qfields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(qfields.join(',')).arg(QString::fromStdString(table)); - if (ids_above.has_value()) { - sql_query += " WHERE id > " + QString::number(ids_above.value()); - } - if (limit.has_value()) { - sql_query += " LIMIT " + QString::number(limit.value()); - } - auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); - return res; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + auto records = qxsql->listRecords(QString::fromStdString(table), toQStringList(fields), ids_above, limit); + return toShvRecordList(records); } namespace { std::string to_lower(const std::string &s) @@ -339,109 +233,29 @@ std::string to_lower(const std::string &s) } int64_t SqlApi::create(const std::string &table, const RpcValue::Map &record) { - QStringList fields; - QStringList placeholders; - for (const auto &[k, v] : record) { - Q_UNUSED(v) - auto qk = QString::fromStdString(k); - fields << qk; - placeholders << ':' + qk; - } - QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") - .arg(QString::fromStdString(table)) - .arg(fields.join(',')) - .arg(placeholders.join(',')); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - auto id = q.lastInsertId().toInt(); - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - // .op = qf::core::sql::RecOp::Insert, - // .issuer = {} - // }); - return id; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->createRecord(QString::fromStdString(table), shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); } -std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) { - QStringList qfields; - for (const auto &fn : fields) { - qfields << QString::fromStdString(fn); - } - if (qfields.isEmpty()) { - qfields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") - .arg(qfields.join(',')) - .arg(QString::fromStdString(table)) - .arg(id) ; - auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); - if (res.rows.empty()) { - return {}; - } - Record ret; - const auto &row = res.rows.first(); - for (qsizetype i = 0; i < std::ssize(res.fields); ++i) { - ret[res.fields[i].name] = row.value(i); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + if (auto rec = qxsql->readRecord(QString::fromStdString(table), id, toQStringList(fields)); rec.has_value()) { + return shv::coreqt::rpc::qVariantToRpcValue(rec.value()).asMap(); } - return ret; + return {}; } bool SqlApi::update(const std::string &table, int64_t id, const RpcValue::Map &record) { - QStringList fields; - for (const auto &[k, v] : record) { - Q_UNUSED(v) - auto qk = QString::fromStdString(k); - fields << qk + " = :" + qk; - } - QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(QString::fromStdString(table)) - .arg(fields.join(',')) - .arg(id); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - bool updated = q.numRowsAffected() == 1; - if (updated) { - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - // .op = qf::core::sql::RecOp::Update, - // .issuer = {} - // }); - } - return updated; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->updateRecord(QString::fromStdString(table), id, shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); } bool SqlApi::drop(const std::string &table, int64_t id) { - QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(QString::fromStdString(table)) - .arg(id); - qf::core::sql::Query q; - q.exec(sql_query, qf::core::Exception::Throw); - bool is_drop = q.numRowsAffected() == 1; - if (is_drop) { - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = {}, - // .op = qf::core::sql::RecOp::Delete, - // .issuer = {} - // }); - } - return is_drop; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->deleteRecord(QString::fromStdString(table), id, this); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index d02966a7e..c0d9334b7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -1,7 +1,7 @@ #pragma once #include -// #include +#include #include #include @@ -11,42 +11,13 @@ namespace qf::core::sql { struct QxRecChng; } namespace Event::services::qx { -struct DbField -{ - QString name; - - //explicit DbField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} - shv::chainpack::RpcValue toRpcValue() const; - // QVariant toVariant() const; - static DbField fromRpcValue(const shv::chainpack::RpcValue &rv); - // static DbField fromVariant(const QVariant &v); -}; - -struct ExecResult -{ - int numRowsAffected = 0; - std::optional lastInsertId = 0; - - shv::chainpack::RpcValue toRpcValue() const; - static ExecResult fromRpcValue(const shv::chainpack::RpcValue &rv); -}; - -struct QueryResult -{ - std::vector fields; - using Row = QVariantList; - QList rows; +using DbField = qf::core::sql::DbField; +using ExecResult = qf::core::sql::ExecResult; +using QueryResult = qf::core::sql::QueryResult; - std::optional columnIndex(const std::string &name) const; - QVariant value(qsizetype row, qsizetype col) const; - QVariant value(qsizetype row, const std::string &name) const; - void setValue(qsizetype row, qsizetype col, const QVariant &val); - void setValue(qsizetype row, const std::string &name, const QVariant &val); - - shv::chainpack::RpcValue toRpcValue() const; - static QueryResult fromRpcValue(const shv::chainpack::RpcValue &rv); - shv::chainpack::RpcValue::List toRecordList() const; -}; +shv::chainpack::RpcValue dbFieldToRpcValue(const DbField &fld); +shv::chainpack::RpcValue execResultToRpcValue(const ExecResult &res); +shv::chainpack::RpcValue queryResultToRpcValue(const QueryResult &res); using Record = QVariantMap; @@ -67,18 +38,14 @@ class SqlApi : public QObject public: explicit SqlApi(QObject *parent = nullptr); - // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); - // void emitRecChng(const qf::core::sql::QxRecChng &chng); - ExecResult exec(const SqlQueryAndParams ¶ms); QueryResult query(const SqlQueryAndParams ¶ms); void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + shv::chainpack::RpcValue::List list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); - std::optional read(const std::string &table, int64_t id, const std::vector &fields); + std::optional read(const std::string &table, int64_t id, const std::vector &fields); bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); bool drop(const std::string &table, int64_t id); -private: }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 9e4b5845f..a495e24b1 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -60,11 +60,11 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin if(shv_path.empty()) { if(method == METH_EXEC) { auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); - return res.toRpcValue(); + return execResultToRpcValue(res); } if(method == METH_QUERY) { auto res = m_sqlApi->query(SqlQueryAndParams::fromRpcValue(params)); - return res.toRpcValue(); + return queryResultToRpcValue(res); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); @@ -82,7 +82,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); auto res = m_sqlApi->list(table, fields, ids_above, limit); - return res.toRecordList(); + return res; } if(method == METH_CREATE) { const auto &map = params.asMap(); @@ -101,7 +101,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto res = m_sqlApi->read(table, id, fields); if (res.has_value()) { - return shv::coreqt::rpc::qVariantToRpcValue(res.value()); + return res.value(); } return RpcValue(nullptr); } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 3964ce8de..efa0248b1 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -11,7 +11,7 @@ // #include "../../Competitors/src/competitorwidget.h" #include "../../CardReader/src/cardreaderplugin.h" #include "../../Event/src/eventplugin.h" -#include "../../Event/src/services/qx/qxlateregistrationswidget.h" +#include "../../Event/src/services/qx/qxlateentrieswidget.h" #include #include @@ -142,15 +142,15 @@ void RunsPlugin::onInstalled() } { auto *dw = new qff::DockWidget(nullptr); - dw->setObjectName("qxLateRegistrationsDockWidget"); - dw->setPersistentSettingsId("Runs/qxLateRegistrations"); - dw->setWindowTitle(tr("Late registrations")); - auto *ew = new Event::services::qx::QxLateRegistrationsWidget(); + dw->setObjectName("qxLateEntriesDockWidget"); + dw->setPersistentSettingsId("Runs/qxLateEntries"); + dw->setWindowTitle(tr("Late entries")); + auto *ew = new Event::services::qx::QxLateEntriesWidget(); dw->setWidget(ew); fwk->addDockWidget(Qt::RightDockWidgetArea, dw); dw->hide(); - connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateRegistrationsWidget::onVisibleChanged); + connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateEntriesWidget::onVisibleChanged); auto *a = dw->toggleViewAction(); // a->setShortcut(QKeySequence("ctrl+shift+E")); diff --git a/quickevent/app/quickevent/src/appversion.h b/quickevent/app/quickevent/src/appversion.h index d6b84b113..a08b78be1 100644 --- a/quickevent/app/quickevent/src/appversion.h +++ b/quickevent/app/quickevent/src/appversion.h @@ -1,4 +1,4 @@ #pragma once -#define APP_VERSION "3.5.6" +#define APP_VERSION "3.6.0" From 30f6b3a737fff89cdd91ff3ce82ae8ede780587b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 8 Jun 2026 21:06:36 +0200 Subject: [PATCH 32/52] Fix ofeed client compiler warnings --- .../quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp | 2 +- .../plugins/Event/src/services/ofeed/ofeedclientwidget.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp index 54d699543..6153547a9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp @@ -1373,7 +1373,7 @@ void OFeedClient::markChangelogEntryAsProcessed(int protocolId) variables["processedByType"] = QString("SYSTEM"); variables["processedBySource"] = QString("IT"); - sendGraphQLRequest(mutation, variables, [this, protocolId](QJsonObject data) { + sendGraphQLRequest(mutation, variables, [protocolId](QJsonObject data) { if (data.isEmpty()) { qfWarning() << serviceName().toStdString() + " Failed to mark changelog entry " << protocolId << " as processed"; } else { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h index feef6e8d5..242409451 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h @@ -40,9 +40,9 @@ class OFeedClientWidget : public qf::gui::framework::DialogWidget QString defaultReceiptEventLink() const; OFeedClient* service(); bool saveSettings(); + bool acceptDialogDone(int result) override; private: Ui::OFeedClientWidget *ui; - bool acceptDialogDone(int result); bool m_isTestConnectionRunning = false; bool m_isImageRefreshRunning = false; QString m_lastAutoReceiptEventLink; From 03ab4d6eb84ae67cbe8430eb8fbd86ff337d0b28 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 9 Jun 2026 11:34:13 +0200 Subject: [PATCH 33/52] Fix create SQL scripts --- libqf/libqfgui/src/framework/dockwidget.h | 4 ++-- quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml | 4 ++-- .../app/quickevent/plugins/Event/qml/sql/def/Field.qml | 2 ++ .../app/quickevent/plugins/Event/sql/create_db_psql.sql | 2 +- .../app/quickevent/plugins/Event/sql/create_db_sqlite.sql | 2 +- .../plugins/Event/src/services/qx/qxlateentrieswidget.cpp | 8 +++++--- .../plugins/Event/src/services/qx/qxlateentrieswidget.h | 2 +- .../plugins/Event/src/services/qx/qxlateentrieswidget.ui | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/libqf/libqfgui/src/framework/dockwidget.h b/libqf/libqfgui/src/framework/dockwidget.h index 1364ec5ee..bc4067236 100644 --- a/libqf/libqfgui/src/framework/dockwidget.h +++ b/libqf/libqfgui/src/framework/dockwidget.h @@ -22,12 +22,12 @@ class QFGUI_DECL_EXPORT DockWidget : public QDockWidget, public framework::IPers public: explicit DockWidget(const QString &window_title, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); explicit DockWidget(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : DockWidget(QString(), parent, flags) {} - ~DockWidget() Q_DECL_OVERRIDE; + ~DockWidget() override; // visibilityChanged() exists already in QDockWidget //Q_SIGNAL void visibleChanged(bool visible); protected: - bool event(QEvent *ev) Q_DECL_OVERRIDE; + bool event(QEvent *ev) override; //void showEvent(QShowEvent *ev) Q_DECL_OVERRIDE; private: void setQmlWidget(QWidget *w); diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index 55222da4a..00bcbb863 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -512,8 +512,8 @@ Schema { Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['data_type', 'data_id']; unique: false }, - Index {fields: ['status']; unique: false } + Index {fields: ['stage_id', 'data_type', 'data_id']; unique: false }, + Index {fields: ['stage_id', 'status']; unique: false } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml index 6d43184fe..c3fae0e09 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml @@ -21,6 +21,8 @@ QtObject { def += ' DEFAULT '; if(typeof defaultValue === 'boolean' && driver_name.endsWith("SQLITE")) def += (defaultValue)? 1: 0; + else if(defaultValue === 'CURRENT_TIMESTAMP') + def += defaultValue; else if(typeof defaultValue === 'string') def += "'" + defaultValue + "'"; else diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index f294be580..6937756b4 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -298,7 +298,7 @@ CREATE TABLE {{eventId}}.qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp with time zone NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + created timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, lock_number integer ); CREATE INDEX qxchanges_ix0 ON {{eventId}}.qxchanges (data_type, data_id); diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index f4f050f6d..cbc337538 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -325,7 +325,7 @@ CREATE TABLE qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, lock_number integer ); CREATE INDEX qxchanges_ix0 ON qxchanges (data_type, data_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index b596faa15..1a25ce21b 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -76,17 +76,19 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); ui->tableView->setTableModel(m_model); - showMessage({}); + showMessage(tr("QxEvent service is disabled"), true); setEnabled(false); - auto *svc = service(); + auto *svc = qxEventService(); connect(svc, &Service::statusChanged, this, [this](Service::Status new_status){ switch (new_status) { case Service::Status::Unknown: case Service::Status::Stopped: + showMessage(tr("QxEvent service is disabled"), true); setEnabled(false); break; case Service::Status::Running: + showMessage({}); setEnabled(true); reload(); break; @@ -152,7 +154,7 @@ void QxLateEntriesWidget::onVisibleChanged(bool is_visible) } } -QxEventService *QxLateEntriesWidget::service() +QxEventService *QxLateEntriesWidget::qxEventService() { auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index 2caed12b8..5ae69ac66 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -23,7 +23,7 @@ class QxLateEntriesWidget : public QWidget void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: - QxEventService* service(); + QxEventService* qxEventService(); void reload(); void addQxChangeRow(int sql_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 70e933293..30abe1ed9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -11,7 +11,7 @@ - Form + Late entries From 3fa9d282ed3725b21889311dacf5d7df3a03232a Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 17 Jun 2026 23:12:33 +0200 Subject: [PATCH 34/52] Add LateEntry support and update qxchanges schema Also add recChng signal forwarding to RPC node, check QSqlQuery::prepare and throw on failure, and rename handleQxRecChng to applyQxRecChng across models and widgets --- libqf/libqfcore/src/sql/qxrecchng.cpp | 1 + libqf/libqfcore/src/sql/qxsql.cpp | 8 +- libqf/libqfgui/src/model/tablemodel.cpp | 30 +++- libqf/libqfgui/src/model/tablemodel.h | 2 +- quickevent/app/quickevent/CMakeLists.txt | 2 +- .../CardReader/src/cardreaderwidget.cpp | 2 +- .../plugins/CardReader/src/cardreaderwidget.h | 2 +- .../quickevent/plugins/Event/qml/DbSchema.qml | 14 +- .../plugins/Event/qml/sql/def/Index.qml | 21 ++- .../plugins/Event/qml/sql/def/Table.qml | 20 +-- .../plugins/Event/sql/create_db_psql.sql | 8 +- .../plugins/Event/sql/create_db_sqlite.sql | 8 +- .../plugins/Event/src/eventplugin.cpp | 19 +-- .../plugins/Event/src/eventplugin.h | 21 ++- ...unchangedialog.cpp => lateentrydialog.cpp} | 135 +++++++----------- .../{runchangedialog.h => lateentrydialog.h} | 21 ++- ...{runchangedialog.ui => lateentrydialog.ui} | 33 ++--- .../Event/src/services/qx/qxeventservice.cpp | 109 -------------- .../Event/src/services/qx/qxeventservice.h | 6 - .../src/services/qx/qxlateentrieswidget.cpp | 79 +++------- .../src/services/qx/qxlateentrieswidget.h | 6 +- .../Event/src/services/qx/runchange.cpp | 30 +++- .../plugins/Event/src/services/qx/runchange.h | 24 +++- .../plugins/Event/src/services/qx/sqlapi.cpp | 5 +- .../plugins/Event/src/services/qx/sqlapi.h | 2 + .../Event/src/services/qx/sqlapinode.cpp | 10 ++ .../plugins/Runs/src/runstablemodel.cpp | 2 +- 27 files changed, 258 insertions(+), 362 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.cpp => lateentrydialog.cpp} (63%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.h => lateentrydialog.h} (55%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.ui => lateentrydialog.ui} (95%) diff --git a/libqf/libqfcore/src/sql/qxrecchng.cpp b/libqf/libqfcore/src/sql/qxrecchng.cpp index fc0ae36b2..b534d9ed5 100644 --- a/libqf/libqfcore/src/sql/qxrecchng.cpp +++ b/libqf/libqfcore/src/sql/qxrecchng.cpp @@ -48,6 +48,7 @@ QString QxRecChng::recopToString(RecOp op) case RecOp::Update: return "Update"; case RecOp::Delete: return "Delete"; } + return QString(); } } diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index dac711b02..45e55d111 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -157,7 +157,9 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { // qDebug() << query << params; QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } @@ -181,7 +183,9 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } diff --git a/libqf/libqfgui/src/model/tablemodel.cpp b/libqf/libqfgui/src/model/tablemodel.cpp index 2fff88940..1eebdbfca 100644 --- a/libqf/libqfgui/src/model/tablemodel.cpp +++ b/libqf/libqfgui/src/model/tablemodel.cpp @@ -672,7 +672,7 @@ QColor TableModel::contrastTextColor(const QColor &background_color) return (qGray(background_color.rgb()) < 128)? QColor(Qt::white) : QColor(Qt::black); } -void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) +void TableModel::applyQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) { // qfInfo() << __FUNCTION__ << source; if (source == this) { @@ -685,12 +685,28 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s return i; } } + qfMessage() << "cannot find table row with id:" << id; } else { - qfWarning() << "cannot find table column:" << (table_name + ".id"); + // not every change is addressing every table model + qfMessage() << metaObject()->className() << objectName() << "cannot find table column:" << (table_name + ".id"); } return -1; }; - if (recchng.op == qf::core::sql::RecOp::Update) { + if (recchng.op == qf::core::sql::RecOp::Insert) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + // row exists already + return; + } + if (auto fld_ix = table().fields().fieldIndex(recchng.table + ".id"); fld_ix >= 0) { + beginInsertRows({}, 0, 0); + auto row = insertTableRow(0); + tableRowRef(row).setValue(fld_ix, recchng.id); + endInsertRows(); + qfMessage() << "reloading inserted row:" << row << "table:" << recchng.table; + reloadRow(row); + } + } + else if (recchng.op == qf::core::sql::RecOp::Update) { if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { for (const auto &[k, v] : recchng.record.asKeyValueRange()) { if (auto col_ix = columnIndexOfTableField(recchng.table + '.' + k); col_ix.has_value()) { @@ -707,12 +723,18 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s } } else { // calculated column - qfInfo() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; + qfMessage() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; reloadRow(row); } } } } + else if (recchng.op == qf::core::sql::RecOp::Delete) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + qfMessage() << "deleteing row:" << row << (recchng.table + ".id") << recchng.id; + removeRowNoOverload(row, !qf::core::Exception::Throw); + } + } } QVariant TableModel::rawValueToEdit(int column_index, const QVariant &val) const diff --git a/libqf/libqfgui/src/model/tablemodel.h b/libqf/libqfgui/src/model/tablemodel.h index fc2c4b805..fded27d00 100644 --- a/libqf/libqfgui/src/model/tablemodel.h +++ b/libqf/libqfgui/src/model/tablemodel.h @@ -209,7 +209,7 @@ class QFGUI_DECL_EXPORT TableModel : public QAbstractTableModel static QColor contrastTextColor(const QColor &background_color); - void handleQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + void applyQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); protected: virtual void checkColumns(); void createColumnsFromTableFields(); diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b0cf1f556..1d7ada5a4 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -107,7 +107,7 @@ add_executable(quickevent plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui plugins/Event/src/services/qx/qxlateentrieswidget.h plugins/Event/src/services/qx/qxlateentrieswidget.cpp plugins/Event/src/services/qx/qxlateentrieswidget.ui - plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui + plugins/Event/src/services/qx/lateentrydialog.h plugins/Event/src/services/qx/lateentrydialog.cpp plugins/Event/src/services/qx/lateentrydialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp plugins/Oris/src/chooseoriseventdialog.cpp diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 2c3db126d..45c6db000 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -522,7 +522,7 @@ void CardReaderWidget::reload() void CardReaderWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { if(isVisible()) { - m_cardsModel->handleQxRecChng(recchng, source); + m_cardsModel->applyQxRecChng(recchng, source); } } diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h index 03b7a6aa5..3e2f312b7 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h @@ -68,7 +68,7 @@ class CardReaderWidget : public QFrame Q_SLOT void reload(); private: - void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); void appendLog(NecroLog::Level level, const QString &msg); void processDriverInfo(NecroLog::Level level, const QString &msg); diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index 00bcbb863..df5393e31 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -494,14 +494,13 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - // Field { name: 'change_id'; type: Int { } }, // not used - Field { name: 'data_id'; type: Int { } }, // not used + // Field { name: 'foreign_table'; type: String { } }, + Field { name: 'foreign_id'; type: Int { } }, Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, Field { name: 'orig_data'; type: String { } comment: 'Store data overriden by change here to enable change rollback.' }, - Field { name: 'source'; type: String { } }, // issuer Field { name: 'user_id'; type: String { } }, Field { name: 'status'; type: String { } }, Field { name: 'status_message'; type: String { } }, @@ -512,8 +511,13 @@ Schema { Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['stage_id', 'data_type', 'data_id']; unique: false }, - Index {fields: ['stage_id', 'status']; unique: false } + Index {fields: ['stage_id', 'data_type', 'foreign_id']; unique: false }, + Index {fields: ['stage_id', 'status']; unique: false }, + Index {fields: ['stage_id', 'foreign_id'] + where: "status = 'Pending' AND data_type = 'LateEntry' AND foreign_id IS NOT NULL" + comment: 'Only one pending late entry can be present for single runs.id (foreign_id).' + unique: true + } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml index bc72921c5..12986f898 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml @@ -9,6 +9,7 @@ QtObject property bool primary: false property bool unique: false property ForeignKeyReference references: null + property string where property string comment function indexName(cnt, table_name) @@ -55,21 +56,29 @@ QtObject else if(primary) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' PRIMARY KEY (' + fields.join(', ') + ')'; } - else if(unique) { + else if(unique && !where) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' UNIQUE (' + fields.join(', ') + ')'; } } return ret; } - function createSqlIndexScript(options) + function createSqlIndexScript(index_name, table_name, options) { - var ret = ''; if(fields) { - if(!primary && !unique && !references) { - ret = '(' + fields.join(', ') + ')'; + if (where) { + return 'CREATE ' + + (unique? 'UNIQUE ': ' ') + +'INDEX ' + index_name + + ' ON ' + table_name + ' (' + fields.join(', ') + ')' + + ' WHERE ' + where; + } + else if(!primary && !unique && !references) { + return 'CREATE INDEX ' + index_name + + ' ON ' + table_name + + ' (' + fields.join(', ') + ')'; } } - return ret; + return ''; } } diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml index ab5b750ca..a50ad1766 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml @@ -20,8 +20,8 @@ QtObject { var field_defs = []; options.tableName = name; var fieldNames = [] - for(var i=0; i #include #include +#include #include @@ -40,7 +41,6 @@ #include #include #include -#include #include #include @@ -73,23 +73,6 @@ using qff::getPlugin; namespace Event { -class DbEventPayload : public QVariantMap -{ -private: - typedef QVariantMap Super; - - QF_VARIANTMAP_FIELD(QString, e, setE, ventName) - QF_VARIANTMAP_FIELD(QString, d, setD, omain) - QF_VARIANTMAP_FIELD(int, c, setc, onnectionId) - QF_VARIANTMAP_FIELD(QVariant, d, setD, ata) -public: - DbEventPayload(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} - - static DbEventPayload fromJson(const QByteArray &json); - - QByteArray toJson() const; -}; - DbEventPayload DbEventPayload::fromJson(const QByteArray &json) { QJsonParseError error; diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h index 50a8e077e..a29badbe4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h @@ -26,6 +26,23 @@ namespace Event { static constexpr auto START_LIST_IOFXML3_FILE = "startlist-iof3.xml"; static constexpr auto RESULTS_IOFXML3_FILE = "results-iof3.xml"; +class DbEventPayload : public QVariantMap +{ +private: + typedef QVariantMap Super; + + QF_VARIANTMAP_FIELD(QString, e, setE, ventName) + QF_VARIANTMAP_FIELD(QString, d, setD, omain) + QF_VARIANTMAP_FIELD(int, c, setc, onnectionId) + QF_VARIANTMAP_FIELD(QVariant, d, setD, ata) +public: + DbEventPayload(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} + + static DbEventPayload fromJson(const QByteArray &json); + + QByteArray toJson() const; +}; + class EventPlugin : public qf::gui::framework::Plugin { Q_OBJECT @@ -60,7 +77,6 @@ class EventPlugin : public qf::gui::framework::Plugin static constexpr auto DBEVENT_PUNCH_RECEIVED = "punchReceived"; static constexpr auto DBEVENT_REGISTRATIONS_IMPORTED = "registrationsImported"; static constexpr auto DBEVENT_STAGE_START_CHANGED = "stageStartChanged"; - static constexpr auto DBEVENT_QX_CHANGE_RECEIVED = "qxChangeReceived"; static constexpr auto DBEVENT_QX_RECCHNG = "qxRecChng"; Q_INVOKABLE void initEventConfig(); @@ -151,8 +167,7 @@ class EventPlugin : public qf::gui::framework::Plugin bool importEventFromFile(const QString &src_file, const QString &dest_event_name); bool convertSqlEvent(const QString &from_event, const QString &to_event); void deleteEvent(const QString &event_name); - QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, - const QString &dest_schema_name); + QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, const QString &dest_schema_name); void onServiceDockVisibleChanged(bool on = true); private: diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp similarity index 63% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index e2c50edd5..aedcf62bf 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -1,11 +1,13 @@ -#include "runchangedialog.h" -#include "ui_runchangedialog.h" +#include "lateentrydialog.h" +#include "ui_lateentrydialog.h" #include "qxeventservice.h" #include "runchange.h" #include +#include +#include #include #include @@ -13,15 +15,17 @@ #include #include #include +#include #include +#include namespace Event::services::qx { -RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent) +LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent) : QDialog(parent) - , ui(new Ui::RunChangeDialog) + , ui(new Ui::LateEntryDialog) , m_changeId(change_id) - , m_runId(run_id) + , m_runId(late_entry.run_id) { ui->setupUi(this); @@ -39,26 +43,43 @@ RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, con resolveChanges(false); }); - ui->edClassName->setText(run_change.class_name.value_or(QString())); - ui->edNote->setText(run_change.note); + ui->edClassName->setText(late_entry.record.class_name.value_or(QString())); + ui->edNote->setText(late_entry.record.note); - ui->edRunId->setValue(run_id); + ui->edRunId->setValue(m_runId); ui->edChangeId->setValue(change_id); ui->edLockNumber->setValue(lock_number); - ui->grpFirstName->setChecked(run_change.first_name.has_value()); - ui->edFirstName->setText(run_change.first_name.has_value()? run_change.first_name.value(): QString()); + auto update_background = [](QWidget *widget) { + bool is_set = false; + if (auto *le = qobject_cast(widget)) { + is_set = !le->text().isEmpty(); + } + else if (auto *spin_box = qobject_cast(widget)) { + is_set = spin_box->value() > 0; + } + if (is_set) { + widget->setStyleSheet("background: honeydew"); + } + }; - ui->grpLastName->setChecked(run_change.last_name.has_value()); - ui->edLastName->setText(run_change.last_name.has_value()? run_change.last_name.value(): QString()); + ui->grpFirstName->setChecked(late_entry.record.first_name.has_value()); + ui->edFirstName->setText(late_entry.record.first_name.has_value()? late_entry.record.first_name.value(): QString()); + update_background(ui->edFirstName); - ui->grpRegistration->setChecked(run_change.registration.has_value()); - ui->edRegistration->setText(run_change.registration.has_value()? run_change.registration.value(): QString()); + ui->grpLastName->setChecked(late_entry.record.last_name.has_value()); + ui->edLastName->setText(late_entry.record.last_name.has_value()? late_entry.record.last_name.value(): QString()); + update_background(ui->edLastName); - ui->grpSiCard->setChecked(run_change.si_id.has_value()); - ui->edSiCard->setValue(run_change.si_id.has_value()? run_change.si_id.value(): 0); + ui->grpRegistration->setChecked(late_entry.record.registration.has_value()); + ui->edRegistration->setText(late_entry.record.registration.has_value()? late_entry.record.registration.value(): QString()); + update_background(ui->edRegistration); - if (run_id > 0) { + ui->grpSiCard->setChecked(late_entry.record.si_id.has_value()); + ui->edSiCard->setValue(late_entry.record.si_id.has_value()? late_entry.record.si_id.value(): 0); + update_background(ui->edSiCard); + + if (m_runId > 0) { loadOrigValues(); } else { @@ -67,19 +88,19 @@ RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, con lockChange(); } -RunChangeDialog::~RunChangeDialog() +LateEntryDialog::~LateEntryDialog() { delete ui; } -QxEventService *RunChangeDialog::service() +QxEventService *LateEntryDialog::service() { auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } -void RunChangeDialog::setMessage(const QString &msg, bool error) +void LateEntryDialog::setMessage(const QString &msg, bool error) { if (msg.isEmpty()) { ui->lblError->setStyleSheet({}); @@ -93,7 +114,7 @@ void RunChangeDialog::setMessage(const QString &msg, bool error) ui->lblError->setText(msg); } -void RunChangeDialog::loadOrigValues() +void LateEntryDialog::loadOrigValues() { Q_ASSERT(m_runId > 0); @@ -123,7 +144,7 @@ void RunChangeDialog::loadOrigValues() ui->edSiCardOrig->setValue(m_origValues.si_id); } -void RunChangeDialog::loadClassId() +void LateEntryDialog::loadClassId() { auto class_name = ui->edClassName->text(); if (class_name.isEmpty()) { @@ -140,7 +161,7 @@ void RunChangeDialog::loadClassId() } } -void RunChangeDialog::lockChange() +void LateEntryDialog::lockChange() { // NIY // auto *svc = service(); @@ -200,42 +221,7 @@ void RunChangeDialog::lockChange() // }); } -void RunChangeDialog::resolveChanges(bool is_accepted) -{ - if (is_accepted) { - applyLocalChanges(is_accepted); - } - auto *svc = service(); - auto *nm = svc->networkManager(); - QNetworkRequest request; - auto url = svc->shvBrokerUrl(); - // qfInfo() << "url " << url.toString(); - url.setPath("/api/event/current/changes/resolve-change"); - - QUrlQuery query; - query.addQueryItem("change_id", QString::number(m_changeId)); - query.addQueryItem("lock_number", QString::number(m_lockNumber)); - query.addQueryItem("accepted", is_accepted? "true": "false"); - query.addQueryItem("status_message", ui->edStatusMessage->text()); - url.setQuery(query); - - request.setUrl(url); - request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken().toUtf8()); - auto *reply = nm->get(request); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - accept(); - } - else { - QMessageBox::warning(this, - QCoreApplication::applicationName(), - tr("Update change error: %1").arg(reply->errorString())); - } - reply->deleteLater(); - }); -} - -void RunChangeDialog::applyLocalChanges(bool is_accepted) +void LateEntryDialog::resolveChanges(bool is_accepted) { using namespace qf::gui::model; @@ -267,33 +253,12 @@ void RunChangeDialog::applyLocalChanges(bool is_accepted) } } doc.save(); - { - qf::core::sql::Query q; - q.execThrow(QStringLiteral("UPDATE qxchanges SET status='%1' WHERE id=%2") - .arg(is_accepted? "Accepted": "Rejected") - .arg(m_changeId) - ); - } - { - auto dc_str = qf::core::Utils::qvariantToJson(m_origValues.toVariantMap()); - QString qs = "UPDATE qxchanges SET orig_data=:orig_data WHERE id=:id"; - qf::core::sql::Query q; - q.prepare(qs, qf::core::Exception::Throw); - q.bindValue(":orig_data", dc_str); - q.bindValue(":id", m_changeId); - q.exec(qf::core::Exception::Throw); - } -} -bool RunChangeDialog::checkHttpError(QNetworkReply *reply) -{ - if (reply->error() != QNetworkReply::NetworkError::NoError) { - setMessage(tr("Http error: %1\n%2") - .arg(reply->request().url().toString()) - .arg(reply->errorString()), true); - return false; - } - return true; + qf::core::sql::Record rec { + {"status", is_accepted? "Accepted": "Rejected"}, + {"orig_data", qf::core::Utils::qvariantToJson(m_origValues.toVariantMap())}, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h similarity index 55% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 15a9bf147..d6be39898 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -1,5 +1,5 @@ -#ifndef RUNCHANGEDIALOG_H -#define RUNCHANGEDIALOG_H +#ifndef LATEENTRYDIALOG_H +#define LATEENTRYDIALOG_H #include "runchange.h" @@ -10,19 +10,19 @@ class QNetworkReply; namespace Event::services::qx { namespace Ui { -class RunChangeDialog; +class LateEntryDialog; } -struct RunChange; +struct LateEntryRecord; class QxEventService; -class RunChangeDialog : public QDialog +class LateEntryDialog : public QDialog { Q_OBJECT public: - explicit RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent = nullptr); - ~RunChangeDialog() override; + explicit LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent = nullptr); + ~LateEntryDialog() override; private: QxEventService* service(); @@ -34,11 +34,8 @@ class RunChangeDialog : public QDialog void lockChange(); void resolveChanges(bool is_accepted); - void applyLocalChanges(bool is_accepted); - - bool checkHttpError(QNetworkReply *reply); private: - Ui::RunChangeDialog *ui; + Ui::LateEntryDialog *ui; int m_changeId = 0; int m_runId = 0; int m_competitorId = 0; @@ -51,4 +48,4 @@ class RunChangeDialog : public QDialog } // namespace Event::services::qx -#endif // RUNCHANGEDIALOG_H +#endif // LATEENTRYDIALOG_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui similarity index 95% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index c18f157b3..b07ff471c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -1,7 +1,7 @@ - Event::services::qx::RunChangeDialog - + Event::services::qx::LateEntryDialog + 0 @@ -13,7 +13,7 @@ Dialog - + @@ -308,6 +308,16 @@ + + + + Message + + + + + + @@ -321,20 +331,6 @@ - - - - - - Message - - - - - - - - @@ -402,7 +398,6 @@ edSiCard edSiCardRent edNote - edStatusMessage chkForce btAccept btReject @@ -413,7 +408,7 @@ btCancel clicked() - Event::services::qx::RunChangeDialog + Event::services::qx::LateEntryDialog reject() diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 6d43c3b4c..999da24c4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,6 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -103,7 +102,6 @@ void QxEventService::run() { void QxEventService::stop() { m_eventId = 0; - disconnectSSE(); if (m_pollChangesTimer) { m_pollChangesTimer->stop(); } @@ -384,113 +382,6 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa } } -void QxEventService::connectToSSE(int event_id) -{ - Q_UNUSED(event_id); - // auto url = exchangeServerUrl(); - // url.setPath(QStringLiteral("/api/event/%1/run/changes/sse").arg(event_id)); - // QNetworkRequest request(url); - // request.setRawHeader(QByteArray("Accept"), QByteArray("text/event-stream")); - // request.setHeader(QNetworkRequest::UserAgentHeader, "QuickEvent"); - // request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached - - // qfInfo() << "Connecting to SSE:" << url.toString(); - // m_replySSE = networkManager()->get(request); - // qfInfo() << "Connected"; - // connect(m_replySSE, &QNetworkReply::readyRead, this, [this]() { - // auto data = m_replySSE->readAll(); - // qfInfo() << "DATA:" << data.toStdString(); - // }); - // connect(m_replySSE, &QNetworkReply::finished, this, [this]() { - // qfInfo() << "SSE finished:" << m_replySSE->errorString(); - // }); -} - -void QxEventService::disconnectSSE() -{ - if (m_replySSE) { - qfInfo() << "Disconnecting SSE:" << m_replySSE; - m_replySSE->deleteLater(); - m_replySSE = nullptr; - } -} - -void QxEventService::pollQxChanges() -{ - auto event_plugin = getPlugin(); - if(!getPlugin()->isEventOpen()) { - return; - } - int stage_id = event_plugin->currentStageId(); - try { - int max_change_id = 0; - qf::core::sql::Query q; - q.execThrow("SELECT MAX(change_id) FROM qxchanges WHERE stage_id=" + QString::number(event_plugin->currentStageId())); - if (q.next()) { - max_change_id = q.value(0).toInt(); - } - auto *reply = getQxChangesReply(max_change_id + 1); - connect(reply, &QNetworkReply::finished, this, [reply, stage_id]() { - QString err; - if(reply->error()) { - err = reply->errorString(); - qfWarning() << "Load qxchanges error:" << err; - } - else { - QJsonParseError err; - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) { - qfWarning() << "Parse qxchanges error:" << err.errorString(); - } - else { - auto records = json.array().toVariantList(); - qf::core::sql::Query q; - q.prepare("INSERT INTO qxchanges (data_type, data, data_id, source, user_id, status, status_message, stage_id, change_id, created)" - " VALUES (:data_type, :data, :data_id, :source, :user_id, :status, :status_message, :stage_id, :change_id, :created)" - " RETURNING id"); - for (const auto &v : records) { - auto rec = v.toMap(); - auto ba = QJsonDocument::fromVariant(rec.value("data")).toJson(QJsonDocument::Compact); - auto data = QString::fromUtf8(ba); - q.bindValue(":data_type", rec.value("data_type")); - q.bindValue(":data", data); - q.bindValue(":data_id", rec.value("data_id")); - q.bindValue(":source", rec.value("source")); - q.bindValue(":user_id", rec.value("user_id")); - q.bindValue(":status", rec.value("status")); - q.bindValue(":status_message", rec.value("status_message")); - q.bindValue(":stage_id", stage_id); - auto change_id = rec.value("id").toInt(); - Q_ASSERT(change_id > 0); - q.bindValue(":change_id", change_id); - auto created = QDateTime::fromString(rec.value("created").toString(), Qt::ISODate); - qfDebug() << "created:" << created.toString(Qt::ISODate); - q.bindValue(":created", created); - // may fail on prikey violation if more clients are inserting simultaneously - if (q.exec()) { - if (q.next()) { - auto id = q.value(0).toInt(); - qfDebug() << "insert ID:" << id; - getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED, id, true); - } - } - else { - qfInfo() << "sql error:" << q.lastErrorText(); - - } - } - } - } - reply->deleteLater(); - }); - } - catch (const qf::core::Exception &e) { - qfWarning() << "Load qxchanges error:" << e.message(); - } -} - EventInfo QxEventService::eventInfo() const { auto *event_plugin = getPlugin(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index fc5af4120..7c0eb71c2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -79,7 +79,6 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - // void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: @@ -92,11 +91,6 @@ class QxEventService : public Service void httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context = nullptr, const std::function &call_back = nullptr); - void connectToSSE(int event_id); - void disconnectSSE(); - - void pollQxChanges(); - EventInfo eventInfo() const; private: // shv shv::iotqt::rpc::DeviceConnection *m_rpcConnection = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 1a25ce21b..edaf69723 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -2,15 +2,18 @@ #include "ui_qxlateentrieswidget.h" #include "qxeventservice.h" -#include "runchangedialog.h" +#include "lateentrydialog.h" #include "runchange.h" #include #include +#include #include #include #include +#include +#include #include #include @@ -23,14 +26,12 @@ using qf::gui::framework::getPlugin; namespace Event::services::qx { constexpr auto COL_ID = "id"; -constexpr auto COL_CHANGE_ID = "change_id"; constexpr auto COL_DATA_TYPE = "data_type"; -constexpr auto COL_DATA_ID = "data_id"; +constexpr auto COL_FOREIGN_ID = "foreign_id"; constexpr auto COL_DATA = "data"; constexpr auto COL_ORIG_DATA = "orig_data"; constexpr auto COL_STATUS = "status"; constexpr auto COL_STATUS_MESSAGE = "status_message"; -constexpr auto COL_SOURCE = "source"; constexpr auto COL_USER_ID = "user_id"; constexpr auto COL_CREATED = "created"; constexpr auto COL_LOCK_NUMBER = "lock_number"; @@ -38,10 +39,6 @@ constexpr auto COL_LOCK_NUMBER = "lock_number"; constexpr auto STATUS_PENDING = "Pending"; constexpr auto STATUS_LOCKED = "Locked"; -constexpr auto DATA_TYPE_RUN_UPDATE_REQUEST = "RunUpdateRequest"; - -constexpr auto SOURCE_WWW = "www"; - QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), ui(new Ui::QxLateEntriesWidget) @@ -65,12 +62,10 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : m_model->addColumn(COL_ID).setReadOnly(true).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_STATUS, tr("Status")); m_model->addColumn(COL_DATA_TYPE, tr("Type")); - m_model->addColumn(COL_DATA_ID, tr("Data ID")).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_SOURCE, tr("Source")); + m_model->addColumn(COL_FOREIGN_ID, tr("Foreign ID")).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_USER_ID, tr("User")); m_model->addColumn(COL_STATUS_MESSAGE, tr("Status message")); m_model->addColumn(COL_CREATED, tr("Created")); - m_model->addColumn(COL_CHANGE_ID, tr("Change ID")); m_model->addColumn(COL_LOCK_NUMBER, tr("Lock")); m_model->addColumn(COL_DATA, tr("Data"));//.setToolTip(tr("Locked for drawing")); m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); @@ -95,8 +90,6 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : } }); - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateEntriesWidget::onDbEventNotify, Qt::QueuedConnection); - { auto *lst = ui->lstType; lst->addItem("All"); @@ -131,6 +124,7 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : reload(); }); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &QxLateEntriesWidget::onQxRecChng, Qt::QueuedConnection); } QxLateEntriesWidget::~QxLateEntriesWidget() @@ -138,15 +132,6 @@ QxLateEntriesWidget::~QxLateEntriesWidget() delete ui; } -void QxLateEntriesWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) -{ - Q_UNUSED(connection_id) - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED)) { - int sql_id = payload.toInt(); - addQxChangeRow(sql_id); - } -} - void QxLateEntriesWidget::onVisibleChanged(bool is_visible) { if (is_visible && isEnabled()) { @@ -227,7 +212,7 @@ void QxLateEntriesWidget::reload() m_model->reload(); } -void QxLateEntriesWidget::addQxChangeRow(int sql_id) +void QxLateEntriesWidget::applyQxChange(int sql_id) { qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; if(sql_id <= 0) { @@ -252,23 +237,6 @@ void QxLateEntriesWidget::addQxChangeRow(int sql_id) } } - -void QxLateEntriesWidget::applyCurrentChange() -{ - auto row = ui->tableView->currentIndex().row(); - if (row < 0) { - return; - } - auto status = m_model->value(row, COL_STATUS).toString(); - if (status != STATUS_PENDING) { - return; - } - //auto run_id = m_model->value(row, COL_RUN_ID).toInt(); - //int competitor_id = 0; - // int result = getPlugin()->editCompetitor(competitor_id, run_id == 0? Mode::Insert: Mode::Edit); - -} - void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) { QAction a_neco(tr("Neco"), nullptr); @@ -283,27 +251,24 @@ void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); - auto data_type = m_model->value(row, COL_DATA_TYPE).toString(); - if (data_type != DATA_TYPE_RUN_UPDATE_REQUEST) { - return; - } - auto source = m_model->value(row, COL_SOURCE).toString(); - if (source != SOURCE_WWW) { - return; - } - auto change_id = m_model->value(row, COL_CHANGE_ID).toInt(); auto status = m_model->value(row, COL_STATUS).toString(); - auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); - auto data_id = m_model->value(row, COL_DATA_ID).toInt(); - auto data = m_model->value(row, COL_DATA).toString().toUtf8(); - auto change_rec = QJsonDocument::fromJson(data).toVariant().toMap(); - auto run_change = RunChange::fromVariantMap(change_rec.value(DATA_TYPE_RUN_UPDATE_REQUEST).toMap()); if (status == STATUS_PENDING || status == STATUS_LOCKED) { - RunChangeDialog dlg(change_id, data_id, lock_number, run_change, this); - dlg.exec(); - ui->tableView->reloadRow(row); + auto data = m_model->value(row, COL_DATA).toString(); + auto qxdata = QxChangeData::fromJson(data); + if (qxdata.lateEntry.has_value()) { + auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); + auto change_id = m_model->value(row, COL_ID).toInt(); + LateEntryDialog dlg(change_id, lock_number, qxdata.lateEntry.value(), this); + dlg.exec(); + ui->tableView->reloadRow(row); + } } } +void QxLateEntriesWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) +{ + m_model->applyQxRecChng(recchng, source); +} + } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index 5ae69ac66..85dd3265e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -2,6 +2,7 @@ #include +namespace qf::core::sql { struct QxRecChng; } namespace qf::gui::model { class SqlTableModel; } namespace Event::services::qx { @@ -20,20 +21,19 @@ class QxLateEntriesWidget : public QWidget explicit QxLateEntriesWidget(QWidget *parent = nullptr); ~QxLateEntriesWidget() override; - void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: QxEventService* qxEventService(); void reload(); - void addQxChangeRow(int sql_id); + void applyQxChange(int sql_id); void resizeColumns(); void showMessage(const QString &msg, bool is_error = false); - void applyCurrentChange(); void onTableCustomContextMenuRequest(const QPoint &pos); void onTableDoubleClicked(const QModelIndex &ix); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); private: Ui::QxLateEntriesWidget *ui; qf::gui::model::SqlTableModel *m_model; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index c9a84e23b..3cd7c04d2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -11,21 +11,31 @@ namespace Event::services::qx { // return ret; // } -RunChange RunChange::fromVariantMap(const QVariantMap &map) +LateEntryRecord LateEntryRecord::fromVariantMap(const QVariantMap &map) { - RunChange ret; + LateEntryRecord ret; if (auto v = map.value("class_name"); v.isValid()) { ret.class_name = v.toString(); } - if (auto v = map.value("first_name"); v.isValid()) { ret.first_name = v.toString(); } - if (auto v = map.value("last_name"); v.isValid()) { ret.last_name = v.toString(); } + if (auto v = map.value("firstname"); v.isValid()) { ret.first_name = v.toString(); } + if (auto v = map.value("lastname"); v.isValid()) { ret.last_name = v.toString(); } if (auto v = map.value("registration"); v.isValid()) { ret.registration = v.toString(); } - if (auto v = map.value("si_id"); v.isValid()) { ret.si_id = v.toInt(); } + if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } ret.note = map.value("note").toString(); return ret; } +LateEntry LateEntry::fromVariantMap(const QVariantMap &map) +{ + LateEntry ret; + + ret.record = LateEntryRecord::fromVariantMap(map.value("record").toMap()); + ret.run_id = map.value("run_id").toInt(); + + return ret; +} + QVariantMap OrigRunRecord::toVariantMap() const { QVariantMap ret; @@ -36,6 +46,16 @@ QVariantMap OrigRunRecord::toVariantMap() const return ret; } +QxChangeData QxChangeData::fromJson(const QString &json) +{ + QxChangeData ret; + auto map = QJsonDocument::fromJson(json.toUtf8()).toVariant().toMap(); + if (auto v = map.value(DATA_TYPE_LATE_ENTRY); v.isValid()) { + ret.lateEntry = LateEntry::fromVariantMap(v.toMap()); + } + return ret; +} + } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 83ab57062..1577011d8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -6,7 +6,9 @@ namespace Event::services::qx { -struct RunChange +constexpr auto DATA_TYPE_LATE_ENTRY = "LateEntry"; + +struct LateEntryRecord { std::optional class_name; std::optional first_name; @@ -16,8 +18,24 @@ struct RunChange // std::optional si_id_rent; QString note; - // static RunChange fromJsonString(const QString &json); - static RunChange fromVariantMap(const QVariantMap &map); + // static LateEntryRecord fromJsonString(const QString &json); + static LateEntryRecord fromVariantMap(const QVariantMap &map); +}; + +struct LateEntry +{ + int run_id = 0; + LateEntryRecord record; + + // static LateEntryRecord fromJsonString(const QString &json); + static LateEntry fromVariantMap(const QVariantMap &map); +}; + +struct QxChangeData +{ + std::optional lateEntry; + + static QxChangeData fromJson(const QString &json); }; struct OrigRunRecord diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index 3e99c1e50..25a68b6fc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -96,7 +96,8 @@ RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) SqlApi::SqlApi(QObject *parent) : QObject{parent} { - // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + connect(qxsql, &qf::core::sql::QxSql::recChng, this, &SqlApi::recChng); } // void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) @@ -201,7 +202,7 @@ void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValu for (const auto &p : params) { qparams << shv::coreqt::rpc::rpcValueToQVariant(p); } - return qxsql->transaction(QString::fromStdString(query), qparams); + qxsql->transaction(QString::fromStdString(query), qparams); } RpcValue::List SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index c0d9334b7..4e3294683 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -38,6 +38,8 @@ class SqlApi : public QObject public: explicit SqlApi(QObject *parent = nullptr); + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + ExecResult exec(const SqlQueryAndParams ¶ms); QueryResult query(const SqlQueryAndParams ¶ms); void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index a495e24b1..063e5d516 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -23,6 +23,16 @@ SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) , m_sqlApi(new SqlApi(this)) { + connect(m_sqlApi, &SqlApi::recChng, this, [this](const qf::core::sql::QxRecChng &recchng, QObject *source) { + Q_UNUSED(source); + RpcSignal sig; + sig.setSignal("recchng"); + sig.setShvPath(shvPath().asString()); + sig.setParams(shv::coreqt::rpc::qVariantToRpcValue(recchng.toVariantMap())); + qfInfo() << "emit recchng:" << recchng.table; + emitSendRpcMessage(sig); + }); + } namespace { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 83391b3ab..bac275c69 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -363,7 +363,7 @@ void RunsTableModel::onDataChanged(const QModelIndex &top_left, const QModelInde void RunsTableModel::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { - handleQxRecChng(recchng, source); + applyQxRecChng(recchng, source); } bool RunsTableModel::postRow(int row_no, bool throw_exc) From 9569886f9cad1a7015186a2ce5f92db00d6e7315 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 25 Jun 2026 23:07:46 +0200 Subject: [PATCH 35/52] Remove Force checkbox from LateEntryDialog --- .../plugins/Event/src/services/qx/lateentrydialog.cpp | 4 ---- .../plugins/Event/src/services/qx/lateentrydialog.ui | 10 +--------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index aedcf62bf..3ed2e597a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -32,10 +32,6 @@ LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry ui->btAccept->setDisabled(true); ui->btReject->setDisabled(true); - connect(ui->chkForce, &QCheckBox::checkStateChanged, this, [this]() { - ui->btAccept->setDisabled(!ui->chkForce->isChecked()); - ui->btReject->setDisabled(!ui->chkForce->isChecked()); - }); connect(ui->btAccept, &QPushButton::clicked, this, [this]() { resolveChanges(true); }); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index b07ff471c..2f19b0246 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 569 + 576 @@ -333,13 +333,6 @@ - - - - Force - - - @@ -398,7 +391,6 @@ edSiCard edSiCardRent edNote - chkForce btAccept btReject btCancel From c368b26cfc133cc57301158e58d2ba4f3df5d2ee Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 25 Jun 2026 23:15:27 +0200 Subject: [PATCH 36/52] Use updateRecord API for siId and change highlight --- .../plugins/Event/src/services/qx/lateentrydialog.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 3ed2e597a..1fe8c879d 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -55,7 +55,7 @@ LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry is_set = spin_box->value() > 0; } if (is_set) { - widget->setStyleSheet("background: honeydew"); + widget->setStyleSheet("background: palegreen"); } }; @@ -241,11 +241,10 @@ void LateEntryDialog::resolveChanges(bool is_accepted) doc.setValue("siId", ui->edSiCard->value()); } else { - qf::core::sql::Query q; - q.execThrow(QStringLiteral("UPDATE runs SET siId=%1 WHERE id=%2") - .arg(ui->edSiCard->value()) - .arg(m_runId) - ); + qf::core::sql::Record rec { + { "siId", ui->edSiCard->value() }, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", m_runId, rec, this); } } doc.save(); From 2844ee89a61d29688a7a8c3fbcba6c2724a4ffaf Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 25 Jun 2026 23:15:47 +0200 Subject: [PATCH 37/52] Revert "Remove Force checkbox from LateEntryDialog" This reverts commit 9569886f9cad1a7015186a2ce5f92db00d6e7315. --- .../plugins/Event/src/services/qx/lateentrydialog.cpp | 4 ++++ .../plugins/Event/src/services/qx/lateentrydialog.ui | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 1fe8c879d..91ee77d58 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -32,6 +32,10 @@ LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry ui->btAccept->setDisabled(true); ui->btReject->setDisabled(true); + connect(ui->chkForce, &QCheckBox::checkStateChanged, this, [this]() { + ui->btAccept->setDisabled(!ui->chkForce->isChecked()); + ui->btReject->setDisabled(!ui->chkForce->isChecked()); + }); connect(ui->btAccept, &QPushButton::clicked, this, [this]() { resolveChanges(true); }); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index 2f19b0246..b07ff471c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 576 + 569 @@ -333,6 +333,13 @@ + + + + Force + + + @@ -391,6 +398,7 @@ edSiCard edSiCardRent edNote + chkForce btAccept btReject btCancel From b6cdb6175241d5a5062331850abb2b8c87f8e104 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 1 Jul 2026 13:51:33 +0200 Subject: [PATCH 38/52] Refactor LateEntry dialog and model Add foreign_table column to qxchanges and narrow the UNIQUE index WHERE clause to include foreign_table='runs' so pending LateEntry uniqueness applies per run. Refactor LateEntry to use a variant LateEntryId (RunId/ClassId), parse ids from variant maps, normalize keys (firstname/lastname/siid), and introduce LateEntryStatus and helper conversion. Update LateEntryDialog API and UI: accept status, extract run id from the variant id, remove lock-number UI and related "Locked"/"Null" filters, and streamline resolve/close and button enable logic. --- .../quickevent/plugins/Event/qml/DbSchema.qml | 5 +- .../plugins/Event/sql/create_db_psql.sql | 3 +- .../plugins/Event/sql/create_db_sqlite.sql | 3 +- .../Event/src/services/qx/lateentrydialog.cpp | 199 ++++++++++++------ .../Event/src/services/qx/lateentrydialog.h | 22 +- .../Event/src/services/qx/lateentrydialog.ui | 32 +-- .../src/services/qx/qxlateentrieswidget.cpp | 39 ++-- .../src/services/qx/qxlateentrieswidget.ui | 14 -- .../Event/src/services/qx/runchange.cpp | 32 ++- .../plugins/Event/src/services/qx/runchange.h | 28 ++- 10 files changed, 193 insertions(+), 184 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index df5393e31..77f00718c 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -494,7 +494,7 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - // Field { name: 'foreign_table'; type: String { } }, + Field { name: 'foreign_table'; type: String { } }, Field { name: 'foreign_id'; type: Int { } }, Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, @@ -514,7 +514,8 @@ Schema { Index {fields: ['stage_id', 'data_type', 'foreign_id']; unique: false }, Index {fields: ['stage_id', 'status']; unique: false }, Index {fields: ['stage_id', 'foreign_id'] - where: "status = 'Pending' AND data_type = 'LateEntry' AND foreign_id IS NOT NULL" + // The WHERE clause means the index only contains rows that satisfy where condition + where: "status = 'Pending' AND data_type = 'LateEntry' AND foreign_table='runs' AND foreign_id IS NOT NULL" comment: 'Only one pending late entry can be present for single runs.id (foreign_id).' unique: true } diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index 08dd346bc..2154be167 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -290,6 +290,7 @@ CREATE TABLE {{eventId}}.lentcards ( CREATE TABLE {{eventId}}.qxchanges ( id serial PRIMARY KEY, stage_id integer, + foreign_table character varying, foreign_id integer, data_type character varying, data character varying, @@ -302,7 +303,7 @@ CREATE TABLE {{eventId}}.qxchanges ( ); CREATE INDEX qxchanges_ix0 ON {{eventId}}.qxchanges (stage_id, data_type, foreign_id); CREATE INDEX qxchanges_ix1 ON {{eventId}}.qxchanges (stage_id, status); -CREATE UNIQUE INDEX qxchanges_unique2 ON {{eventId}}.qxchanges (stage_id, foreign_id) WHERE status = 'Pending' AND data_type = 'LateEntry' AND foreign_id IS NOT NULL; +CREATE UNIQUE INDEX qxchanges_unique2 ON {{eventId}}.qxchanges (stage_id, foreign_id) WHERE status = 'Pending' AND data_type = 'LateEntry' AND foreign_table='runs' AND foreign_id IS NOT NULL; COMMENT ON COLUMN {{eventId}}.qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; ------------------------------------; diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index 4611adaf7..51aec9f21 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -317,6 +317,7 @@ CREATE TABLE lentcards ( CREATE TABLE qxchanges ( id integer PRIMARY KEY, stage_id integer, + foreign_table character varying, foreign_id integer, data_type character varying, data character varying, @@ -329,7 +330,7 @@ CREATE TABLE qxchanges ( ); CREATE INDEX qxchanges_ix0 ON qxchanges (stage_id, data_type, foreign_id); CREATE INDEX qxchanges_ix1 ON qxchanges (stage_id, status); -CREATE UNIQUE INDEX qxchanges_unique2 ON qxchanges (stage_id, foreign_id) WHERE status = 'Pending' AND data_type = 'LateEntry' AND foreign_id IS NOT NULL; +CREATE UNIQUE INDEX qxchanges_unique2 ON qxchanges (stage_id, foreign_id) WHERE status = 'Pending' AND data_type = 'LateEntry' AND foreign_table='runs' AND foreign_id IS NOT NULL; -- comments not suported for driver: SQLITE -- COMMENT ON COLUMN qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 91ee77d58..39e631cfc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -18,14 +18,35 @@ #include #include #include +#include namespace Event::services::qx { -LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent) - : QDialog(parent) +namespace { + +LateEntryStatus lateEntryStatusFromString(const QString &status) +{ + if (status.compare(QStringLiteral("Pending"), Qt::CaseInsensitive) == 0) { + return LateEntryStatus::Pending; + } + if (status.compare(QStringLiteral("Accepted"), Qt::CaseInsensitive) == 0) { + return LateEntryStatus::Accepted; + } + if (status.compare(QStringLiteral("Rejected"), Qt::CaseInsensitive) == 0) { + return LateEntryStatus::Rejected; + } + qfWarning() << "Unknown late entry status:" << status; + return LateEntryStatus::Rejected; +} + +} // namespace + +LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, QWidget *parent) + : Super(parent) , ui(new Ui::LateEntryDialog) , m_changeId(change_id) - , m_runId(late_entry.run_id) + , m_lateEntryId(late_entry.id) + , m_status(lateEntryStatusFromString(status)) { ui->setupUi(this); @@ -33,22 +54,20 @@ LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry ui->btReject->setDisabled(true); connect(ui->chkForce, &QCheckBox::checkStateChanged, this, [this]() { - ui->btAccept->setDisabled(!ui->chkForce->isChecked()); - ui->btReject->setDisabled(!ui->chkForce->isChecked()); + updateButtonsEnabled(); }); connect(ui->btAccept, &QPushButton::clicked, this, [this]() { - resolveChanges(true); + resolveChangesAndClose(true); + done(QDialog::Accepted); }); connect(ui->btReject, &QPushButton::clicked, this, [this]() { - resolveChanges(false); + resolveChangesAndClose(false); + done(QDialog::Accepted); }); - ui->edClassName->setText(late_entry.record.class_name.value_or(QString())); - ui->edNote->setText(late_entry.record.note); + ui->edNote->setText(late_entry.note.value_or(QString())); - ui->edRunId->setValue(m_runId); ui->edChangeId->setValue(change_id); - ui->edLockNumber->setValue(lock_number); auto update_background = [](QWidget *widget) { bool is_set = false; @@ -63,27 +82,26 @@ LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry } }; - ui->grpFirstName->setChecked(late_entry.record.first_name.has_value()); - ui->edFirstName->setText(late_entry.record.first_name.has_value()? late_entry.record.first_name.value(): QString()); + ui->grpFirstName->setChecked(late_entry.first_name.has_value()); + ui->edFirstName->setText(late_entry.first_name.value_or(QString())); update_background(ui->edFirstName); - ui->grpLastName->setChecked(late_entry.record.last_name.has_value()); - ui->edLastName->setText(late_entry.record.last_name.has_value()? late_entry.record.last_name.value(): QString()); + ui->grpLastName->setChecked(late_entry.last_name.has_value()); + ui->edLastName->setText(late_entry.last_name.value_or(QString())); update_background(ui->edLastName); - ui->grpRegistration->setChecked(late_entry.record.registration.has_value()); - ui->edRegistration->setText(late_entry.record.registration.has_value()? late_entry.record.registration.value(): QString()); + ui->grpRegistration->setChecked(late_entry.registration.has_value()); + ui->edRegistration->setText(late_entry.registration.value_or(QString())); update_background(ui->edRegistration); - ui->grpSiCard->setChecked(late_entry.record.si_id.has_value()); - ui->edSiCard->setValue(late_entry.record.si_id.has_value()? late_entry.record.si_id.value(): 0); + ui->grpSiCard->setChecked(late_entry.si_id.has_value()); + ui->edSiCard->setValue(late_entry.si_id.value_or(0)); update_background(ui->edSiCard); - if (m_runId > 0) { - loadOrigValues(); - } - else { - loadClassId(); + if (auto *run_id = std::get_if(&late_entry.id)) { + auto id = run_id->id; + ui->edRunId->setValue(id); + loadOrigValues(id); } lockChange(); } @@ -114,16 +132,16 @@ void LateEntryDialog::setMessage(const QString &msg, bool error) ui->lblError->setText(msg); } -void LateEntryDialog::loadOrigValues() +void LateEntryDialog::loadOrigValues(int run_id) { - Q_ASSERT(m_runId > 0); + Q_ASSERT(run_id > 0); qf::core::sql::Query q; q.execThrow(QStringLiteral("SELECT classes.name AS class_name, competitors.id AS competitor_id" " FROM runs" " JOIN competitors ON competitors.id=runs.competitorId AND runs.id=%1" " LEFT JOIN classes ON competitors.classId=classes.id") - .arg(m_runId) + .arg(run_id) ); if (q.next()) { ui->edClassName->setText(q.value("class_name").toString()); @@ -144,25 +162,50 @@ void LateEntryDialog::loadOrigValues() ui->edSiCardOrig->setValue(m_origValues.si_id); } -void LateEntryDialog::loadClassId() +// void LateEntryDialog::loadClassId() +// { +// auto class_name = ui->edClassName->text(); +// if (class_name.isEmpty()) { +// return; +// } +// qf::core::sql::Query q; +// q.execThrow(QStringLiteral("SELECT id" +// " FROM classes" +// " WHERE name='%1'") +// .arg(class_name) +// ); +// if (q.next()) { +// m_classId = q.value("id").toInt(); +// } +// } + +void LateEntryDialog::unlockChange() const { - auto class_name = ui->edClassName->text(); - if (class_name.isEmpty()) { - return; - } - qf::core::sql::Query q; - q.execThrow(QStringLiteral("SELECT id" - " FROM classes" - " WHERE name='%1'") - .arg(class_name) - ); - if (q.next()) { - m_classId = q.value("id").toInt(); + if (m_lockNumber > 0 || ui->chkForce->isChecked()) { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=NULL WHERE id=%1") + .arg(m_changeId) + ); } } void LateEntryDialog::lockChange() { + auto lock_number = QxEventService::currentConnectionId(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=%1 WHERE id=%2 AND (lock_number IS NULL OR lock_number=%1)") + .arg(lock_number) + .arg(m_changeId) + ); + if (q.numRowsAffected() == 0) { + m_lockNumber = 0; + setMessage(tr("Change is already locked by other user."), true); + } + else { + m_lockNumber = lock_number; + } + updateButtonsEnabled(); + // NIY // auto *svc = service(); // auto *nm = svc->networkManager(); @@ -221,45 +264,69 @@ void LateEntryDialog::lockChange() // }); } -void LateEntryDialog::resolveChanges(bool is_accepted) +void LateEntryDialog::updateButtonsEnabled() +{ + ui->btAccept->setEnabled(m_status == LateEntryStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); + ui->btReject->setEnabled(m_status == LateEntryStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); +} + +void LateEntryDialog::resolveChangesAndClose(bool is_accepted) { using namespace qf::gui::model; - bool is_insert = m_runId == 0; - Competitors::CompetitorDocument doc; - doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); - if (is_insert) { - doc.setValue("classId", m_classId); - } - if (ui->grpFirstName->isChecked()) { - doc.setValue("firstName", ui->edFirstName->text()); - } - if (ui->grpLastName->isChecked()) { - doc.setValue("lastName", ui->edLastName->text()); - } - if (ui->grpRegistration->isChecked()) { - doc.setValue("registration", ui->edRegistration->text()); - } - if (ui->grpSiCard->isChecked()) { + if (is_accepted) { + bool is_insert = !runId().has_value(); + if (is_insert) { + qfError() << "insert not implemented yet"; + return; + } + Competitors::CompetitorDocument doc; + doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); if (is_insert) { - doc.setValue("siId", ui->edSiCard->value()); + // doc.setValue("classId", m_classId); } - else { - qf::core::sql::Record rec { - { "siId", ui->edSiCard->value() }, - }; - qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", m_runId, rec, this); + if (ui->grpFirstName->isChecked()) { + doc.setValue("firstName", ui->edFirstName->text()); } + if (ui->grpLastName->isChecked()) { + doc.setValue("lastName", ui->edLastName->text()); + } + if (ui->grpRegistration->isChecked()) { + doc.setValue("registration", ui->edRegistration->text()); + } + if (ui->grpSiCard->isChecked()) { + if (is_insert) { + doc.setValue("siId", ui->edSiCard->value()); + } + else { + qf::core::sql::Record rec { + { "siId", ui->edSiCard->value() }, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", runId().value(), rec, this); + } + } + doc.save(); } - doc.save(); qf::core::sql::Record rec { {"status", is_accepted? "Accepted": "Rejected"}, - {"orig_data", qf::core::Utils::qvariantToJson(m_origValues.toVariantMap())}, + {"orig_data", qf::core::Utils::qvariantToJson(is_accepted? m_origValues.toVariantMap(): QVariantMap{})}, }; qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } -} // namespace Event::services::qx +void LateEntryDialog::done(int result) +{ + unlockChange(); + Super::done(result); +} +std::optional LateEntryDialog::runId() const +{ + if (auto *id = std::get_if(&m_lateEntryId)) { + return id->id; + } + return std::nullopt; +} +} // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index d6be39898..def041690 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -13,34 +13,40 @@ namespace Ui { class LateEntryDialog; } -struct LateEntryRecord; +struct LateEntry; class QxEventService; +enum class LateEntryStatus {Pending, Accepted, Rejected}; + class LateEntryDialog : public QDialog { Q_OBJECT - + using Super = QDialog; public: - explicit LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent = nullptr); + explicit LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, QWidget *parent = nullptr); ~LateEntryDialog() override; + void done(int result) override; private: + std::optional runId() const; QxEventService* service(); void setMessage(const QString &msg, bool error); - void loadOrigValues(); - void loadClassId(); + void loadOrigValues(int run_id); + // void loadClassId(); void lockChange(); + void unlockChange() const; + void updateButtonsEnabled(); - void resolveChanges(bool is_accepted); + void resolveChangesAndClose(bool is_accepted); private: Ui::LateEntryDialog *ui; int m_changeId = 0; - int m_runId = 0; + LateEntryId m_lateEntryId = RunId{}; int m_competitorId = 0; - int m_classId = 0; int m_lockNumber = 0; + LateEntryStatus m_status = LateEntryStatus::Rejected; OrigRunRecord m_origValues; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index b07ff471c..fc234271c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 569 + 576 @@ -101,35 +101,6 @@ - - - - Lock number - - - - - - - - 0 - 0 - - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - - - true - - - None - - - 999999999 - - - @@ -382,7 +353,6 @@ edClassName edRunId edChangeId - edLockNumber grpFirstName edFirstNameOrig edFirstName diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index edaf69723..02cc882b2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -36,9 +36,6 @@ constexpr auto COL_USER_ID = "user_id"; constexpr auto COL_CREATED = "created"; constexpr auto COL_LOCK_NUMBER = "lock_number"; -constexpr auto STATUS_PENDING = "Pending"; -constexpr auto STATUS_LOCKED = "Locked"; - QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), ui(new Ui::QxLateEntriesWidget) @@ -101,9 +98,7 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : lst->setCurrentIndex(0); connect(lst, &QComboBox::currentIndexChanged, this, &QxLateEntriesWidget::reload); } - connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); - connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); @@ -111,12 +106,8 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QSignalBlocker sb1(ui->lstType); ui->lstType->setCurrentIndex(0); - QSignalBlocker sb2(ui->chkNull); - ui->chkNull->setChecked(true); QSignalBlocker sb3(ui->chkPending); ui->chkPending->setChecked(true); - QSignalBlocker sb4(ui->chkLocked); - ui->chkLocked->setChecked(true); QSignalBlocker sb5(ui->chkAccepted); ui->chkAccepted->setChecked(true); QSignalBlocker sb6(ui->chkRejected); @@ -185,15 +176,9 @@ void QxLateEntriesWidget::reload() .where("stage_id=" + QString::number(stage_id)) .orderBy("id"); QStringList status_cond_list; - if (ui->chkNull->isChecked()) { - status_cond_list << "status IS NULL"; - } if (ui->chkPending->isChecked()) { status_cond_list << "status='Pending'"; } - if (ui->chkLocked->isChecked()) { - status_cond_list << "status LIKE 'Locked%'"; - } if (ui->chkAccepted->isChecked()) { status_cond_list << "status='Accepted'"; } @@ -252,23 +237,25 @@ void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); auto status = m_model->value(row, COL_STATUS).toString(); - if (status == STATUS_PENDING || status == STATUS_LOCKED) { - auto data = m_model->value(row, COL_DATA).toString(); - auto qxdata = QxChangeData::fromJson(data); - if (qxdata.lateEntry.has_value()) { - auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); - auto change_id = m_model->value(row, COL_ID).toInt(); - LateEntryDialog dlg(change_id, lock_number, qxdata.lateEntry.value(), this); - dlg.exec(); - ui->tableView->reloadRow(row); - } + auto data = m_model->value(row, COL_DATA).toString(); + auto qxdata = QxChangeData::fromJson(data); + if (qxdata.lateEntry.has_value()) { + // auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); + auto change_id = m_model->value(row, COL_ID).toInt(); + LateEntryDialog dlg(change_id, qxdata.lateEntry.value(), status, this); + dlg.exec(); + ui->tableView->reloadRow(row); } } void QxLateEntriesWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { + if (recchng.table == "qxchanges" && recchng.record.contains("status")) { + // status affect filter, cannot be solved by applyQxRecChng + m_model->reload(); + return; + } m_model->applyQxRecChng(recchng, source); } } - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 30abe1ed9..9ac4e2a67 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -87,13 +87,6 @@ 0 - - - - Locked - - - @@ -108,13 +101,6 @@ - - - - Null - - - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index 3cd7c04d2..fe2150e97 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -11,27 +11,23 @@ namespace Event::services::qx { // return ret; // } -LateEntryRecord LateEntryRecord::fromVariantMap(const QVariantMap &map) +LateEntry LateEntry::fromVariantMap(const QVariantMap &map) { - LateEntryRecord ret; + LateEntry ret; - if (auto v = map.value("class_name"); v.isValid()) { ret.class_name = v.toString(); } + auto id_map = map.value("id").toMap(); + if (auto id = id_map.value("RunId").toInt(); id > 0) { + ret.id = RunId{id}; + } + if (auto id = id_map.value("ClassId").toInt(); id > 0) { + ret.id = ClassId{id}; + } if (auto v = map.value("firstname"); v.isValid()) { ret.first_name = v.toString(); } if (auto v = map.value("lastname"); v.isValid()) { ret.last_name = v.toString(); } if (auto v = map.value("registration"); v.isValid()) { ret.registration = v.toString(); } if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } - ret.note = map.value("note").toString(); - - return ret; -} - -LateEntry LateEntry::fromVariantMap(const QVariantMap &map) -{ - LateEntry ret; - - ret.record = LateEntryRecord::fromVariantMap(map.value("record").toMap()); - ret.run_id = map.value("run_id").toInt(); + if (auto v = map.value("note"); v.isValid()) { ret.note = v.toString(); } return ret; } @@ -39,10 +35,10 @@ LateEntry LateEntry::fromVariantMap(const QVariantMap &map) QVariantMap OrigRunRecord::toVariantMap() const { QVariantMap ret; - ret["first_name"] = first_name; - ret["last_name"] = last_name; + ret["firstname"] = first_name; + ret["lastname"] = last_name; ret["registration"] = registration; - ret["si_id"] = si_id; + ret["siid"] = si_id; return ret; } @@ -57,5 +53,3 @@ QxChangeData QxChangeData::fromJson(const QString &json) } } // namespace Event::services::qx - - diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 1577011d8..1020c4a45 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -4,30 +4,29 @@ #include #include +#include +#include + namespace Event::services::qx { constexpr auto DATA_TYPE_LATE_ENTRY = "LateEntry"; -struct LateEntryRecord +struct RunId { int id = 0; }; +struct ClassId { int id = 0; }; + +using LateEntryId = std::variant; + +struct LateEntry { - std::optional class_name; + LateEntryId id = RunId{}; std::optional first_name; std::optional last_name; std::optional registration; std::optional si_id; // std::optional si_id_rent; - QString note; - - // static LateEntryRecord fromJsonString(const QString &json); - static LateEntryRecord fromVariantMap(const QVariantMap &map); -}; + std::optional note; -struct LateEntry -{ - int run_id = 0; - LateEntryRecord record; - - // static LateEntryRecord fromJsonString(const QString &json); + // static LateEntry fromJsonString(const QString &json); static LateEntry fromVariantMap(const QVariantMap &map); }; @@ -92,6 +91,3 @@ struct EventChange // }; } // namespace Event::services::qx - - - From efd64804af9ea85929703e416db07d09bb096ee4 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sat, 4 Jul 2026 00:26:22 +0200 Subject: [PATCH 39/52] Add status message and class handling to late entries Constructor now accepts a status_message; UI adds edStatusMessage and a Send button which calls updateQxChangeMessage to persist the message. Implement loadClassName() and classId() to handle ClassId foreign ids and use the class id for inserts. Rename LateEntryId to LateEntryForeignId and add a foreign_table column in the model. Persist status_message when updating qxchanges and adjust related logic and UI layout. --- .../Event/src/services/qx/lateentrydialog.cpp | 69 +++++++++++-------- .../Event/src/services/qx/lateentrydialog.h | 9 ++- .../Event/src/services/qx/lateentrydialog.ui | 40 +++++++++-- .../src/services/qx/qxlateentrieswidget.cpp | 5 +- .../plugins/Event/src/services/qx/runchange.h | 4 +- 5 files changed, 86 insertions(+), 41 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 39e631cfc..b61a48168 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -41,7 +41,7 @@ LateEntryStatus lateEntryStatusFromString(const QString &status) } // namespace -LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, QWidget *parent) +LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent) : Super(parent) , ui(new Ui::LateEntryDialog) , m_changeId(change_id) @@ -50,6 +50,8 @@ LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, con { ui->setupUi(this); + ui->edStatusMessage->setPlainText(status_message); + ui->btAccept->setDisabled(true); ui->btReject->setDisabled(true); @@ -64,6 +66,7 @@ LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, con resolveChangesAndClose(false); done(QDialog::Accepted); }); + connect(ui->btSendMessage, &QPushButton::clicked, this, &LateEntryDialog::updateQxChangeMessage); ui->edNote->setText(late_entry.note.value_or(QString())); @@ -103,6 +106,10 @@ LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, con ui->edRunId->setValue(id); loadOrigValues(id); } + else if (auto *id = std::get_if(&late_entry.id)) { + auto class_id = id->id; + loadClassName(class_id); + } lockChange(); } @@ -162,30 +169,22 @@ void LateEntryDialog::loadOrigValues(int run_id) ui->edSiCardOrig->setValue(m_origValues.si_id); } -// void LateEntryDialog::loadClassId() -// { -// auto class_name = ui->edClassName->text(); -// if (class_name.isEmpty()) { -// return; -// } -// qf::core::sql::Query q; -// q.execThrow(QStringLiteral("SELECT id" -// " FROM classes" -// " WHERE name='%1'") -// .arg(class_name) -// ); -// if (q.next()) { -// m_classId = q.value("id").toInt(); -// } -// } +void LateEntryDialog::loadClassName(int class_id) +{ + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT name FROM classes WHERE id=%1") + .arg(class_id) ); + if (q.next()) { + ui->edClassName->setText(q.value(0).toString()); + } +} void LateEntryDialog::unlockChange() const { if (m_lockNumber > 0 || ui->chkForce->isChecked()) { qf::core::sql::Query q; q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=NULL WHERE id=%1") - .arg(m_changeId) - ); + .arg(m_changeId) ); } } @@ -275,15 +274,12 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) using namespace qf::gui::model; if (is_accepted) { - bool is_insert = !runId().has_value(); - if (is_insert) { - qfError() << "insert not implemented yet"; - return; - } + auto class_id = classId(); + bool is_insert = class_id.has_value(); Competitors::CompetitorDocument doc; doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); if (is_insert) { - // doc.setValue("classId", m_classId); + doc.setValue("classId", class_id.value()); } if (ui->grpFirstName->isChecked()) { doc.setValue("firstName", ui->edFirstName->text()); @@ -298,11 +294,11 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) if (is_insert) { doc.setValue("siId", ui->edSiCard->value()); } - else { + else if (auto run_id = runId(); run_id.has_value()) { qf::core::sql::Record rec { { "siId", ui->edSiCard->value() }, }; - qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", runId().value(), rec, this); + qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", run_id.value(), rec, this); } } doc.save(); @@ -310,7 +306,16 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) qf::core::sql::Record rec { {"status", is_accepted? "Accepted": "Rejected"}, - {"orig_data", qf::core::Utils::qvariantToJson(is_accepted? m_origValues.toVariantMap(): QVariantMap{})}, + {"status_message", ui->edStatusMessage->toPlainText()}, + {"orig_data", qf::core::Utils::qvariantToJson(is_accepted? m_origValues.toVariantMap(): QVariantMap{})}, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); +} + +void LateEntryDialog::updateQxChangeMessage() +{ + qf::core::sql::Record rec { + { "status_message", ui->edStatusMessage->toPlainText() }, }; qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } @@ -329,4 +334,12 @@ std::optional LateEntryDialog::runId() const return std::nullopt; } +std::optional LateEntryDialog::classId() const +{ + if (auto *id = std::get_if(&m_lateEntryId)) { + return id->id; + } + return std::nullopt; +} + } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index def041690..584b62fd1 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -23,27 +23,30 @@ class LateEntryDialog : public QDialog Q_OBJECT using Super = QDialog; public: - explicit LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, QWidget *parent = nullptr); + explicit LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent = nullptr); ~LateEntryDialog() override; void done(int result) override; private: std::optional runId() const; + std::optional classId() const; QxEventService* service(); void setMessage(const QString &msg, bool error); void loadOrigValues(int run_id); - // void loadClassId(); + void loadClassName(int class_id); void lockChange(); void unlockChange() const; void updateButtonsEnabled(); void resolveChangesAndClose(bool is_accepted); + void updateQxChangeMessage(); + private: Ui::LateEntryDialog *ui; int m_changeId = 0; - LateEntryId m_lateEntryId = RunId{}; + LateEntryForeignId m_lateEntryId = RunId{}; int m_competitorId = 0; int m_lockNumber = 0; LateEntryStatus m_status = LateEntryStatus::Rejected; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index fc234271c..e2b9a4766 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 576 + 586 @@ -280,14 +280,38 @@ - - - Message - - + + + + + Message + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Send + + + + - + @@ -368,6 +392,8 @@ edSiCard edSiCardRent edNote + edStatusMessage + btSendMessage chkForce btAccept btReject diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 02cc882b2..780c9a28e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -28,6 +28,7 @@ namespace Event::services::qx { constexpr auto COL_ID = "id"; constexpr auto COL_DATA_TYPE = "data_type"; constexpr auto COL_FOREIGN_ID = "foreign_id"; +constexpr auto COL_FOREIGN_TABLE = "foreign_table"; constexpr auto COL_DATA = "data"; constexpr auto COL_ORIG_DATA = "orig_data"; constexpr auto COL_STATUS = "status"; @@ -59,6 +60,7 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : m_model->addColumn(COL_ID).setReadOnly(true).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_STATUS, tr("Status")); m_model->addColumn(COL_DATA_TYPE, tr("Type")); + m_model->addColumn(COL_FOREIGN_TABLE, tr("Foreign table")); m_model->addColumn(COL_FOREIGN_ID, tr("Foreign ID")).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_USER_ID, tr("User")); m_model->addColumn(COL_STATUS_MESSAGE, tr("Status message")); @@ -237,12 +239,13 @@ void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); auto status = m_model->value(row, COL_STATUS).toString(); + auto status_message = m_model->value(row, COL_STATUS_MESSAGE).toString(); auto data = m_model->value(row, COL_DATA).toString(); auto qxdata = QxChangeData::fromJson(data); if (qxdata.lateEntry.has_value()) { // auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); auto change_id = m_model->value(row, COL_ID).toInt(); - LateEntryDialog dlg(change_id, qxdata.lateEntry.value(), status, this); + LateEntryDialog dlg(change_id, qxdata.lateEntry.value(), status, status_message, this); dlg.exec(); ui->tableView->reloadRow(row); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 1020c4a45..58f0371a7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -14,11 +14,11 @@ constexpr auto DATA_TYPE_LATE_ENTRY = "LateEntry"; struct RunId { int id = 0; }; struct ClassId { int id = 0; }; -using LateEntryId = std::variant; +using LateEntryForeignId = std::variant; struct LateEntry { - LateEntryId id = RunId{}; + LateEntryForeignId id = RunId{}; std::optional first_name; std::optional last_name; std::optional registration; From dbce7f3b9dc82e1a9fc6cfef59d2d571966e15c2 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 5 Jul 2026 19:35:40 +0200 Subject: [PATCH 40/52] Move start-time logic to CompetitorDocument Make possibleStartTimesMs a static method on CompetitorDocument and replace the widget-local implementation with a call to it. Add start_time fields to LateEntry/OrigRunRecord and update late-entry dialog (pass stage id, UI tweaks, size, readonly original name). Add isExportDatabase setting and only set RPC device options when enabled. Add RunsWidget::showEditCompetitorDialog helper. --- .../Competitors/src/competitordocument.cpp | 98 +++++++++++++ .../Competitors/src/competitordocument.h | 2 + .../Competitors/src/competitorwidget.cpp | 92 +----------- .../Event/src/services/qx/lateentrydialog.cpp | 131 +++++++++--------- .../Event/src/services/qx/lateentrydialog.h | 4 +- .../Event/src/services/qx/lateentrydialog.ui | 52 ++++++- .../Event/src/services/qx/qxeventservice.cpp | 12 +- .../Event/src/services/qx/qxeventservice.h | 1 + .../src/services/qx/qxeventservicewidget.cpp | 11 +- .../src/services/qx/qxeventservicewidget.h | 4 +- .../src/services/qx/qxeventservicewidget.ui | 73 ++++------ .../src/services/qx/qxlateentrieswidget.cpp | 78 ++++++++--- .../Event/src/services/qx/runchange.cpp | 2 + .../plugins/Event/src/services/qx/runchange.h | 4 +- .../plugins/Runs/src/runswidget.cpp | 12 ++ .../quickevent/plugins/Runs/src/runswidget.h | 1 + 16 files changed, 335 insertions(+), 242 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp index 698f22ebd..cae50bfde 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp @@ -1,5 +1,6 @@ #include "competitordocument.h" +#include #include #include @@ -213,4 +214,101 @@ QVariant CompetitorDocument::siid() const return value(SIID); } +QList CompetitorDocument::possibleStartTimesMs(int run_id) +{ + using namespace qf::core::sql; + + int class_id; + int stage_id; + int start_time; + QString club_abbr; + { + qf::core::sql::QueryBuilder qb; + qb.select2("runs", "competitorId, stageId, startTimeMs") + .select2("competitors", "classId, registration") + .from("runs") + .joinRestricted("runs.competitorId", "competitors.id", "runs.id=" QF_IARG(run_id), qf::core::sql::QueryBuilder::INNER_JOIN) + .where("runs.isRunning"); + Query q; + q.exec(qb.toString(), qf::core::Exception::Throw); + if (q.next()) { + class_id = q.value("classId").toInt(); + stage_id = q.value("stageId").toInt(); + start_time = q.value("startTimeMs").toInt(); + club_abbr = q.value("registration").toString().mid(0, 3).toUpper(); + } + else { + return {}; + } + } + + Classes::ClassDef class_def; + class_def.load(class_id, stage_id, false); + + if (class_def.classInterval == 0) { + return {}; + } + + struct S { + QString club; + int start_time = 0; + + bool operator<(const S &o) const { return start_time < o.start_time; } + }; + QList runs; + { + qf::core::sql::QueryBuilder qb; + qb.select2("runs", "startTimeMs") + .select2("competitors", "registration") + .from("runs") + .joinRestricted("runs.competitorId", "competitors.id", "competitors.classId=" QF_IARG(class_id), qf::core::sql::QueryBuilder::INNER_JOIN) + .where("runs.stageId=" QF_IARG(stage_id)) + .where("runs.isRunning") + .orderBy("runs.startTimeMs"); + //qfInfo() << qb.toString(); + Query q; + q.exec(qb.toString(), qf::core::Exception::Throw); + while (q.next()) { + //auto start_time = q.value("startTimeMs").toInt(); + //if (start_time < class_def.classStartFirst) { + // continue; + //} + runs << S { + .club = q.value("registration").toString().mid(0, 3).toUpper(), + .start_time = q.value("startTimeMs").toInt() + }; + } + std::sort(runs.begin(), runs.end()); + } + QList ret; + auto start_interval_ms = class_def.classInterval * 60 * 1000; + for (auto stime = class_def.classStartFirst; stime <= class_def.classStartLast; stime += class_def.classInterval) { + S r { .club = {}, .start_time = stime }; + auto [lo, up] = std::equal_range(runs.begin(), runs.end(), r); + if (lo == up || stime == start_time) { + // stime does not exist or it is my current start_time, should be inserted before up + // check next club + // qfInfo() << "free stime:" << quickevent::core::og::TimeMs(stime).toString(); + if (up != runs.end()) { + // qfInfo() << "next club:" << up->club; + if (up->club == club_abbr && (up->start_time - stime) <= start_interval_ms) { + // qfInfo() << "same next club:" << club_abbr; + continue; + } + } + // check prev club + if (up != runs.begin()) { + up--; + // qfInfo() << "prev club:" << up->club; + if (up->club == club_abbr && (stime - up->start_time) <= start_interval_ms) { + // qfInfo() << "same prev club:" << club_abbr; + continue; + } + } + ret << stime; + } + } + return ret; +} + diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index b282d4ebb..8894d63d3 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -19,6 +19,8 @@ class CompetitorDocument : public qf::gui::model::SqlDataDocument void setSiid(const QVariant &siid); QVariant siid() const; const QVector& runsIds() const {return m_runsIds;} + + static QList possibleStartTimesMs(int run_id); protected: bool loadData() override; bool saveData() override; diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp index 62c225d4e..a19672881 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp @@ -376,97 +376,7 @@ QString CompetitorWidget::guessClassFromRegistration(const QString ®istration QList CompetitorWidget::possibleStartTimesMs(int run_id) { - int class_id; - int stage_id; - int start_time; - QString club_abbr; - { - qf::core::sql::QueryBuilder qb; - qb.select2("runs", "competitorId, stageId, startTimeMs") - .select2("competitors", "classId, registration") - .from("runs") - .joinRestricted("runs.competitorId", "competitors.id", "runs.id=" QF_IARG(run_id), qf::core::sql::QueryBuilder::INNER_JOIN) - .where("runs.isRunning"); - qfs::Query q; - q.exec(qb.toString(), qf::core::Exception::Throw); - if (q.next()) { - class_id = q.value("classId").toInt(); - stage_id = q.value("stageId").toInt(); - start_time = q.value("startTimeMs").toInt(); - club_abbr = q.value("registration").toString().mid(0, 3).toUpper(); - } - else { - return {}; - } - } - - Classes::ClassDef class_def; - class_def.load(class_id, stage_id, false); - - if (class_def.classInterval == 0) { - return {}; - } - - struct S { - QString club; - int start_time = 0; - - bool operator<(const S &o) const { return start_time < o.start_time; } - }; - QList runs; - { - qf::core::sql::QueryBuilder qb; - qb.select2("runs", "startTimeMs") - .select2("competitors", "registration") - .from("runs") - .joinRestricted("runs.competitorId", "competitors.id", "competitors.classId=" QF_IARG(class_id), qf::core::sql::QueryBuilder::INNER_JOIN) - .where("runs.stageId=" QF_IARG(stage_id)) - .where("runs.isRunning") - .orderBy("runs.startTimeMs"); - //qfInfo() << qb.toString(); - qfs::Query q; - q.exec(qb.toString(), qf::core::Exception::Throw); - while (q.next()) { - //auto start_time = q.value("startTimeMs").toInt(); - //if (start_time < class_def.classStartFirst) { - // continue; - //} - runs << S { - .club = q.value("registration").toString().mid(0, 3).toUpper(), - .start_time = q.value("startTimeMs").toInt() - }; - } - std::sort(runs.begin(), runs.end()); - } - QList ret; - auto start_interval_ms = class_def.classInterval * 60 * 1000; - for (auto stime = class_def.classStartFirst; stime <= class_def.classStartLast; stime += class_def.classInterval) { - S r { .club = {}, .start_time = stime }; - auto [lo, up] = std::equal_range(runs.begin(), runs.end(), r); - if (lo == up || stime == start_time) { - // stime does not exist or it is my current start_time, should be inserted before up - // check next club - // qfInfo() << "free stime:" << quickevent::core::og::TimeMs(stime).toString(); - if (up != runs.end()) { - qfInfo() << "next club:" << up->club; - if (up->club == club_abbr && (up->start_time - stime) <= start_interval_ms) { - // qfInfo() << "same next club:" << club_abbr; - continue; - } - } - // check prev club - if (up != runs.begin()) { - up--; - // qfInfo() << "prev club:" << up->club; - if (up->club == club_abbr && (stime - up->start_time) <= start_interval_ms) { - // qfInfo() << "same prev club:" << club_abbr; - continue; - } - } - ret << stime; - } - } - return ret; + return Competitors::CompetitorDocument::possibleStartTimesMs(run_id); } void CompetitorWidget::onRegistrationSelected(const QVariantMap &values) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index b61a48168..7b9244214 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -6,6 +6,8 @@ #include +#include + #include #include #include @@ -20,6 +22,8 @@ #include #include +using namespace quickevent::core::og; + namespace Event::services::qx { namespace { @@ -41,13 +45,16 @@ LateEntryStatus lateEntryStatusFromString(const QString &status) } // namespace -LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent) +LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent) : Super(parent) , ui(new Ui::LateEntryDialog) , m_changeId(change_id) + , m_stageId(stage_id) , m_lateEntryId(late_entry.id) , m_status(lateEntryStatusFromString(status)) { + Q_ASSERT(change_id > 0); + ui->setupUi(this); ui->edStatusMessage->setPlainText(status_message); @@ -101,10 +108,17 @@ LateEntryDialog::LateEntryDialog(int change_id, const LateEntry &late_entry, con ui->edSiCard->setValue(late_entry.si_id.value_or(0)); update_background(ui->edSiCard); + ui->grpStartTime->setChecked(late_entry.start_time_ms.has_value()); + ui->edStartTime->setText(TimeMs(late_entry.start_time_ms.value_or(0)).toString()); + update_background(ui->edStartTime); + + connect(ui->edStartTime, &QLineEdit::textEdited, this, &LateEntryDialog::checkStartTimeIsValid); + if (auto *run_id = std::get_if(&late_entry.id)) { auto id = run_id->id; ui->edRunId->setValue(id); loadOrigValues(id); + checkStartTimeIsValid(); } else if (auto *id = std::get_if(&late_entry.id)) { auto class_id = id->id; @@ -161,11 +175,23 @@ void LateEntryDialog::loadOrigValues(int run_id) m_origValues.first_name = doc.value("firstName").toString(); m_origValues.last_name = doc.value("lastName").toString(); m_origValues.registration = doc.value("registration").toString(); + { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT startTimeMs" + " FROM runs" + " WHERE id=%1") + .arg(run_id) + ); + if (q.next()) { + m_origValues.start_time_ms = q.value("startTimeMs").toInt(); + } + } m_origValues.si_id = doc.value("siId").toInt(); ui->edFirstNameOrig->setText(m_origValues.first_name); ui->edLastNameOrig->setText(m_origValues.last_name); ui->edRegistrationOrig->setText(m_origValues.registration); + ui->edStartTimeOrig->setText(TimeMs(m_origValues.start_time_ms).toString()); ui->edSiCardOrig->setValue(m_origValues.si_id); } @@ -204,63 +230,6 @@ void LateEntryDialog::lockChange() m_lockNumber = lock_number; } updateButtonsEnabled(); - - // NIY - // auto *svc = service(); - // auto *nm = svc->networkManager(); - - // auto path = QStringLiteral("/api/event/%1/changes").arg(svc->eventId()); - // QUrlQuery query; - // query.addQueryItem("from_id", QString::number(m_changeId)); - // query.addQueryItem("limit", QString::number(1)); - // svc->getHttpJson(path, query, this, [this](auto data, auto error) { - // if (!error.isEmpty()) { - // setMessage(error, true); - // return; - // } - // auto rec = data.toList().value(0).toMap(); - // }); - - - // QNetworkRequest request; - // auto url = svc->exchangeServerUrl(); - // // qfInfo() << "url " << url.toString(); - // url.setPath("/api/event/current/changes/lock-change"); - - // QUrlQuery query; - // query.addQueryItem("change_id", QString::number(m_changeId)); - // auto connection_id = QxClientService::currentConnectionId(); - // query.addQueryItem("lock_number", QString::number(connection_id)); - // url.setQuery(query); - // qfInfo() << "GET " << url.toString(); - - // request.setUrl(url); - // request.setRawHeader(QxClientService::QX_API_TOKEN, svc->apiToken()); - // auto *reply = nm->get(request); - // connect(reply, &QNetworkReply::finished, this, [this, reply, connection_id]() { - // auto data = reply->readAll(); - // if (reply->error() == QNetworkReply::NetworkError::NoError) { - // m_lockNumber = data.toInt(); - // ui->edLockNumber->setValue(m_lockNumber); - // if (m_lockNumber == connection_id) { - // ui->btAccept->setDisabled(false); - // ui->btReject->setDisabled(false); - - // qf::core::sql::Query q; - // q.execThrow(QStringLiteral("UPDATE qxchanges SET lock_number=%1, status='Locked' WHERE id=%2") - // .arg(connection_id) - // .arg(m_changeId) - // ); - // } - // else { - // setMessage(tr("Change is locked already by other client: %1, current client id:.%2").arg(m_lockNumber).arg(connection_id), false); - // } - // } - // else { - // setMessage(tr("Lock change error: %1\n%2").arg(reply->errorString()).arg(QString::fromUtf8(data)), true); - // } - // reply->deleteLater(); - // }); } void LateEntryDialog::updateButtonsEnabled() @@ -294,14 +263,33 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) if (is_insert) { doc.setValue("siId", ui->edSiCard->value()); } - else if (auto run_id = runId(); run_id.has_value()) { - qf::core::sql::Record rec { - { "siId", ui->edSiCard->value() }, - }; - qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", run_id.value(), rec, this); - } } doc.save(); + + qf::core::sql::Record rec; + if (ui->grpSiCard->isChecked()) { + rec["siId"] = ui->edSiCard->value(); + } + if (ui->grpStartTime->isChecked()) { + rec["starttimems"] = TimeMs::fromString(ui->edStartTime->text()).msec(); + } + if (!rec.isEmpty()) { + auto competitor_id = doc.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + int run_id = 0; + { + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT id FROM runs WHERE competitorId=%1 AND stageId=%2") + .arg(competitor_id) + .arg(m_stageId) + ); + if (q.next()) { + run_id = q.value(0).toInt(); + } + } + Q_ASSERT(run_id > 0); + qf::gui::framework::Application::instance()->qxSql()->updateRecord("runs", run_id, rec, this); + } } qf::core::sql::Record rec { @@ -320,6 +308,21 @@ void LateEntryDialog::updateQxChangeMessage() qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } +void LateEntryDialog::checkStartTimeIsValid() +{ + if (auto run_id = runId(); run_id.has_value()) { + auto stime = TimeMs::fromString(ui->edStartTime->text()); + if (stime.isValid()) { + auto possible_stimes = Competitors::CompetitorDocument::possibleStartTimesMs(run_id.value()); + if (possible_stimes.contains(stime.msec())) { + ui->edStartTime->setStyleSheet({}); + } else { + ui->edStartTime->setStyleSheet("background: red"); + } + } + } +} + void LateEntryDialog::done(int result) { unlockChange(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 584b62fd1..909cebfd2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -23,7 +23,7 @@ class LateEntryDialog : public QDialog Q_OBJECT using Super = QDialog; public: - explicit LateEntryDialog(int change_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent = nullptr); + explicit LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent = nullptr); ~LateEntryDialog() override; void done(int result) override; @@ -43,9 +43,11 @@ class LateEntryDialog : public QDialog void resolveChangesAndClose(bool is_accepted); void updateQxChangeMessage(); + void checkStartTimeIsValid(); private: Ui::LateEntryDialog *ui; int m_changeId = 0; + int m_stageId = 0; LateEntryForeignId m_lateEntryId = RunId{}; int m_competitorId = 0; int m_lockNumber = 0; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index e2b9a4766..73d119d72 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 586 + 668 @@ -113,7 +113,11 @@ - + + + true + + @@ -141,7 +145,11 @@ - + + + true + + @@ -169,7 +177,11 @@ - + + + true + + @@ -261,6 +273,38 @@ + + + + Start time + + + true + + + + + + true + + + + + + + -> + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index c0b4d42d6..fb27d5912 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -83,11 +83,13 @@ void QxEventService::run() { m_rpcConnection = new DeviceConnection("QuickEvent", this); m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); - RpcValue::Map opts; - RpcValue::Map device; - device["deviceId"] = api_token.toStdString(); - opts["device"] = device; - m_rpcConnection->setConnectionOptions(opts); + if (ss.isExportDatabase()) { + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = api_token.toStdString(); + opts["device"] = device; + m_rpcConnection->setConnectionOptions(opts); + } connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 7c0eb71c2..0e0ec5509 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -19,6 +19,7 @@ class QxEventServiceSettings : public ServiceSettings using Super = ServiceSettings; QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=test&password=test") + QF_VARIANTMAP_FIELD2(bool, is, set, ExportDatabase, false) public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index d18cd0e38..e6ba839e5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -30,8 +30,6 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) { setPersistentSettingsId("QxEventServiceWidget"); ui->setupUi(this); - connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); - connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); setMessage(""); @@ -41,6 +39,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) auto current_stage = event_plugin->currentStageId(); auto settings = svc->settings(); ui->edServerUrl->setText(settings.shvBrokerUrl()); + ui->chkExportDatabase->setChecked(settings.isExportDatabase()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); ui->edEventId->setValue(svc->eventId()); @@ -99,6 +98,7 @@ bool QxEventServiceWidget::saveSettings() if(svc) { auto ss = svc->settings(); ss.setShvBrokerUrl(ui->edServerUrl->text()); + ss.setExportDatabase(ui->chkExportDatabase->isChecked()); svc->setSettings(ss); auto *event_plugin = getPlugin(); @@ -110,13 +110,6 @@ bool QxEventServiceWidget::saveSettings() return true; } -void QxEventServiceWidget::updateOCheckListPostUrl() -{ - auto url = QStringLiteral("%1/api/event/current/oc").arg(ui->edServerUrl->text()); - ui->edOChecklistUrl->setText(url); - ui->edOChecklistUrlHeader->setText(QStringLiteral("qx-api-token=%1").arg(ui->edApiToken->text())); -} - void QxEventServiceWidget::testConnection() { using namespace shv::iotqt::rpc; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index 19454ad86..7e4b3de21 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -18,19 +18,19 @@ class QxEventServiceWidget : public qf::gui::framework::DialogWidget public: explicit QxEventServiceWidget(QWidget *parent = nullptr); ~QxEventServiceWidget() override; + + bool acceptDialogDone(int result) override; private: enum class MessageType { Ok, Error, Progress }; void setMessage(const QString &msg = {}, MessageType msg_type = MessageType::Ok); QxEventService* service(); bool saveSettings(); - void updateOCheckListPostUrl(); void testConnection(); void exportEventInfo(); void exportStartList(); void exportRuns(); private: Ui::QxEventServiceWidget *ui; - bool acceptDialogDone(int result) override; }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index 5f4a41e1a..590077127 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -16,30 +16,23 @@ - - - - SHV broker url - - - - - - - - + + Fill this url into HTTP POST synchonization method in O-CheckList - API token + Event ID + + + - + Fill this url into HTTP POST synchonization method in O-CheckList @@ -49,7 +42,7 @@ - + false @@ -65,17 +58,14 @@ - - - - Fill this url into HTTP POST synchonization method in O-CheckList - + + - Event ID + SHV broker url - + false @@ -94,37 +84,20 @@ - - + + Fill this url into HTTP POST synchonization method in O-CheckList - OCheckList Url - - - - - - - true + API token - - - - Fill this url into HTTP POST synchonization method in O-CheckList - + + - Header - - - - - - - true + Export database @@ -147,6 +120,9 @@ + + false + Export event info @@ -161,6 +137,9 @@ + + false + Export start list @@ -168,6 +147,9 @@ + + false + Export runs @@ -202,7 +184,6 @@ edServerUrl - edOChecklistUrl diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 780c9a28e..f6e408ef6 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -6,6 +6,7 @@ #include "runchange.h" #include +#include #include #include @@ -17,6 +18,7 @@ #include #include +#include namespace qfm = qf::gui::model; namespace qfs = qf::core::sql; @@ -25,7 +27,25 @@ using qf::gui::framework::getPlugin; namespace Event::services::qx { +namespace { +enum Columns { + col_id = 0, + col_stage_id, + col_data_type, + col_foreign_id, + col_foreign_table, + col_data, + col_orig_data, + col_status, + col_status_message, + col_user_id, + col_created, + col_lock_number, + col_COUNT +}; + constexpr auto COL_ID = "id"; +constexpr auto COL_STAGE_ID = "stage_id"; constexpr auto COL_DATA_TYPE = "data_type"; constexpr auto COL_FOREIGN_ID = "foreign_id"; constexpr auto COL_FOREIGN_TABLE = "foreign_table"; @@ -36,6 +56,7 @@ constexpr auto COL_STATUS_MESSAGE = "status_message"; constexpr auto COL_USER_ID = "user_id"; constexpr auto COL_CREATED = "created"; constexpr auto COL_LOCK_NUMBER = "lock_number"; +} // namespace QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), @@ -57,19 +78,24 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : ui->toolbar->setTableView(ui->tableView); m_model = new qfm::SqlTableModel(this); //m->setObjectName("classes.classesModel"); - m_model->addColumn(COL_ID).setReadOnly(true).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_STATUS, tr("Status")); - m_model->addColumn(COL_DATA_TYPE, tr("Type")); - m_model->addColumn(COL_FOREIGN_TABLE, tr("Foreign table")); - m_model->addColumn(COL_FOREIGN_ID, tr("Foreign ID")).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_USER_ID, tr("User")); - m_model->addColumn(COL_STATUS_MESSAGE, tr("Status message")); - m_model->addColumn(COL_CREATED, tr("Created")); - m_model->addColumn(COL_LOCK_NUMBER, tr("Lock")); - m_model->addColumn(COL_DATA, tr("Data"));//.setToolTip(tr("Locked for drawing")); - m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); + + m_model->clearColumns(col_COUNT); + m_model->setColumn(col_id, qf::gui::model::TableModel::ColumnDefinition(COL_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_stage_id, qf::gui::model::TableModel::ColumnDefinition(COL_STAGE_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_data_type, qf::gui::model::SqlTableModel::ColumnDefinition(COL_DATA_TYPE).setReadOnly(true)); + m_model->setColumn(col_foreign_id, qf::gui::model::SqlTableModel::ColumnDefinition(COL_FOREIGN_ID).setReadOnly(true).setAlignment(Qt::AlignRight)); + m_model->setColumn(col_foreign_table, qf::gui::model::SqlTableModel::ColumnDefinition(COL_FOREIGN_TABLE).setReadOnly(true)); + m_model->setColumn(col_data, qf::gui::model::SqlTableModel::ColumnDefinition(COL_DATA).setReadOnly(true)); + m_model->setColumn(col_orig_data, qf::gui::model::SqlTableModel::ColumnDefinition(COL_ORIG_DATA).setReadOnly(true)); + m_model->setColumn(col_status, qf::gui::model::SqlTableModel::ColumnDefinition(COL_STATUS).setReadOnly(true)); + m_model->setColumn(col_status_message, qf::gui::model::SqlTableModel::ColumnDefinition(COL_STATUS_MESSAGE).setReadOnly(true)); + m_model->setColumn(col_user_id, qf::gui::model::SqlTableModel::ColumnDefinition(COL_USER_ID).setReadOnly(true)); + m_model->setColumn(col_created, qf::gui::model::SqlTableModel::ColumnDefinition(COL_CREATED).setReadOnly(true)); + m_model->setColumn(col_lock_number, qf::gui::model::SqlTableModel::ColumnDefinition(COL_LOCK_NUMBER).setReadOnly(true)); ui->tableView->setTableModel(m_model); + ui->tableView->setColumnHidden(col_stage_id, true); + showMessage(tr("QxEvent service is disabled"), true); setEnabled(false); @@ -226,26 +252,40 @@ void QxLateEntriesWidget::applyQxChange(int sql_id) void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) { - QAction a_neco(tr("Neco"), nullptr); + QAction a_edit_competitor(tr("Edit competitor"), nullptr); QList lst; - lst << &a_neco; + lst << &a_edit_competitor; QAction *a = QMenu::exec(lst, ui->tableView->viewport()->mapToGlobal(pos)); - if(a == &a_neco) { - //printSelectedCards(); + if(a == &a_edit_competitor) { + if (auto ix = ui->tableView->indexAt(pos); ix.isValid()) { + if (auto t = ix.sibling(ix.row(), col_foreign_table).data().toString(); t == "runs") { + auto run_id = ix.sibling(ix.row(), col_foreign_id).data().toInt(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT competitorId FROM runs WHERE id=%1") + .arg(run_id) + ); + if (q.next()) { + auto competitor_id = q.value(0).toInt(); + RunsWidget::showEditCompetitorDialog(competitor_id, this); + return; + } + } + } + QMessageBox::information(this, tr("Edit competitor"), tr("No competitor found for this run.")); } } void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); - auto status = m_model->value(row, COL_STATUS).toString(); - auto status_message = m_model->value(row, COL_STATUS_MESSAGE).toString(); auto data = m_model->value(row, COL_DATA).toString(); auto qxdata = QxChangeData::fromJson(data); if (qxdata.lateEntry.has_value()) { - // auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); + auto status = m_model->value(row, COL_STATUS).toString(); + auto status_message = m_model->value(row, COL_STATUS_MESSAGE).toString(); auto change_id = m_model->value(row, COL_ID).toInt(); - LateEntryDialog dlg(change_id, qxdata.lateEntry.value(), status, status_message, this); + auto stage_id = m_model->value(row, COL_STAGE_ID).toInt(); + LateEntryDialog dlg(change_id, stage_id, qxdata.lateEntry.value(), status, status_message, this); dlg.exec(); ui->tableView->reloadRow(row); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index fe2150e97..b85350c36 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -25,6 +25,7 @@ LateEntry LateEntry::fromVariantMap(const QVariantMap &map) if (auto v = map.value("firstname"); v.isValid()) { ret.first_name = v.toString(); } if (auto v = map.value("lastname"); v.isValid()) { ret.last_name = v.toString(); } if (auto v = map.value("registration"); v.isValid()) { ret.registration = v.toString(); } + if (auto v = map.value("starttimems"); v.isValid()) { ret.start_time_ms = v.toInt(); } if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } if (auto v = map.value("note"); v.isValid()) { ret.note = v.toString(); } @@ -39,6 +40,7 @@ QVariantMap OrigRunRecord::toVariantMap() const ret["lastname"] = last_name; ret["registration"] = registration; ret["siid"] = si_id; + ret["starttimems"] = start_time_ms; return ret; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 58f0371a7..62e9e78d4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -23,6 +23,7 @@ struct LateEntry std::optional last_name; std::optional registration; std::optional si_id; + std::optional start_time_ms; // std::optional si_id_rent; std::optional note; @@ -42,7 +43,8 @@ struct OrigRunRecord QString first_name; QString last_name; QString registration; - int si_id; + int start_time_ms = 0; + int si_id = 0; // bool si_id_rent; QVariantMap toVariantMap() const; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp index 19533c62b..6f60be93f 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp @@ -1230,6 +1230,18 @@ void RunsWidget::editCompetitor_helper(const QVariant &id, int mode, int siid) } } +void RunsWidget::showEditCompetitorDialog(int competitor_id, QWidget *parent) +{ + auto *w = new CompetitorWidget(); + w->setWindowTitle(tr("Edit Competitor")); + qfd::Dialog dlg(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, parent); + dlg.setDefaultButton(QDialogButtonBox::Ok); + + dlg.setCentralWidget(w); + w->load(competitor_id, qf::gui::model::DataDocument::ModeEdit); + dlg.exec(); +} + void RunsWidget::updateClassComboBox() { m_cbxClasses->blockSignals(true); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index e2efbb970..df6e7fd9b 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -51,6 +51,7 @@ class RunsWidget : public QFrame void export_results_stage_csv(); public: + static void showEditCompetitorDialog(int competitor_id, QWidget *parent); void editCompetitor(const QVariant &id, int mode) {editCompetitor_helper(id, mode, 0);} void editCompetitors(int mode); void editCompetitorOnPunch(int siid); From 5febecf146e9877a233445f61bd776cb9421de00 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 7 Jul 2026 00:31:49 +0200 Subject: [PATCH 41/52] Add duplicate checks, SHV UI and misc fixes LateEntryDialog: handle empty start time, add duplicate-registration and duplicate-name checks and call them on init; reset start-time style when group is unchecked. QxEventService: extract eventctl SHV path into method and use it. QxEventServiceWidget/UI: add SHV broker group with default-url radios, only set connection options when exporting DB, add helper to set URL. RunsTableModel: ignore Insert rec changes to avoid applying filtered inserts before they are relevant. --- .../Event/src/services/qx/lateentrydialog.cpp | 46 ++++++- .../Event/src/services/qx/lateentrydialog.h | 2 + .../Event/src/services/qx/qxeventservice.cpp | 7 +- .../Event/src/services/qx/qxeventservice.h | 1 + .../src/services/qx/qxeventservicewidget.cpp | 41 ++++++- .../src/services/qx/qxeventservicewidget.h | 1 + .../src/services/qx/qxeventservicewidget.ui | 116 +++++++++++------- .../plugins/Runs/src/runstablemodel.cpp | 4 + 8 files changed, 169 insertions(+), 49 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 7b9244214..5be8525ef 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -109,7 +109,7 @@ LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &l update_background(ui->edSiCard); ui->grpStartTime->setChecked(late_entry.start_time_ms.has_value()); - ui->edStartTime->setText(TimeMs(late_entry.start_time_ms.value_or(0)).toString()); + ui->edStartTime->setText(late_entry.start_time_ms.has_value()? TimeMs(late_entry.start_time_ms.value()).toString(): QString{}); update_background(ui->edStartTime); connect(ui->edStartTime, &QLineEdit::textEdited, this, &LateEntryDialog::checkStartTimeIsValid); @@ -124,6 +124,8 @@ LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &l auto class_id = id->id; loadClassName(class_id); } + checkDuplicitRegistration(); + checkDuplicitName(); lockChange(); } @@ -308,8 +310,50 @@ void LateEntryDialog::updateQxChangeMessage() qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } +void LateEntryDialog::checkDuplicitRegistration() +{ + if (!ui->grpRegistration->isChecked() || !classId().has_value()) { + return; + } + auto reg = ui->edRegistration->text().trimmed().toUpper(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT firstName, lastName FROM competitors WHERE registration='%1'") + .arg(reg) ); + if (q.next()) { + setMessage(tr("Competitor %1 %2 %3 is registered already.") + .arg(q.value("firstName").toString()) + .arg(q.value("lastName").toString()) + .arg(reg) + , true); + } +} + +void LateEntryDialog::checkDuplicitName() +{ + if (!ui->grpFirstName->isChecked() || !ui->grpLastName->isChecked() || !classId().has_value()) { + return; + } + auto first_name = ui->edFirstName->text().trimmed(); + auto last_name = ui->edLastName->text().trimmed(); + qf::core::sql::Query q; + q.execThrow(QStringLiteral("SELECT registration FROM competitors WHERE firstName='%1' AND lastName='%2'") + .arg(first_name) + .arg(last_name) ); + if (q.next()) { + setMessage(tr("Competitor %1 %2 %3 is registered already.") + .arg(first_name) + .arg(last_name) + .arg(q.value("registration").toString()) + , true); + } +} + void LateEntryDialog::checkStartTimeIsValid() { + if (!ui->grpStartTime->isChecked()) { + ui->edStartTime->setStyleSheet({}); + return; + } if (auto run_id = runId(); run_id.has_value()) { auto stime = TimeMs::fromString(ui->edStartTime->text()); if (stime.isValid()) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 909cebfd2..82fc59261 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -43,6 +43,8 @@ class LateEntryDialog : public QDialog void resolveChangesAndClose(bool is_accepted); void updateQxChangeMessage(); + void checkDuplicitRegistration(); + void checkDuplicitName(); void checkStartTimeIsValid(); private: Ui::LateEntryDialog *ui; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index fb27d5912..45c65a25c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -470,6 +470,11 @@ int QxEventService::currentConnectionId() return qf::core::sql::Connection::forName().connectionId(); } +const char *QxEventService::eventctlShvPath() const +{ + return "qx/qxeventd/eventctl"; +} + void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { @@ -478,7 +483,7 @@ void QxEventService::onBrokerConnectedChanged(bool is_connected) auto stage_data = event_plugin->stageData(current_stage); auto api_token = stage_data.qxApiToken().toStdString(); auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) - ->setShvPath("test/qx/qxeventd/eventctl") + ->setShvPath(eventctlShvPath()) ->setMethod("openEventApiKey") ->setParams(RpcValue(api_token)); connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 0e0ec5509..ca1b93e08 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -81,6 +81,7 @@ class QxEventService : public Service void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); + const char* eventctlShvPath() const; void subscribeChanges(); private: void loadSettings() override; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index e6ba839e5..63b2ff223 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -47,6 +47,22 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportRuns); + +#ifdef QT_DEBUG + ui->grpShvConnection->setVisible(true); +#else + ui->grpShvConnection->setVisible(false); +#endif + connect(ui->btQxQxOrg, &QAbstractButton::clicked, this, [this](bool checked) { + if (checked) { + setDefaultShvBrokerUrl(false); + } + }); + connect(ui->btLocalhost, &QAbstractButton::clicked, this, [this](bool checked) { + if (checked) { + setDefaultShvBrokerUrl(true); + } + }); } QxEventServiceWidget::~QxEventServiceWidget() @@ -119,11 +135,13 @@ void QxEventServiceWidget::testConnection() auto *rpc = new DeviceConnection("QuickEventTest", this); rpc->setConnectionString(ui->edServerUrl->text()); - RpcValue::Map opts; - RpcValue::Map device; - device["deviceId"] = ui->edApiToken->text().toStdString(); - opts["device"] = device; - rpc->setConnectionOptions(opts); + if (ui->chkExportDatabase->isChecked()) { + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = ui->edApiToken->text().toStdString(); + opts["device"] = device; + rpc->setConnectionOptions(opts); + } connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { if (is_connected) { @@ -207,5 +225,18 @@ void QxEventServiceWidget::exportRuns() }); } +void QxEventServiceWidget::setDefaultShvBrokerUrl(bool is_local_host) +{ + auto url = QStringLiteral("tcp://%1?user=qe&%2=%3"); + if (is_local_host) { + url = url.arg("localhost"); + } else { + url = url.arg("qxqx.org"); + } + url = url.arg("password"); + url = url.arg("2ddf6394ea6"); + ui->edServerUrl->setText(url); +} + } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index 7e4b3de21..1bde76efc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -29,6 +29,7 @@ class QxEventServiceWidget : public qf::gui::framework::DialogWidget void exportEventInfo(); void exportStartList(); void exportRuns(); + void setDefaultShvBrokerUrl(bool is_local_host); private: Ui::QxEventServiceWidget *ui; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index 590077127..d61295c58 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -14,36 +14,62 @@ SHV API service + + + + SHV broker connection + + + + + + + 0 + 0 + + + + Url + + + + + + + localhost + + + false + + + + + + + qxqx.org + + + true + + + + + + + + + - - - - Fill this url into HTTP POST synchonization method in O-CheckList - - - Event ID - - - - - - - - - - - - - Fill this url into HTTP POST synchonization method in O-CheckList - + + - Current stage + Export database - - + + false @@ -53,20 +79,29 @@ true - - 1 + + Not loaded + + + 0 - - + + + + Fill this url into HTTP POST synchonization method in O-CheckList + - SHV broker url + Event ID - - + + + + + false @@ -76,11 +111,8 @@ true - - Not loaded - - - 0 + + 1 @@ -94,10 +126,13 @@ - - + + + + Fill this url into HTTP POST synchonization method in O-CheckList + - Export database + Current stage @@ -182,9 +217,6 @@ - - edServerUrl - diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index bac275c69..b6409de8f 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -363,6 +363,10 @@ void RunsTableModel::onDataChanged(const QModelIndex &top_left, const QModelInde void RunsTableModel::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { + if (recchng.op == qf::core::sql::RecOp::Insert) { + // inserts might be in filterred out class or stage, this cannot be solved by applyQxRecChng + return; + } applyQxRecChng(recchng, source); } From e12290e992af4c82df183228b01a8b8c021c2f14 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 7 Jul 2026 17:01:44 +0200 Subject: [PATCH 42/52] Delete testing DeviceConnection on dialog accept --- .../plugins/Event/src/services/qx/qxeventservicewidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 63b2ff223..8c87f2f27 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -93,6 +93,8 @@ void QxEventServiceWidget::setMessage(const QString &msg, MessageType msg_type) bool QxEventServiceWidget::acceptDialogDone(int result) { + // delete testing connection if any + delete findChild(); if(result == QDialog::Accepted) { if(!saveSettings()) { return false; From f6f5e83c9df88e6d33ffc5ef708ad9083d6118f6 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 7 Jul 2026 17:13:23 +0200 Subject: [PATCH 43/52] Fix clazy errors --- libqf/libqfgui/src/model/sqltablemodel.cpp | 11 +++++------ .../plugins/Event/src/services/ofeed/ofeedclient.cpp | 12 ++++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index e61a1e0d9..e2d3d25dc 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -227,15 +227,14 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) QF_ASSERT(num_rows_affected == 1, tr("numRowsAffected() = %1, should be 1\n%2").arg(num_rows_affected).arg(qs), return false); - auto insert_id = q.lastInsertId(); if(serial_ix >= 0 && !serial_ix_explicitly_set) { - QVariant v = q.lastInsertId(); - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; - if(v.isValid()) { - row_ref.setValue(serial_ix, v); + auto insert_id = q.lastInsertId(); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; + if(insert_id.isValid()) { + row_ref.setValue(serial_ix, insert_id); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { - app->qxSql()->emitRecInserted(table_id, v.value(), record_to_map(rec), this); + app->qxSql()->emitRecInserted(table_id, insert_id.value(), record_to_map(rec), this); } } else { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp index 74c1398c6..3fe2f6dec 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp @@ -304,7 +304,7 @@ void OFeedClient::onDbEventNotify(const QString &domain, int connection_id, cons auto lst = data.toList(); int run_id = lst.value(0).toInt(); auto dirty_vals = lst.value(1).toMap(); - qfInfo() << serviceName().toStdString() + " DB event RUN CHANGED received, run_id: " << run_id << ", dirty keys: " << dirty_vals.keys().join(", "); + qfInfo() << serviceName().toStdString() + " DB event RUN CHANGED received, run_id: " << run_id << ", dirty keys: " << dirty_vals.keys().join(", "); // clazy:exclude=container-anti-pattern if (!dirty_vals.isEmpty()) { static const QSet relevant_fields = { // Run fields (finishTimeMs and timeMs are covered by DBEVENT_CARD_PROCESSED_AND_ASSIGNED) @@ -319,7 +319,7 @@ void OFeedClient::onDbEventNotify(const QString &domain, int connection_id, cons QStringLiteral("classid"), }; bool has_relevant = false; - for (const auto &key : dirty_vals.keys()) { + for (const auto &[key, _] : dirty_vals.asKeyValueRange()) { if (relevant_fields.contains(key.section('.', -1).toLower())) { has_relevant = true; break; @@ -758,7 +758,7 @@ void OFeedClient::testConnection(const QString &host_url, const QByteArray response_bytes = reply->readAll(); const QString response_text = QString::fromUtf8(response_bytes).trimmed(); - auto callback_error = [&](const QString &msg) { + auto callback_error = [=](const QString &msg) { const QString status_text = http_status > 0 ? QStringLiteral("HTTP %1").arg(http_status) : QStringLiteral("HTTP status unavailable"); const QString full_message = response_text.isEmpty() ? QStringLiteral("%1: %2").arg(status_text, msg) @@ -1522,7 +1522,7 @@ void OFeedClient::processNewRunner(int ofeed_competitor_id) // Save emits db event doc.save(); - auto competitor_id = doc.value("competitors.id"); + // auto competitor_id = doc.value("competitors.id"); unused // Get runs.id for current stage int current_stage = getPlugin()->currentStageId(); @@ -1563,8 +1563,8 @@ void OFeedClient::storeChange(const QJsonObject &change) QString firstname = competitor["firstname"].isString() ? competitor["firstname"].toString() : no_data; QString lastname = competitor["lastname"].isString() ? competitor["lastname"].toString() : no_data; - QJsonDocument change_json_doc(change); - QString change_json = QString::fromUtf8(change_json_doc.toJson(QJsonDocument::Compact)); + // QJsonDocument change_json_doc(change); unused + // QString change_json = QString::fromUtf8(change_json_doc.toJson(QJsonDocument::Compact)); int change_id = change["id"].toInt(); auto created = QDateTime::fromString(change["createdAt"].toString(), Qt::ISODate); From 716d6a85f18415fd12ebbee509e14813f02d180e Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 7 Jul 2026 22:50:10 +0200 Subject: [PATCH 44/52] Delete RPC connection on stop; guard debug connect --- .../quickevent/plugins/Event/src/services/qx/qxeventservice.cpp | 2 ++ quickevent/app/quickevent/src/main.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 45c65a25c..61ec5d71c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -103,6 +103,8 @@ void QxEventService::run() { void QxEventService::stop() { + delete m_rpcConnection; + m_rpcConnection = nullptr; m_eventId = 0; if (m_pollChangesTimer) { m_pollChangesTimer->stop(); diff --git a/quickevent/app/quickevent/src/main.cpp b/quickevent/app/quickevent/src/main.cpp index 742e5efcc..04d873e64 100644 --- a/quickevent/app/quickevent/src/main.cpp +++ b/quickevent/app/quickevent/src/main.cpp @@ -198,6 +198,7 @@ int main(int argc, char *argv[]) main_window.show(); emit main_window.applicationLaunched(); +#ifdef QT_DEBUG QObject::connect(app.qxSql(), &qf::core::sql::QxSql::recChng, &app, [](const qf::core::sql::QxRecChng &recchng) { auto dump_map = [](const QVariantMap &m) { QStringList rows; @@ -211,6 +212,7 @@ int main(int argc, char *argv[]) << "id:" << recchng.id << "record:" << dump_map(recchng.record); }); +#endif int ret = Application::exec(); From 7fadeaf000116da6e4a80642d853820df65d45fe Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 8 Jul 2026 20:36:52 +0200 Subject: [PATCH 45/52] Support paid late entries and status handling Add 'paid' to LateEntry and parse it from change data. Show paid status in LateEntryDialog (lblPaid) and color rows in the late entries table based on paid/status. Replace local LateEntryStatus with QxChangeStatus and provide qxChangeStatusFromString for unified parsing. Introduce changeEventEntryToStageEntry and m_setIsRunning to update an existing competitor run instead of inserting duplicates. --- .../Event/src/services/qx/lateentrydialog.cpp | 75 +++++++++++-------- .../Event/src/services/qx/lateentrydialog.h | 6 +- .../Event/src/services/qx/lateentrydialog.ui | 23 +++++- .../src/services/qx/qxlateentrieswidget.cpp | 36 ++++++++- .../Event/src/services/qx/runchange.cpp | 18 +++++ .../plugins/Event/src/services/qx/runchange.h | 8 +- 6 files changed, 125 insertions(+), 41 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 5be8525ef..93579c22b 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -20,38 +20,20 @@ #include #include #include + #include using namespace quickevent::core::og; namespace Event::services::qx { -namespace { - -LateEntryStatus lateEntryStatusFromString(const QString &status) -{ - if (status.compare(QStringLiteral("Pending"), Qt::CaseInsensitive) == 0) { - return LateEntryStatus::Pending; - } - if (status.compare(QStringLiteral("Accepted"), Qt::CaseInsensitive) == 0) { - return LateEntryStatus::Accepted; - } - if (status.compare(QStringLiteral("Rejected"), Qt::CaseInsensitive) == 0) { - return LateEntryStatus::Rejected; - } - qfWarning() << "Unknown late entry status:" << status; - return LateEntryStatus::Rejected; -} - -} // namespace - LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent) : Super(parent) , ui(new Ui::LateEntryDialog) , m_changeId(change_id) , m_stageId(stage_id) , m_lateEntryId(late_entry.id) - , m_status(lateEntryStatusFromString(status)) + , m_status(qxChangeStatusFromString(status)) { Q_ASSERT(change_id > 0); @@ -124,6 +106,15 @@ LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &l auto class_id = id->id; loadClassName(class_id); } + + if (late_entry.paid.value_or(false)) { + ui->lblPaid->setText(tr("Paid")); + ui->lblPaid->setStyleSheet("background:green;color:white"); + } else { + ui->lblPaid->setText(tr("Not Paid")); + ui->lblPaid->setStyleSheet("background:red;color:white"); + } + checkDuplicitRegistration(); checkDuplicitName(); lockChange(); @@ -236,8 +227,8 @@ void LateEntryDialog::lockChange() void LateEntryDialog::updateButtonsEnabled() { - ui->btAccept->setEnabled(m_status == LateEntryStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); - ui->btReject->setEnabled(m_status == LateEntryStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); + ui->btAccept->setEnabled(m_status == QxChangeStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); + ui->btReject->setEnabled(m_status == QxChangeStatus::Pending && (ui->chkForce->isChecked() || m_lockNumber > 0)); } void LateEntryDialog::resolveChangesAndClose(bool is_accepted) @@ -246,7 +237,7 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) if (is_accepted) { auto class_id = classId(); - bool is_insert = class_id.has_value(); + bool is_insert = class_id.has_value() && !m_setIsRunning; Competitors::CompetitorDocument doc; doc.load(m_competitorId, is_insert? DataDocument::ModeInsert: DataDocument::ModeEdit); if (is_insert) { @@ -275,6 +266,9 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) if (ui->grpStartTime->isChecked()) { rec["starttimems"] = TimeMs::fromString(ui->edStartTime->text()).msec(); } + if (m_setIsRunning) { + rec["isRunning"] = true; + } if (!rec.isEmpty()) { auto competitor_id = doc.value("id").toInt(); Q_ASSERT(competitor_id > 0); @@ -312,42 +306,61 @@ void LateEntryDialog::updateQxChangeMessage() void LateEntryDialog::checkDuplicitRegistration() { - if (!ui->grpRegistration->isChecked() || !classId().has_value()) { + auto class_id = classId(); + if (!ui->grpRegistration->isChecked() || !class_id.has_value()) { return; } auto reg = ui->edRegistration->text().trimmed().toUpper(); qf::core::sql::Query q; - q.execThrow(QStringLiteral("SELECT firstName, lastName FROM competitors WHERE registration='%1'") - .arg(reg) ); + q.execThrow(QStringLiteral("SELECT id, firstName, lastName FROM competitors WHERE registration='%1' AND classId=%2") + .arg(reg).arg(class_id.value()) ); if (q.next()) { - setMessage(tr("Competitor %1 %2 %3 is registered already.") + setMessage(tr("Competitor %1 %2 %3 is registered in this class already, run will be updated.") .arg(q.value("firstName").toString()) .arg(q.value("lastName").toString()) .arg(reg) , true); + auto competitor_id = q.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + changeEventEntryToStageEntry(competitor_id); } } void LateEntryDialog::checkDuplicitName() { - if (!ui->grpFirstName->isChecked() || !ui->grpLastName->isChecked() || !classId().has_value()) { + auto class_id = classId(); + if (!ui->grpFirstName->isChecked() || !ui->grpLastName->isChecked() || !class_id.has_value()) { return; } auto first_name = ui->edFirstName->text().trimmed(); auto last_name = ui->edLastName->text().trimmed(); qf::core::sql::Query q; - q.execThrow(QStringLiteral("SELECT registration FROM competitors WHERE firstName='%1' AND lastName='%2'") + q.execThrow(QStringLiteral("SELECT id, registration FROM competitors WHERE firstName='%1' AND lastName='%2' AND classId=%3") .arg(first_name) - .arg(last_name) ); + .arg(last_name) + .arg(class_id.value())); if (q.next()) { - setMessage(tr("Competitor %1 %2 %3 is registered already.") + setMessage(tr("Competitor %1 %2 %3 is registered in this class already, run will be updated.") .arg(first_name) .arg(last_name) .arg(q.value("registration").toString()) , true); + auto competitor_id = q.value("id").toInt(); + Q_ASSERT(competitor_id > 0); + changeEventEntryToStageEntry(competitor_id); } } +void LateEntryDialog::changeEventEntryToStageEntry(int competitor_id) +{ + m_setIsRunning = true; + m_competitorId = competitor_id; + + ui->grpFirstName->setChecked(false); + ui->grpLastName->setChecked(false); + ui->grpRegistration->setChecked(false); +} + void LateEntryDialog::checkStartTimeIsValid() { if (!ui->grpStartTime->isChecked()) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 82fc59261..088a2ec39 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -16,8 +16,6 @@ class LateEntryDialog; struct LateEntry; class QxEventService; -enum class LateEntryStatus {Pending, Accepted, Rejected}; - class LateEntryDialog : public QDialog { Q_OBJECT @@ -46,6 +44,7 @@ class LateEntryDialog : public QDialog void checkDuplicitRegistration(); void checkDuplicitName(); void checkStartTimeIsValid(); + void changeEventEntryToStageEntry(int competitor_id); private: Ui::LateEntryDialog *ui; int m_changeId = 0; @@ -53,8 +52,9 @@ class LateEntryDialog : public QDialog LateEntryForeignId m_lateEntryId = RunId{}; int m_competitorId = 0; int m_lockNumber = 0; - LateEntryStatus m_status = LateEntryStatus::Rejected; + QxChangeStatus m_status = QxChangeStatus::Rejected; OrigRunRecord m_origValues; + bool m_setIsRunning = false; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index 73d119d72..809a8a08a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -7,7 +7,7 @@ 0 0 712 - 668 + 696 @@ -370,6 +370,25 @@ + + + + + 0 + 0 + + + + QFrame::Shape::Box + + + Not Paid + + + Qt::AlignmentFlag::AlignCenter + + + @@ -386,7 +405,7 @@ - 40 + 0 20 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index f6e408ef6..35a6751ee 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include namespace qfm = qf::gui::model; @@ -56,6 +58,36 @@ constexpr auto COL_STATUS_MESSAGE = "status_message"; constexpr auto COL_USER_ID = "user_id"; constexpr auto COL_CREATED = "created"; constexpr auto COL_LOCK_NUMBER = "lock_number"; + +class LateEntriesModel : public qfm::SqlTableModel +{ +public: + using qfm::SqlTableModel::SqlTableModel; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (role == Qt::BackgroundRole) { + if (index.column() == col_data) { + auto data = index.data().toString(); + auto qxdata = QxChangeData::fromJson(data); + if (auto le = qxdata.lateEntry; le.has_value()) { + if (le.value().paid.value_or(false)) { + return QColor("lightgreen"); + } + } + } + else if (index.column() == col_status) { + auto status = qxChangeStatusFromString(index.data().toString()); + switch (status) { + case QxChangeStatus::Pending: return {}; + case QxChangeStatus::Accepted: return QColor("lightgreen"); + case QxChangeStatus::Rejected: return QColor("salmon"); + } + } + } + return qfm::SqlTableModel::data(index, role); + } +}; } // namespace QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : @@ -76,7 +108,7 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : ui->tableView->setDirtyRowsMenuSectionEnabled(false); ui->toolbar->setTableView(ui->tableView); - m_model = new qfm::SqlTableModel(this); + m_model = new LateEntriesModel(this); //m->setObjectName("classes.classesModel"); m_model->clearColumns(col_COUNT); @@ -277,7 +309,7 @@ void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { - auto row = ix.row(); + auto row = ui->tableView->toTableModelIndex(ix).row(); auto data = m_model->value(row, COL_DATA).toString(); auto qxdata = QxChangeData::fromJson(data); if (qxdata.lateEntry.has_value()) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index b85350c36..8e4cdd5c2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -1,5 +1,7 @@ #include "runchange.h" +#include + #include #include @@ -29,6 +31,7 @@ LateEntry LateEntry::fromVariantMap(const QVariantMap &map) if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } if (auto v = map.value("note"); v.isValid()) { ret.note = v.toString(); } + if (auto v = map.value("paid"); v.isValid()) { ret.paid = v.toBool(); } return ret; } @@ -54,4 +57,19 @@ QxChangeData QxChangeData::fromJson(const QString &json) return ret; } +QxChangeStatus qxChangeStatusFromString(const QString &status) +{ + if (status.compare(QStringLiteral("Pending"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Pending; + } + if (status.compare(QStringLiteral("Accepted"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Accepted; + } + if (status.compare(QStringLiteral("Rejected"), Qt::CaseInsensitive) == 0) { + return QxChangeStatus::Rejected; + } + qfWarning() << "Unknown late entry status:" << status; + return QxChangeStatus::Rejected; +} + } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 62e9e78d4..37d576f8d 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -26,6 +26,7 @@ struct LateEntry std::optional start_time_ms; // std::optional si_id_rent; std::optional note; + std::optional paid; // static LateEntry fromJsonString(const QString &json); static LateEntry fromVariantMap(const QVariantMap &map); @@ -57,12 +58,13 @@ enum class DataType { RadioPunch, CardReadout, }; -enum class ChangeStatus { +enum class QxChangeStatus { Pending, - Locked, Accepted, Rejected, }; +QxChangeStatus qxChangeStatusFromString(const QString &status); + struct EventChange { int64_t id; @@ -71,7 +73,7 @@ struct EventChange int64_t data_id; QVariant data; QString user_id; - ChangeStatus status; + QxChangeStatus status; QString status_message; QDateTime created; int64_t lock_number; From 213a87303b1a712eb98a1555dfaff695cc7af4a5 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Jul 2026 00:47:36 +0200 Subject: [PATCH 46/52] Disable proxy sorting during batch cell updates, TableView::setValueInSelection --- libqf/libqfgui/src/tableview.cpp | 49 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/libqf/libqfgui/src/tableview.cpp b/libqf/libqfgui/src/tableview.cpp index 7f935dc21..17e40e82a 100644 --- a/libqf/libqfgui/src/tableview.cpp +++ b/libqf/libqfgui/src/tableview.cpp @@ -633,40 +633,37 @@ void TableView::paste() void TableView::setValueInSelection_helper(const QVariant &new_val) { + auto *sql_m = qobject_cast(tableModel()); + if (!sql_m) { + return; + } QModelIndexList lst = selectedIndexes(); QMap row_selections; foreach(const QModelIndex &ix, lst) { row_selections[ix.row()] << ix; } - QList selected_row_indexes = row_selections.keys(); - if(selected_row_indexes.count() == 1) { - int row_ix = selected_row_indexes.value(0); - foreach(const QModelIndex &ix, row_selections.value(row_ix)) { - model()->setData(ix, new_val); - } - } - else if(selected_row_indexes.count() > 1) { - qfc::sql::Connection conn; - auto *sql_m = qobject_cast(tableModel()); - if(sql_m) { - try { - conn = sql_m->sqlConnection(); - qfc::sql::Transaction transaction(conn); - QF_TIME_SCOPE(QString("Saving %1 rows").arg(selected_row_indexes.count())); - foreach(int row_ix, selected_row_indexes) { - foreach(const QModelIndex &ix, row_selections.value(row_ix)) { - model()->setData(ix, new_val); - } - sql_m->postRow(toTableModelRowNo(row_ix), qf::core::Exception::Throw); - } - transaction.commit(); - } - catch(qfc::Exception &e) { - dialogs::MessageBox::showException(this, e); + auto *proxy_m = qobject_cast(lastProxyModel()); + Q_ASSERT(proxy_m); + auto is_dynamic_sort_filter = proxy_m->dynamicSortFilter(); + proxy_m->setDynamicSortFilter(false); + qfc::sql::Connection conn; + try { + conn = sql_m->sqlConnection(); + qfc::sql::Transaction transaction(conn); + QF_TIME_SCOPE(QString("Saving %1 rows").arg(selected_row_indexes.count())); + for(const auto &[row_ix, row_indexes] : row_selections.asKeyValueRange()) { + for(const auto &ix : row_indexes) { + proxy_m->setData(ix, new_val); } + sql_m->postRow(toTableModelRowNo(row_ix), qf::core::Exception::Throw); } - update(); + transaction.commit(); + } + catch(qfc::Exception &e) { + dialogs::MessageBox::showException(this, e); } + proxy_m->setDynamicSortFilter(is_dynamic_sort_filter); + update(); } void TableView::setValueInSelection() From 2a19e634f17b2cd0e5a872a65816dfe8bc2059f5 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Jul 2026 22:45:20 +0200 Subject: [PATCH 47/52] Add Accept and Edit option to LateEntryDialog Emit editCompetitor when user chooses Accept and Edit; save the competitor id after resolving changes; connect the dialog to open the competitor edit dialog from QxLateEntriesWidget. Comment out unused EventChange struct in runchange.h. --- .../Event/src/services/qx/lateentrydialog.cpp | 7 ++++- .../Event/src/services/qx/lateentrydialog.h | 2 ++ .../Event/src/services/qx/lateentrydialog.ui | 7 +++++ .../src/services/qx/qxlateentrieswidget.cpp | 3 ++ .../plugins/Event/src/services/qx/runchange.h | 30 +++++++++---------- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index 93579c22b..b21873fd7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -51,6 +51,11 @@ LateEntryDialog::LateEntryDialog(int change_id, int stage_id, const LateEntry &l resolveChangesAndClose(true); done(QDialog::Accepted); }); + connect(ui->btAcceptAndEdit, &QPushButton::clicked, this, [this]() { + resolveChangesAndClose(true); + emit editCompetitor(m_competitorId); + done(QDialog::Accepted); + }); connect(ui->btReject, &QPushButton::clicked, this, [this]() { resolveChangesAndClose(false); done(QDialog::Accepted); @@ -258,7 +263,7 @@ void LateEntryDialog::resolveChangesAndClose(bool is_accepted) } } doc.save(); - + m_competitorId = doc.dataId().toInt(); qf::core::sql::Record rec; if (ui->grpSiCard->isChecked()) { rec["siId"] = ui->edSiCard->value(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 088a2ec39..fee286b76 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -24,6 +24,8 @@ class LateEntryDialog : public QDialog explicit LateEntryDialog(int change_id, int stage_id, const LateEntry &late_entry, const QString &status, const QString &status_message, QWidget *parent = nullptr); ~LateEntryDialog() override; + Q_SIGNAL void editCompetitor(int competitor_id); + void done(int result) override; private: std::optional runId() const; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index 809a8a08a..6e2cee6ef 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -432,6 +432,13 @@ + + + + Accept and Edit + + + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 35a6751ee..49b59528e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -318,6 +318,9 @@ void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) auto change_id = m_model->value(row, COL_ID).toInt(); auto stage_id = m_model->value(row, COL_STAGE_ID).toInt(); LateEntryDialog dlg(change_id, stage_id, qxdata.lateEntry.value(), status, status_message, this); + connect(&dlg, &LateEntryDialog::editCompetitor, this, [this](int competitor_id) { + RunsWidget::showEditCompetitorDialog(competitor_id, this); + }, Qt::QueuedConnection); dlg.exec(); ui->tableView->reloadRow(row); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 37d576f8d..6fe5aef7a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -65,21 +65,21 @@ enum class QxChangeStatus { }; QxChangeStatus qxChangeStatusFromString(const QString &status); -struct EventChange -{ - int64_t id; - QString source; - DataType data_type; - int64_t data_id; - QVariant data; - QString user_id; - QxChangeStatus status; - QString status_message; - QDateTime created; - int64_t lock_number; - - QVariantMap toVariantMap() const; -}; +// struct EventChange +// { +// int64_t id; +// QString source; +// DataType data_type; +// int64_t data_id; +// QVariant data; +// QString user_id; +// QxChangeStatus status; +// QString status_message; +// QDateTime created; +// int64_t lock_number; + +// QVariantMap toVariantMap() const; +// }; // struct ChangeRecord // { From af629417d5567ffcb9b60857d0e2883611a87d5c Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 10 Jul 2026 12:07:19 +0200 Subject: [PATCH 48/52] Show qx connection params always --- .../Event/src/services/qx/qxeventservicewidget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 8c87f2f27..0a3f57225 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -48,11 +48,11 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportRuns); -#ifdef QT_DEBUG +// #ifdef QT_DEBUG ui->grpShvConnection->setVisible(true); -#else - ui->grpShvConnection->setVisible(false); -#endif +// #else +// ui->grpShvConnection->setVisible(false); +// #endif connect(ui->btQxQxOrg, &QAbstractButton::clicked, this, [this](bool checked) { if (checked) { setDefaultShvBrokerUrl(false); From b901d87b9e83cd13276f46c086f1644cbe112a39 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 10 Jul 2026 14:09:27 +0200 Subject: [PATCH 49/52] Fix runners per courses count --- .../app/quickevent/plugins/Classes/src/editcourseswidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp b/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp index 05d6ebe35..98cbc1029 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/editcourseswidget.cpp @@ -151,7 +151,7 @@ void EditCoursesWidget::updateQuery() .join("courses.id", "classdefs.courseId", qf::core::sql::QueryBuilder::INNER_JOIN) .join("classdefs.classId", "classes.id") .join("classes.id", "competitors.classId") - .joinRestricted("competitors.id", "runs.competitorId", "runs.isRunning") + .joinRestricted("competitors.id", "runs.competitorId", "runs.isRunning AND runs.stageId=" QF_IARG(m_stageId)) .where("classdefs.stageId=" QF_IARG(m_stageId)) .groupBy("courses.id") .orderBy("courses.name"); From 43df156cc809638baaa2b3097cf210e8138efbc5 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 10 Jul 2026 14:10:24 +0200 Subject: [PATCH 50/52] Disable db event on card read in sake of QxRecChng --- .../app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 45c6db000..554bc6f9c 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -722,7 +722,7 @@ void CardReaderWidget::processReadCard(const quickevent::core::si::ReadCard &rea if(card_id) { /// receipts printer needs this /// emitDbEvent is using queued invocation - getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_CARD_READ, card_id, true); + // getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_CARD_READ, card_id, true); } } From 02550b5ee5732381e27f50081bc4d34a781df399 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 10 Jul 2026 15:36:56 +0200 Subject: [PATCH 51/52] Fix receipts autoprint --- .../CardReader/src/cardreaderwidget.cpp | 24 +++++++++---------- .../plugins/CardReader/src/cardreaderwidget.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 554bc6f9c..c5e9f37b0 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -476,7 +476,7 @@ void CardReaderWidget::settleDownInPartWidget(::PartWidget *part_widget) a_tools->addActionInto(a); } } - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &CardReaderWidget::onDbEventNotify, Qt::QueuedConnection); + // connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &CardReaderWidget::onDbEventNotify, Qt::QueuedConnection); } void CardReaderWidget::reset() @@ -526,16 +526,16 @@ void CardReaderWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObj } } -void CardReaderWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) -{ - Q_UNUSED(connection_id) - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_CARD_READ)) { - int card_id = data.toInt(); - if(isVisible()) { - updateTableView(card_id); - } - } -} +// void CardReaderWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) +// { +// Q_UNUSED(connection_id) +// if(domain == QLatin1String(Event::EventPlugin::DBEVENT_CARD_READ)) { +// int card_id = data.toInt(); +// if(isVisible()) { +// updateTableView(card_id); +// } +// } +// } void CardReaderWidget::createActions() { @@ -722,7 +722,7 @@ void CardReaderWidget::processReadCard(const quickevent::core::si::ReadCard &rea if(card_id) { /// receipts printer needs this /// emitDbEvent is using queued invocation - // getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_CARD_READ, card_id, true); + getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_CARD_READ, card_id, true); } } diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h index 3e2f312b7..8911758d9 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h @@ -69,7 +69,7 @@ class CardReaderWidget : public QFrame private: void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); + // void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); void appendLog(NecroLog::Level level, const QString &msg); void processDriverInfo(NecroLog::Level level, const QString &msg); void logDriverRawData(const QByteArray &data); From 11bf6d159c6d2a1c57da8cf99d91ee5bd417ac18 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 14 Jul 2026 22:36:30 +0200 Subject: [PATCH 52/52] Migrate qxApiToken to EventConfig --- .../quickevent/plugins/Event/qml/DbSchema.qml | 4 +- .../plugins/Event/sql/create_db_psql.sql | 1 - .../plugins/Event/sql/create_db_sqlite.sql | 1 - .../plugins/Event/src/eventconfig.cpp | 13 +++ .../plugins/Event/src/eventconfig.h | 5 + .../Event/src/services/qx/qxeventservice.cpp | 7 +- .../Event/src/services/qx/qxeventservice.h | 3 +- .../src/services/qx/qxeventservicewidget.cpp | 109 +++++------------- .../src/services/qx/qxeventservicewidget.h | 6 +- .../src/services/qx/qxeventservicewidget.ui | 49 +++----- .../app/quickevent/plugins/Event/src/stage.h | 1 - 11 files changed, 70 insertions(+), 129 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index 77f00718c..dff9e5d07 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -40,8 +40,8 @@ Schema { defaultValue: false notNull: true }, - Field { name: 'drawingConfig'; type: String {} }, - Field { name: 'qxApiToken'; type: String {} } + Field { name: 'drawingConfig'; type: String {} } + // Field { name: 'qxApiToken'; type: String {} } ] indexes: [ Index {fields: ['id']; primary: true } diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index 2154be167..1b5767339 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -30,7 +30,6 @@ CREATE TABLE {{eventId}}.stages ( startDateTime timestamp with time zone, useAllMaps boolean NOT NULL DEFAULT false, drawingConfig character varying, - qxApiToken character varying, CONSTRAINT stages_pkey PRIMARY KEY (id) ); ; diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index 51aec9f21..640c65c58 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -29,7 +29,6 @@ CREATE TABLE stages ( startDateTime timestamp, useAllMaps boolean NOT NULL DEFAULT 0, drawingConfig character varying, - qxApiToken character varying, CONSTRAINT stages_pkey PRIMARY KEY (id) ); ; diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp index 0e3358385..75a583f92 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp @@ -275,6 +275,19 @@ QDateTime EventConfig::eventDateTime() const } return dt; } +namespace { +constexpr auto QX_API_TOKEN = "qx.apiToken"; +} +QString EventConfig::qxApiToken() const +{ + return value(QX_API_TOKEN).toString(); +} + +void EventConfig::setQxApiToken(const QString &at) +{ + setValue(QX_API_TOKEN, at); + save(QX_API_TOKEN); +} int EventConfig::dbVersion() const { diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h index a4d9c4ea5..d8f48cbaa 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h @@ -20,6 +20,7 @@ class EventConfig : public QObject Q_PROPERTY(bool iofXmlRaceNumber READ iofXmlRaceNumber) Q_PROPERTY(QString director READ director) Q_PROPERTY(QString mainReferee READ mainReferee) + Q_PROPERTY(QString qxApiToken READ qxApiToken) //Q_PROPERTY(QString eventName READ eventName) public: enum class Sport {OB = 1, LOB, MTBO, TRAIL}; @@ -72,6 +73,10 @@ class EventConfig : public QObject QString director() const; QString mainReferee() const; QDateTime eventDateTime() const; + + QString qxApiToken() const; + void setQxApiToken(const QString &at); + int dbVersion() const; std::optional maximumCardCheckAdvanceSec() const; bool isOneTenthSecResults() const; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 61ec5d71c..305f718cc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -284,11 +284,8 @@ int QxEventService::eventId() const QString QxEventService::apiToken() const { - // API token must not be cached to enable service to point - // always to current stage event on qxhttpd auto *event_plugin = getPlugin(); - auto current_stage = event_plugin->currentStageId(); - return event_plugin->stageData(current_stage).qxApiToken(); + return event_plugin->eventConfig()->qxApiToken(); } QUrl QxEventService::shvBrokerUrl() const @@ -483,7 +480,7 @@ void QxEventService::onBrokerConnectedChanged(bool is_connected) auto *event_plugin = getPlugin(); auto current_stage = event_plugin->currentStageId(); auto stage_data = event_plugin->stageData(current_stage); - auto api_token = stage_data.qxApiToken().toStdString(); + auto api_token = event_plugin->eventConfig()->qxApiToken().toStdString(); auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) ->setShvPath(eventctlShvPath()) ->setMethod("openEventApiKey") diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index ca1b93e08..e78831898 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -18,7 +18,8 @@ class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=test&password=test") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=qe&password=test") + QF_VARIANTMAP_FIELD2(bool, is, set, LocalBroker, false) QF_VARIANTMAP_FIELD2(bool, is, set, ExportDatabase, false) public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 0a3f57225..4d7fe6171 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -44,25 +44,10 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) ui->edCurrentStage->setValue(current_stage); ui->edEventId->setValue(svc->eventId()); connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); - connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); - connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); - connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportRuns); - -// #ifdef QT_DEBUG - ui->grpShvConnection->setVisible(true); -// #else -// ui->grpShvConnection->setVisible(false); -// #endif - connect(ui->btQxQxOrg, &QAbstractButton::clicked, this, [this](bool checked) { - if (checked) { - setDefaultShvBrokerUrl(false); - } - }); - connect(ui->btLocalhost, &QAbstractButton::clicked, this, [this](bool checked) { - if (checked) { - setDefaultShvBrokerUrl(true); - } - }); + + connect(ui->btQxQxOrg, &QRadioButton::toggled, this, &QxEventServiceWidget::setConnectionType); + connect(ui->btLocalhost, &QRadioButton::toggled, this, &QxEventServiceWidget::setConnectionType); + ui->btQxQxOrg->setChecked(!settings.isLocalBroker()); } QxEventServiceWidget::~QxEventServiceWidget() @@ -115,15 +100,17 @@ bool QxEventServiceWidget::saveSettings() auto *svc = service(); if(svc) { auto ss = svc->settings(); - ss.setShvBrokerUrl(ui->edServerUrl->text()); + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + ss.setShvBrokerUrl(ui->edServerUrl->text()); + } + ss.setLocalBroker(is_localhost); ss.setExportDatabase(ui->chkExportDatabase->isChecked()); svc->setSettings(ss); - auto *event_plugin = getPlugin(); - auto current_stage = event_plugin->currentStageId(); - auto stage_data = event_plugin->stageData(current_stage); - stage_data.setQxApiToken(ui->edApiToken->text()); - event_plugin->setStageData(current_stage, stage_data); + auto *event_plugin = getPlugin(); + auto *event_config = event_plugin->eventConfig(); + event_config->setQxApiToken(ui->edApiToken->text()); } return true; } @@ -175,69 +162,29 @@ void QxEventServiceWidget::testConnection() rpc->open(); } -void QxEventServiceWidget::exportEventInfo() -{ - auto *svc = service(); - Q_ASSERT(svc); - auto *reply = svc->postEventInfo(ui->edServerUrl->text(), ui->edApiToken->text()); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - auto data = reply->readAll(); - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - ui->edEventId->setValue(event_info.id()); - setMessage(tr("Event info updated OK")); - } - else { - setMessage(tr("Event info update error: %1\n%2").arg(reply->errorString()).arg(QString::fromUtf8(data)), MessageType::Error); - } - reply->deleteLater(); - }); -} - -void QxEventServiceWidget::exportStartList() -{ - auto *svc = service(); - Q_ASSERT(svc); - saveSettings(); - setMessage(tr("Start list export started ..."), MessageType::Progress); - svc->postStartListIofXml3(this, [this](auto err) { - if (err.isEmpty()) { - setMessage(tr("Start list exported Ok")); - } - else { - setMessage(err, MessageType::Error); - } - }); -} - -void QxEventServiceWidget::exportRuns() +void QxEventServiceWidget::setConnectionType() { - auto *svc = service(); - Q_ASSERT(svc); - saveSettings(); - setMessage(tr("Runs export started ..."), MessageType::Progress); - svc->postRuns(this, [this](auto err) { - if (err.isEmpty()) { - setMessage(tr("Runs exported Ok")); - } - else { - setMessage(err, MessageType::Error); - } - }); + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + ui->lblUrl->setVisible(true); + ui->edServerUrl->setVisible(true); + } else { + ui->lblUrl->setVisible(false); + ui->edServerUrl->setVisible(false); + } } -void QxEventServiceWidget::setDefaultShvBrokerUrl(bool is_local_host) +QString QxEventServiceWidget::brokerUrl() const { - auto url = QStringLiteral("tcp://%1?user=qe&%2=%3"); - if (is_local_host) { - url = url.arg("localhost"); - } else { - url = url.arg("qxqx.org"); + bool is_localhost = ui->btLocalhost->isChecked(); + if (is_localhost) { + return ui->edServerUrl->text(); } + auto url = QStringLiteral("tcp://%1?user=qe&%2=%3"); + url = url.arg("qxqx.org"); url = url.arg("password"); url = url.arg("2ddf6394ea6"); - ui->edServerUrl->setText(url); + return url; } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index 1bde76efc..0ae762934 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -26,10 +26,8 @@ class QxEventServiceWidget : public qf::gui::framework::DialogWidget QxEventService* service(); bool saveSettings(); void testConnection(); - void exportEventInfo(); - void exportStartList(); - void exportRuns(); - void setDefaultShvBrokerUrl(bool is_local_host); + void setConnectionType(); + QString brokerUrl() const; private: Ui::QxEventServiceWidget *ui; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index d61295c58..b6bc76c50 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -21,7 +21,7 @@ - + 0 @@ -39,7 +39,7 @@ localhost - false + true @@ -49,7 +49,7 @@ qxqx.org - true + false @@ -111,8 +111,14 @@ true + + Not defined + - 1 + 0 + + + 0 @@ -139,8 +145,8 @@ - - + + Qt::Orientation::Horizontal @@ -153,40 +159,17 @@ - - - - false - - - Export event info - - - - + Test connection - - - - false - - - Export start list - - - - - - - false - + + - Export runs + Archive event diff --git a/quickevent/app/quickevent/plugins/Event/src/stage.h b/quickevent/app/quickevent/plugins/Event/src/stage.h index f52c79399..ec50622c9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/stage.h +++ b/quickevent/app/quickevent/plugins/Event/src/stage.h @@ -16,7 +16,6 @@ class StageData : public QVariantMap QF_VARIANTMAP_KEY_FIELD(bool, useallmaps, is, set, UseAllMaps) QF_VARIANTMAP_KEY_FIELD(QVariantMap, drawingconfig, d, setD, rawingConfig) - QF_VARIANTMAP_KEY_FIELD(QString, qxapitoken, qx, setQx, ApiToken) public: StageData(const QVariantMap &data = QVariantMap());