From 6d7a0601333f22634ffd697d1f2108c5fd0a9cb1 Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Sun, 31 May 2026 22:26:52 +0200 Subject: [PATCH 01/13] Add option to generate vacants in all classes - including free start - and print them both for starters & classes & export to XML --- .../src/reportoptionsdialog.cpp | 14 +- .../src/reportoptionsdialog.h | 5 +- .../src/reportoptionsdialog.ui | 22 +- patch_runsplugin.py | 153 +++++++++++++ patch_runsplugin_sort.py | 119 ++++++++++ patch_runsplugin_starters.py | 173 ++++++++++++++ .../plugins/Event/src/services/emmaclient.cpp | 2 +- .../Event/src/services/ofeed/ofeedclient.cpp | 2 +- .../Event/src/services/oresultsclient.cpp | 2 +- .../Event/src/services/qx/qxclientservice.cpp | 2 +- .../plugins/Relays/src/relayswidget.cpp | 4 +- .../plugins/Runs/src/runsplugin.cpp | 213 ++++++++++++------ .../quickevent/plugins/Runs/src/runsplugin.h | 12 +- .../plugins/Runs/src/runswidget.cpp | 15 +- .../quickevent/plugins/Runs/src/runswidget.h | 3 +- 15 files changed, 637 insertions(+), 104 deletions(-) create mode 100644 patch_runsplugin.py create mode 100644 patch_runsplugin_sort.py create mode 100644 patch_runsplugin_starters.py diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp index 0c1ed71c8..08ad9bf6d 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp @@ -54,7 +54,7 @@ ReportOptionsDialog::ReportOptionsDialog(QWidget *parent) connect(this, &ReportOptionsDialog::startListOptionsVisibleChanged, ui->grpStartOptions, &QGroupBox::setVisible); connect(this, &ReportOptionsDialog::classFilterVisibleChanged, ui->grpClassFilter, &QGroupBox::setVisible); connect(this, &ReportOptionsDialog::startersOptionsVisibleChanged, ui->grpStartersOptions, &QGroupBox::setVisible); - connect(this, &ReportOptionsDialog::vacantsVisibleChanged, ui->chkStartOpts_PrintVacants, &QCheckBox::setVisible); + connect(this, &ReportOptionsDialog::vacantsVisibleChanged, ui->cbxStartOpts_PrintVacants, &QComboBox::setVisible); connect(this, &ReportOptionsDialog::stagesOptionVisibleChanged, ui->grpStages, &QGroupBox::setVisible); connect(this, &ReportOptionsDialog::legsOptionVisibleChanged, ui->grpLegs, &QGroupBox::setVisible); connect(this, &ReportOptionsDialog::pageLayoutVisibleChanged, ui->grpPageLayout, &QGroupBox::setVisible); @@ -117,9 +117,9 @@ ReportOptionsDialog::BreakType ReportOptionsDialog::breakType() const return static_cast(ui->cbxBreakAfterClassType->currentIndex()); } -bool ReportOptionsDialog::isStartListPrintVacants() const +ReportOptionsDialog::VacantsOption ReportOptionsDialog::startListPrintVacantsOption() const { - return ui->chkStartOpts_PrintVacants->isChecked(); + return static_cast(ui->cbxStartOpts_PrintVacants->currentIndex()); } bool ReportOptionsDialog::isStartListPrintStartNumbers() const @@ -207,8 +207,8 @@ int ReportOptionsDialog::exec() void ReportOptionsDialog::setStartListPrintVacantsVisible(bool b) { if(!b) - ui->chkStartOpts_PrintVacants->setChecked(b); - ui->chkStartOpts_PrintVacants->setVisible(b); + ui->cbxStartOpts_PrintVacants->setCurrentIndex(0); + ui->cbxStartOpts_PrintVacants->setVisible(b); } void ReportOptionsDialog::setOptions(const ReportOptionsDialog::Options &options) @@ -232,7 +232,7 @@ void ReportOptionsDialog::setOptions(const ReportOptionsDialog::Options &options ui->btClassNames->setChecked(filter_type == FilterType::ClassName); auto index = ui->cbxStartNumber->findData(options.startNumber()); ui->cbxStartNumber->setCurrentIndex(index); - ui->chkStartOpts_PrintVacants->setChecked(options.isStartListPrintVacants()); + ui->cbxStartOpts_PrintVacants->setCurrentIndex(options.startListPrintVacantsOption()); ui->chkStartOpts_PrintStartNumbers->setChecked(options.isStartListPrintStartNumbers()); ui->edStartersOptionsLineSpacing->setValue(options.startersOptionsLineSpacing()); ui->edNumPlaces->setValue(options.resultNumPlaces()); @@ -268,7 +268,7 @@ ReportOptionsDialog::Options ReportOptionsDialog::options() const opts.setStartNumber(ui->cbxStartNumber->currentData().toInt()); FilterType filter_type = ui->btWildCard->isChecked()? FilterType::WildCard: ui->btRegExp->isChecked()? FilterType::RegExp: FilterType::ClassName; opts.setClassFilterType((int)filter_type); - opts.setStartListPrintVacants(isStartListPrintVacants()); + opts.setStartListPrintVacantsOption((int)startListPrintVacantsOption()); opts.setStartListPrintStartNumbers(isStartListPrintStartNumbers()); opts.setStartersOptionsLineSpacing(ui->edStartersOptionsLineSpacing->value()); opts[QStringLiteral("isBreakAfterEachClass")] = isBreakAfterEachClass(); diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.h b/libquickevent/libquickeventgui/src/reportoptionsdialog.h index 0780f1db1..e25ef7ee2 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.h +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.h @@ -54,6 +54,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: enum class FilterType : int {WildCard = 0, RegExp, ClassName}; enum class StartTimeFormat : int {RelativeToClassStart = 0, DayTime}; enum class StartlistOrderFirstBy : int { ClassName = 0, StartTime, Names }; + enum class VacantsOption : int { OnlyRunners = 0, RegularVacants = 1, AllVacants = 2 }; class Options : public QVariantMap { @@ -69,7 +70,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: QF_VARIANTMAP_FIELD2(int, c, setC, lassFilterType, 0) QF_VARIANTMAP_FIELD(bool, is, set, UseClassFilter) QF_VARIANTMAP_FIELD(bool, is, set, InvertClassFilter) - QF_VARIANTMAP_FIELD(bool, is, set, StartListPrintVacants) + QF_VARIANTMAP_FIELD2(int, s, setS, tartListPrintVacantsOption, 0) QF_VARIANTMAP_FIELD(bool, is, set, StartListPrintStartNumbers) QF_VARIANTMAP_FIELD2(int, s, setS, tagesCount, 1) QF_VARIANTMAP_FIELD2(int, l, setL, egsCount, 1) @@ -119,7 +120,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: StartTimeFormat startTimeFormat() const; StartlistOrderFirstBy startlistOrderFirstBy() const; BreakType breakType() const; - Q_INVOKABLE bool isStartListPrintVacants() const; + Q_INVOKABLE VacantsOption startListPrintVacantsOption() const; Q_INVOKABLE bool isStartListPrintStartNumbers() const; Q_INVOKABLE bool isBreakAfterEachClass() const {return breakType() != BreakType::None;} Q_INVOKABLE bool isColumnBreak() const {return breakType() == BreakType::Column;} diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.ui b/libquickevent/libquickeventgui/src/reportoptionsdialog.ui index b8b94c412..68cbb5080 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.ui +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.ui @@ -349,10 +349,22 @@ 5 - - - Print vacants - + + + + Only runners + + + + + Regular vacants + + + + + All vacants + + @@ -567,7 +579,7 @@ chkExcludeDisq edStartersOptionsLineSpacing cbxStartNumber - chkStartOpts_PrintVacants + cbxStartOpts_PrintVacants chkStartOpts_PrintStartNumbers btStartTimes1 btStartTimes2 diff --git a/patch_runsplugin.py b/patch_runsplugin.py new file mode 100644 index 000000000..31c745963 --- /dev/null +++ b/patch_runsplugin.py @@ -0,0 +1,153 @@ +import re + +path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' +with open(path, 'r', encoding='utf-8') as f: + content = f.read() + +# 1. startListClassesTable signature +content = content.replace( + 'qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const bool insert_vacants, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format)', + 'qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format)' +) + +# 2. Add vacants fields to query +content = content.replace( + '.select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin")', + '.select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter")' +) + +# 3. Vacants logic +old_vacants_logic = ''' if(start_interval > 0 && insert_vacants) { + for(int j=0; j 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + for(int j=0; j 0) ? cnt / vacantEvery : 0; + int total_vacants = vacantsBefore + vacants + vacantsAfter; + for (int k = 0; k < total_vacants; ++k) { + int ix = tt2.appendRow(); + qf::core::utils::TreeTableRow tt2_row = tt2.row(ix); + tt2_row.setValue(QStringLiteral("startTimeMs"), QVariant()); + tt2_row.setValue(QStringLiteral("competitorName"), "---"); + tt2_row.setValue(QStringLiteral("registration"), QString()); + tt2_row.setValue(QStringLiteral("siId"), 0); + tt2_row.setValue(QStringLiteral("startNumber"), 0); + tt2.setRow(ix, tt2_row); + } + }''' + +content = content.replace(old_vacants_logic, new_vacants_logic) + +# 4. XML 3.0 Export +content = content.replace( + 'QString RunsPlugin::startListStageIofXml30(int stage_id, bool with_vacants)', + 'QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' +) + +content = content.replace( + 'bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, bool with_vacants)', + 'bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' +) + +content = content.replace( + 'QString str = startListStageIofXml30(stage_id, with_vacants);', + 'QString str = startListStageIofXml30(stage_id, vacants_option);' +) + +content = content.replace( + 'auto tt1 = startListClassesTable("", with_vacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart);', + 'auto tt1 = startListClassesTable("", vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart);' +) + + +# 5. UI calls mappings +content = content.replace( + 'auto tt = startListClassesTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.isStartListPrintVacants(), dlg.startTimeFormat());', + 'auto tt = startListClassesTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption(), dlg.startTimeFormat());' +) + +content = content.replace( + 'qf::core::utils::TreeTable tt1 = startListClassesTable("", false, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', + 'qf::core::utils::TreeTable tt1 = startListClassesTable("", quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' +) + +content = content.replace( + 'auto tt1 = startListClassesTable(sql_where, true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', + 'auto tt1 = startListClassesTable(sql_where, quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' +) + +content = content.replace( + 'auto tt1 = startListClassesTable("",true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', + 'auto tt1 = startListClassesTable("", quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' +) + +with open(path, 'w', encoding='utf-8') as f: + f.write(content) diff --git a/patch_runsplugin_sort.py b/patch_runsplugin_sort.py new file mode 100644 index 000000000..18a194cc0 --- /dev/null +++ b/patch_runsplugin_sort.py @@ -0,0 +1,119 @@ +import sys + +path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' +with open(path, 'r', encoding='utf-8') as f: + content = f.read() + +# 1. Fix appendVacantsToClassTable +old_append = '''void RunsPlugin::appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) +{ + if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) {''' +new_append = '''void RunsPlugin::appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) +{ + int start_time_0 = tt_row.value(QStringLiteral("startTimeMin")).toInt() * 60 * 1000; + int start_time_last = tt_row.value(QStringLiteral("lastStartTimeMin")).toInt() * 60 * 1000; + int start_interval = tt_row.value(QStringLiteral("startIntervalMin")).toInt() * 60 * 1000; + if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) {''' +content = content.replace(old_append, new_append) + +# 2. Fix startListClassesTable where I forgot to remove the variables +content = content.replace( +''' for(int i=0; i rowsData; + + for(int i=0; i()->stageStartDateTime(stage_id).toSecsSinceEpoch();\n\n\tauto tt_classes', 'auto tt_classes') + +with open(path, 'w', encoding='utf-8') as f: + f.write(content) diff --git a/patch_runsplugin_starters.py b/patch_runsplugin_starters.py new file mode 100644 index 000000000..4b91430a0 --- /dev/null +++ b/patch_runsplugin_starters.py @@ -0,0 +1,173 @@ +import sys + +path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' +with open(path, 'r', encoding='utf-8') as f: + content = f.read() + +# 1. Update startListStartersTable signature +content = content.replace( + 'qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr)', + 'qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' +) + +# 2. Extract appendVacantsToClassTable +old_vacants_logic = ''' if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + for(int j=0; j()->currentStageId()));', 'auto tt = startListStartersTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption());') +content = content.replace('dlg.setStartListPrintVacantsVisible(false);', 'dlg.setStartListPrintVacantsVisible(true);') + +# 5. Redefine startListStartersTable to flatten from startListClassesTable +old_starters = '''qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) +{ + int stage_id = selectedStageId(); + auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); + + qfs::QueryBuilder qb; + qb.select2("competitors", "registration, id, startNumber, country") + .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") + .select("COALESCE(runs.startTimeMs / 1000 / 60, 0) AS startTimeMin") + .select2("runs", "siId, startTimeMs") + .select2("classes", "name") + .from("competitors") + .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning", "INNER JOIN") + .join("competitors.classId", "classes.id") + .orderBy("runs.startTimeMs, classes.name, competitors.lastName");//.limit(1); + if(!where_expr.isEmpty()) + qb.where(where_expr); + QVariantMap qpm; + qpm["stage_id"] = stage_id; + qf::gui::model::SqlTableModel m; + m.setQueryBuilder(qb); + m.setQueryParameters(qpm); + m.reload(); + auto tt = m.toTreeTable(); + addStartTimeTextToClass(tt,start00_epoch_sec, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + tt.setValue("stageId", stage_id); + tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); + return tt; +}''' + +new_starters = '''qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) +{ + int stage_id = selectedStageId(); + auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); + + auto tt_classes = startListClassesTable(where_expr, vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + + qf::core::utils::TreeTable tt; + tt.setValue("stageId", stage_id); + tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); + + tt.appendColumn("competitors.registration", QMetaType(QMetaType::QString)); + tt.appendColumn("competitors.id", QMetaType(QMetaType::Int)); + tt.appendColumn("competitors.startNumber", QMetaType(QMetaType::Int)); + tt.appendColumn("competitors.country", QMetaType(QMetaType::QString)); + tt.appendColumn("competitorName", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeMin", QMetaType(QMetaType::Int)); + tt.appendColumn("runs.siId", QMetaType(QMetaType::Int)); + tt.appendColumn("startTimeMs", QMetaType(QMetaType::Int)); + tt.appendColumn("classes.name", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeText", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeMsText", QMetaType(QMetaType::QString)); + + for(int i=0; i()->startListIofXml30() - : getPlugin()->startListStageIofXml30(current_stage, false); + : getPlugin()->startListStageIofXml30(current_stage, quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); QFile f(file_name); if(f.open(QFile::WriteOnly)) { 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 667b37943..1be9a08d0 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp @@ -163,7 +163,7 @@ void OFeedClient::exportStartListIofXml3(std::function on_success) QString str = is_relays ? getPlugin()->startListIofXml30() - : getPlugin()->startListStageIofXml30(current_stage, false); + : getPlugin()->startListStageIofXml30(current_stage, quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); sendFile(tr("start list upload"), "/rest/v1/upload/iof", str, on_success); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp index af54621c3..61c99f7fb 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp @@ -76,7 +76,7 @@ void OResultsClient::exportStartListIofXml3(std::function on_success) QString str = is_relays ? getPlugin()->startListIofXml30() - : getPlugin()->startListStageIofXml30(current_stage, false); + : getPlugin()->startListStageIofXml30(current_stage, quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); sendFile("start list upload", "/start-lists", str, on_success); } 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..e80607c83 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp @@ -203,7 +203,7 @@ void QxClientService::postStartListIofXml3(QObject *context, std::functioncurrentStageId(); bool is_relays = ep->eventConfig()->isRelays(); if (!is_relays) { - auto xml = getPlugin()->startListStageIofXml30(current_stage, false); + auto xml = getPlugin()->startListStageIofXml30(current_stage, quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); uploadSpecFile(SpecFile::StartListIofXml3, xml.toUtf8(), context, call_back); } } diff --git a/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp b/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp index 02cfe3ffa..f128fb23e 100644 --- a/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp +++ b/quickevent/app/quickevent/plugins/Relays/src/relayswidget.cpp @@ -454,7 +454,7 @@ void RelaysWidget::print_start_list_classes() if(!dlg.exec()) return; QVariantMap props = dlg.reportProperties(); - bool vacants = dlg.isStartListPrintVacants(); + bool vacants = dlg.startListPrintVacantsOption() != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners; QVariant td = getPlugin()->startListByClassesTableData(dlg.sqlWhereExpression(), vacants); auto report_name = (dlg.options().isRelayShowLegsDetails()) ? "startList_classes.qml" : "startList_classes_condensed.qml"; qf::gui::reports::ReportViewWidget::showReport(this @@ -477,7 +477,7 @@ void RelaysWidget::print_start_list_clubs() if(!dlg.exec()) return; QVariantMap props = dlg.reportProperties(); - bool vacants = dlg.isStartListPrintVacants(); + bool vacants = dlg.startListPrintVacantsOption() != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners; QVariant td = startListByClubsTableData(vacants); auto report_name = (dlg.options().isRelayShowLegsDetails()) ? "startList_clubs.qml" : "startList_clubs_condensed.qml"; qf::gui::reports::ReportViewWidget::showReport(this, diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 743ba5f4c..b6c2d2a6c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1379,7 +1379,7 @@ void RunsPlugin::setRunsRecord(int run_id, const QVariant &rec) qf::core::sql::QueryBuilder RunsPlugin::startListQuery() { qfs::QueryBuilder qb; - qb.select2("competitors", "lastName, firstName, registration, iofId, startNumber, country, club") + qb.select2("competitors", "id, lastName, firstName, registration, iofId, startNumber, country, club") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .select2("runs", "id, stageId, siId, startTimeMs") .select2("classes","name") @@ -1406,14 +1406,14 @@ QVariantMap RunsPlugin::startListRecord(int run_id) return {}; } -qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const bool insert_vacants, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format) +qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format) { int stage_id = selectedStageId(); auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); qfs::QueryBuilder qb; qb.select2("classes", "id, name") - .select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin") + .select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter") .select2("courses", "length, climb, id") .from("classes") .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId={{stage_id}}") @@ -1435,7 +1435,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher tt.appendColumn("courses.startNumber", QMetaType(QMetaType::Int)); qfs::QueryBuilder qb2; - qb2.select2("competitors", "lastName, firstName, registration, iofId, startNumber, country, club") + qb2.select2("competitors", "id, lastName, firstName, registration, iofId, startNumber, country, club") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .select2("runs", "id, siId, startTimeMs") .select2("clubs","name, abbr, importId") @@ -1487,43 +1487,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher m2.setQueryParameters(qpm); m2.reload(); auto tt2 = m2.toTreeTable(); - int start_time_0 = tt_row.value(QStringLiteral("startTimeMin")).toInt() * 60 * 1000; - int start_time_last = tt_row.value(QStringLiteral("lastStartTimeMin")).toInt() * 60 * 1000; - int start_interval = tt_row.value(QStringLiteral("startIntervalMin")).toInt() * 60 * 1000; - if(start_interval > 0 && insert_vacants) { - for(int j=0; j 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + for(int j=0; j()->stageStartDateTime(stage_id).toSecsSinceEpoch(); + auto tt_classes = startListClassesTable(where_expr, vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); - qfs::QueryBuilder qb; - qb.select2("competitors", "registration, id, startNumber, country") - .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") - .select("COALESCE(runs.startTimeMs / 1000 / 60, 0) AS startTimeMin") - .select2("runs", "siId, startTimeMs") - .select2("classes", "name") - .from("competitors") - .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning", "INNER JOIN") - .join("competitors.classId", "classes.id") - .orderBy("runs.startTimeMs, classes.name, competitors.lastName");//.limit(1); - if(!where_expr.isEmpty()) - qb.where(where_expr); - QVariantMap qpm; - qpm["stage_id"] = stage_id; - qf::gui::model::SqlTableModel m; - m.setQueryBuilder(qb); - m.setQueryParameters(qpm); - m.reload(); - auto tt = m.toTreeTable(); - addStartTimeTextToClass(tt,start00_epoch_sec, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + qf::core::utils::TreeTable tt; tt.setValue("stageId", stage_id); tt.setValue("event", getPlugin()->eventConfig()->value("event")); tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); + + tt.appendColumn("competitors.registration", QMetaType(QMetaType::QString)); + tt.appendColumn("competitors.id", QMetaType(QMetaType::Int)); + tt.appendColumn("competitors.startNumber", QMetaType(QMetaType::Int)); + tt.appendColumn("competitors.country", QMetaType(QMetaType::QString)); + tt.appendColumn("competitorName", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeMin", QMetaType(QMetaType::Int)); + tt.appendColumn("runs.siId", QMetaType(QMetaType::Int)); + tt.appendColumn("startTimeMs", QMetaType(QMetaType::Int)); + tt.appendColumn("classes.name", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeText", QMetaType(QMetaType::QString)); + tt.appendColumn("startTimeMsText", QMetaType(QMetaType::QString)); + + QList rowsData; + + for(int i=0; i()->currentStageId()), dlg.isStartListPrintVacants(), dlg.startTimeFormat()); + auto tt = startListClassesTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption(), dlg.startTimeFormat()); auto opts = dlg.optionsMap(); QVariantMap props; props["options"] = opts; @@ -1795,7 +1860,7 @@ void RunsPlugin::report_startListClubs() dlg.setCurrentStageId(getPlugin()->currentStageId()); dlg.setClassFilterVisible(false); dlg.setStartListOptionsVisible(true); - dlg.setStartListPrintVacantsVisible(false); + dlg.setStartListPrintVacantsVisible(true); dlg.setPageLayoutVisible(true); dlg.setStartTimeFormatVisible(true); dlg.setStartlistOrderFirstByVisible(true); @@ -1823,10 +1888,10 @@ void RunsPlugin::report_startListStarters() dlg.setCurrentStageId(getPlugin()->currentStageId()); dlg.setClassFilterVisible(true); dlg.setStartListOptionsVisible(true); - dlg.setStartListPrintVacantsVisible(false); + dlg.setStartListPrintVacantsVisible(true); dlg.setStartersOptionsVisible(true); if(dlg.exec()) { - auto tt = startListStartersTable(dlg.sqlWhereExpression(getPlugin()->currentStageId())); + auto tt = startListStartersTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption()); auto opts = dlg.optionsMap(); QVariantMap props; props["options"] = opts; @@ -2061,7 +2126,7 @@ void append_list(QVariantList &lst, const QVariantList &new_lst) } void RunsPlugin::export_startListClassesHtml() { - qf::core::utils::TreeTable tt1 = startListClassesTable("", false, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + qf::core::utils::TreeTable tt1 = startListClassesTable("", quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); QVariantList body{QStringLiteral("body")}; QString h1_str = "{{documentTitle}}"; QVariantMap event = tt1.value("event").toMap(); @@ -2655,12 +2720,12 @@ QString RunsPlugin::getClubAbbrFromName(QString name) return ""; } -QString RunsPlugin::startListStageIofXml30(int stage_id, bool with_vacants) +QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) { QDateTime start00 = getPlugin()->stageStartDateTime(stage_id); Event::EventConfig *event_config = getPlugin()->eventConfig(); //console.debug("print_vacants", print_vacants); - auto tt1 = startListClassesTable("", with_vacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart); + auto tt1 = startListClassesTable("", vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart); bool is_iof_race = event_config->isIofRace(); int iof_xml_race_number = event_config->iofXmlRaceNumber(); @@ -2799,14 +2864,14 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, bool with_vacants) return qf::core::utils::HtmlUtils::fromXmlList(xml_root, opts); } -bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, bool with_vacants) +bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) { QFile f(file_name); if(!f.open(QIODevice::WriteOnly)) { qfError() << "Cannot open file" << f.fileName() << "for writing."; return false; } - QString str = startListStageIofXml30(stage_id, with_vacants); + QString str = startListStageIofXml30(stage_id, vacants_option); f.write(str.toUtf8()); qfInfo() << "exported:" << file_name; return true; @@ -2867,7 +2932,7 @@ bool RunsPlugin::exportStartListCurrentStageCsvSime(const QString &file_name, bo csv.setGenerateByteOrderMark(true); #endif - auto tt1 = startListClassesTable(sql_where, true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + auto tt1 = startListClassesTable(sql_where, quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); int id = 0; for(int i=0; i #include +#include namespace qf { namespace core { namespace utils { class Table; class TreeTable; + class TreeTableRow; } } namespace gui { @@ -74,7 +76,7 @@ class RunsPlugin : public qf::gui::framework::Plugin Q_INVOKABLE QVariantMap printAwardsOptionsWithDialog(const QVariantMap &opts); - bool exportStartListStageIofXml30(int stage_id, const QString &file_name, bool with_vacants); + bool exportStartListStageIofXml30(int stage_id, const QString &file_name, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); bool exportStartListCurrentStageCsvSime(const QString &file_name, bool bibs, QString sql_where); bool exportStartListCurrentStageTvGraphics(const QString &file_name); @@ -93,9 +95,10 @@ class RunsPlugin : public qf::gui::framework::Plugin qf::core::sql::QueryBuilder startListQuery(); QVariantMap startListRecord(int run_id); - qf::core::utils::TreeTable startListClassesTable(const QString &where_expr, const bool insert_vacants, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); + qf::core::utils::TreeTable startListClassesTable(const QString &where_expr, const quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); + void appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); qf::core::utils::TreeTable startListClubsTable(const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format, const quickevent::gui::ReportOptionsDialog::StartlistOrderFirstBy order_first_by); - qf::core::utils::TreeTable startListStartersTable(const QString &where_expr); + qf::core::utils::TreeTable startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); qf::core::utils::TreeTable startListClassesNStagesTable(const int stages_count, const QString &where_expr, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); qf::core::utils::TreeTable startListClubsNStagesTable(const int stages_count, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); @@ -121,7 +124,7 @@ class RunsPlugin : public qf::gui::framework::Plugin QString export_resultsHtmlStage(bool with_laps = false); void export_resultsHtmlStageWithLaps(); void export_resultsHtmlNStages(); - QString startListStageIofXml30(int stage_id, bool with_vacants); + QString startListStageIofXml30(int stage_id, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); QString resultsIofXml30Stage(int stage_id); int competitorForRun(int run_id); int runForCompetitorStage(int competitor_id, int stage_id); @@ -150,3 +153,4 @@ class RunsPlugin : public qf::gui::framework::Plugin } #endif // RUNS_RUNSPLUGIN_H + diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp index 557f46fbe..45b59ded6 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp @@ -284,12 +284,17 @@ void RunsWidget::settleDownInPartWidget(::PartWidget *part_widget) { { auto *a = new qfw::Action(tr("&IOF-XML 3.0")); - connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(false); }); + connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); }); m_export_stlist_xml->addActionInto(a); } { - auto *a = new qfw::Action(tr("IOF-XML 3.0 with &vacants")); - connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(true); }); + auto *a = new qfw::Action(tr("IOF-XML 3.0 with ®ular vacants")); + connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants); }); + m_export_stlist_xml->addActionInto(a); + } + { + auto *a = new qfw::Action(tr("IOF-XML 3.0 with &all vacants")); + connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption::AllVacants); }); m_export_stlist_xml->addActionInto(a); } } @@ -977,13 +982,13 @@ int RunsWidget::selectedStageId() return getPlugin()->selectedStageId(); } -void RunsWidget::export_startList_stage_iofxml30(bool with_vacants) +void RunsWidget::export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) { int stage_id = selectedStageId(); QString fn = getSaveFileName(Event::START_LIST_IOFXML3_FILE, stage_id); if(fn.isEmpty()) return; - getPlugin()->exportStartListStageIofXml30(stage_id, fn, with_vacants); + getPlugin()->exportStartListStageIofXml30(stage_id, fn, vacants_option); } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index 9be9da658..b090a1a8a 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -4,6 +4,7 @@ #include #include "partwidget.h" +#include class QComboBox; class QCheckBox; @@ -47,7 +48,7 @@ class RunsWidget : public QFrame Q_INVOKABLE int selectedStageId(); public: - void export_startList_stage_iofxml30(bool with_vacants); + void export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); void export_startList_stage_csv_sime(); void export_startList_stage_tv_graphics(); From 7cdead3b09a7039a47f9ada712070ceaa2e6faca Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Sun, 31 May 2026 22:34:57 +0200 Subject: [PATCH 02/13] Make XML export for vacants respect IOF.xsd (omit Person & Organization elements) --- patch_runsplugin_xml.py | 140 ++++++++++++++++++ .../plugins/Runs/src/runsplugin.cpp | 76 +++++----- 2 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 patch_runsplugin_xml.py diff --git a/patch_runsplugin_xml.py b/patch_runsplugin_xml.py new file mode 100644 index 000000000..1ba9a8d09 --- /dev/null +++ b/patch_runsplugin_xml.py @@ -0,0 +1,140 @@ +import sys + +path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' +with open(path, 'r', encoding='utf-8') as f: + content = f.read() + +old_logic = ''' for(int j=0; j Date: Mon, 1 Jun 2026 00:39:44 +0200 Subject: [PATCH 03/13] Add start time to vacants and fix export to match --- image.png | Bin 0 -> 18921 bytes .../libquickeventgui-cs_CZ.ts | 15 +++++++ patch_runsplugin_starters_robust.py | 42 ++++++++++++++++++ .../plugins/Runs/src/runsplugin.cpp | 27 ++++++----- quickevent/app/quickevent/quickevent-cs_CZ.ts | 9 +++- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 image.png create mode 100644 patch_runsplugin_starters_robust.py diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..927e37ae698881902b771535c6c40c1e72f6a867 GIT binary patch literal 18921 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 vacants Tisknout vakanty + + + Only runners + Jen běžci + + + + Regular vacants + Běžné vakanty + + + + All vacants + Všechny vakanty + Print start numbers diff --git a/patch_runsplugin_starters_robust.py b/patch_runsplugin_starters_robust.py new file mode 100644 index 000000000..c713b74b1 --- /dev/null +++ b/patch_runsplugin_starters_robust.py @@ -0,0 +1,42 @@ +import sys + +path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' +with open(path, 'r', encoding='utf-8') as f: + content = f.read() + +old_logic = ''' rowMap["competitors.registration"] = tt2_row.value("registration"); + rowMap["competitors.id"] = tt2_row.value("competitors.id"); + rowMap["competitors.startNumber"] = tt2_row.value("competitors.startNumber"); + rowMap["competitors.country"] = tt2_row.value("competitors.country"); + rowMap["competitorName"] = tt2_row.value("competitorName"); + + QVariant startTimeMsVar = tt2_row.value("startTimeMs"); + if (!startTimeMsVar.isNull()) + rowMap["startTimeMin"] = startTimeMsVar.toInt() / 1000 / 60; + else + rowMap["startTimeMin"] = QVariant(); + rowMap["runs.siId"] = tt2_row.value("runs.siId");''' + +new_logic = ''' auto val = [tt2_row](const char* key1, const char* key2) { + QVariant v = tt2_row.value(key1); + if (v.isValid()) return v; + return tt2_row.value(key2); + }; + + rowMap["competitors.registration"] = val("competitors.registration", "registration"); + rowMap["competitors.id"] = val("competitors.id", "id"); + rowMap["competitors.startNumber"] = val("competitors.startNumber", "startNumber"); + rowMap["competitors.country"] = val("competitors.country", "country"); + rowMap["competitorName"] = tt2_row.value("competitorName"); + + QVariant startTimeMsVar = val("runs.startTimeMs", "startTimeMs"); + if (!startTimeMsVar.isNull()) + rowMap["startTimeMin"] = startTimeMsVar.toInt() / 1000 / 60; + else + rowMap["startTimeMin"] = QVariant(); + rowMap["runs.siId"] = val("runs.siId", "siId");''' + +content = content.replace(old_logic, new_logic) + +with open(path, 'w', encoding='utf-8') as f: + f.write(content) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 88313737d..4e5f6040e 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1413,7 +1413,7 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher qfs::QueryBuilder qb; qb.select2("classes", "id, name") - .select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter") + .select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter, mapCount") .select2("courses", "length, climb, id") .from("classes") .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId={{stage_id}}") @@ -1538,7 +1538,7 @@ void RunsPlugin::appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, cons int mapCount = tt_row.value(QStringLiteral("mapCount")).toInt(); int cnt = tt2.rowCount(); int total_vacants = mapCount - cnt; - if (total_vacants < 0) total_vacants = 0; + if (total_vacants < 0) total_vacants = 0; for (int k = 0; k < total_vacants; ++k) { int ix = tt2.appendRow(); qf::core::utils::TreeTableRow tt2_row = tt2.row(ix); @@ -1660,18 +1660,21 @@ qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &whe qf::core::utils::TreeTableRow tt2_row = tt2.row(j); QVariantMap rowMap; - rowMap["competitors.registration"] = tt2_row.value("registration"); - rowMap["competitors.id"] = tt2_row.value("competitors.id"); - rowMap["competitors.startNumber"] = tt2_row.value("competitors.startNumber"); - rowMap["competitors.country"] = tt2_row.value("competitors.country"); + auto val = [tt2_row](const char* key1, const char* key2) { + QVariant v = tt2_row.value(key1); + if (v.isValid()) return v; + return tt2_row.value(key2); + }; + + rowMap["competitors.registration"] = val("competitors.registration", "registration"); + rowMap["competitors.id"] = val("competitors.id", "id"); + rowMap["competitors.startNumber"] = val("competitors.startNumber", "startNumber"); + rowMap["competitors.country"] = val("competitors.country", "country"); rowMap["competitorName"] = tt2_row.value("competitorName"); - QVariant startTimeMsVar = tt2_row.value("startTimeMs"); - if (!startTimeMsVar.isNull()) - rowMap["startTimeMin"] = startTimeMsVar.toInt() / 1000 / 60; - else - rowMap["startTimeMin"] = QVariant(); - rowMap["runs.siId"] = tt2_row.value("runs.siId"); + QVariant startTimeMsVar = val("runs.startTimeMs", "startTimeMs"); + rowMap["startTimeMin"] = startTimeMsVar.toInt() / 1000 / 60; + rowMap["runs.siId"] = val("runs.siId", "siId"); rowMap["startTimeMs"] = startTimeMsVar; rowMap["classes.name"] = class_name; rowMap["startTimeText"] = tt2_row.value("startTimeText"); diff --git a/quickevent/app/quickevent/quickevent-cs_CZ.ts b/quickevent/app/quickevent/quickevent-cs_CZ.ts index ccec2eb30..d2458e027 100644 --- a/quickevent/app/quickevent/quickevent-cs_CZ.ts +++ b/quickevent/app/quickevent/quickevent-cs_CZ.ts @@ -6901,8 +6901,13 @@ Stskněte tlačítko pro obnovení pro zobrazení importovaných dat. - IOF-XML 3.0 with &vacants - IOF XML 3.0 s &vakanty + IOF-XML 3.0 with &regular vacants + IOF XML 3.0 s &běžnými vakanty + + + + IOF-XML 3.0 with &all vacants + IOF XML 3.0 se &všemi vakanty From 67a38e1ac42fbfa7737f91ab269aa23056cf532e Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 08:01:59 +0200 Subject: [PATCH 04/13] Unassign the start time --- image.png | Bin 18921 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 image.png diff --git a/image.png b/image.png deleted file mode 100644 index 927e37ae698881902b771535c6c40c1e72f6a867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18921 zcmeIaXH*njv@J@KBvFto2r5Vh$&!PjK$DZCCL^I~P?6XKK{6teB;hs;KVTwQH}n=9+VELey0ii3#ZlF)%QQU%pV# z#K6ED0e&>_ae*^P10y`(7v@_{MLCR;K8AJR0LMlaB#VJj9(nu1>=tlL;Pm3vTMP`6 zwwoW!PH4U*2F6S8mkP4lZYEnZ;IM;97~2^%MT87Y;}5GW=LR+OCojl^e=n2~{c7RF zE|g2MG%}jI+a4Z@^X75X3@c42pP<7BPrSSQ4`U>#b0_p-TLe=-QSOxSVmaRWfGvNj z(h#`>@v@NL`M6IwR;RCNYis*b=g?s^&HL=!@55@;h|bWLDsOaJm4_TYCJPJ8nZgw{ z?7Qp_tpksyZW}r@Jv@Ye=1AleTID#LUz3d8j9zx4$6@nz?E3ODZBI(Tds z`p&FWaN{#SY*xFvMW&&|9%Wp2gr{G{Sk~AVwuLv@v8#PqT)iF24>1B?Mj)@~eh)5G zial%-K9lN3jzGFS9~u%BqVnwO(k9{ezCHA*F{2+Aa}cY}MdyhMUw*Y;C^)SjKgznI z*cyL+enu1~dwHVLDVx^&9otS~D`T~UJR3`HDNV3ta#Zb%P0&aV-)~tR?7TwKgqpy@ z773ufwt011P>O36WTPi`QyJ}|VFojc8-gi1j?)(+ZexWT$c)?Z{I5{2| zvCeIbNjNjh0q1x}_2HMiRM!mSZzIIcFQV8m)D+I%FbBNAo^K(SEiB~=3BbeeKY7OX z1-I2!v|7*!Cs}6m-RpbG*ovmn>NZ~;>$Gw-gkVw`KkX$0fBHLdP`hzpDS~y23c+ey z=LXU>R*BDB1*Q!TFKr(j!;dZ_&MPG)mr!^tPU_c;ZM!uS?~6VHACrJaHg?3QS{aNF zr>CkOT6MdNWNTcCFH1SZ30;E99j4AEuLd$0ssiayK~5jg1_XCMKWeLsZ$U(e{DeG0U6=@y7bwKU$a_ z>6ap@hw^Y#4{!4^c($C;()y~Hyoj}cKc^7wq8Pe2HoVKgm!9|}rf@Hl)_fCl9wy`_ zRdmN~agT#!hI6jub;V~ST`{sFg!J6Iabi~4%LoqfdA!>+)Emdr5_h>~idZZlMljJr z&FjRi_U~M9a=#963PY(`t&F25+!Col2C7XHJiE^R;-?}}K|g8+(KB;TpufW#ajs%@ z`SKa^G720}?}jbIWOLY6r!hqgqEluS5^(KnpUqYC&TCf-zwr8%>kI9DZKyBM+e9W} z)kUZ$MJA|F<>2f;kksbnfvm78N>Tn*eCw;G7gK9O_y#8DyB@?iVMD(?Fr{!ZOQOUv zB3D2EofQyaXhSH!%Q*h;Y8+$GQJLIA1Tq%m0e!qGmKLpT&4=M5uOUh&ovFW5@;g3* z$V!15y0eM-?T7yzwRs#@CEO=JT;}e-lB}AJVax=TKS6otg?Z^Q8^Lj7ee5o|+Hy4O zXL9292#&?y%?GMlSW6gjTpEnwM;ipDUo;D~O-I9QmNUg9iptr2<&PfC-et3Ki)blG zEuD8Uea!!@^OYAg7*uDs{Z6Z^S%6nFQpb9U-W`%%P1;3BnlOE(At8)ctc1uSIyvGg zCU8f}ix}m8Epl+bHz9Qvbu^{K*VsDa4KrP*xTA&HQHAI*FfieqW`N@GzKcyyTws4W zH+4+I`4MghHtkBB*)o=yRFrTjFIl2)2zH**4Cap>x61LQRWsYY&kIf*Y&e$6L~&LnUy|nwyW$6FM%6SeAM0Flb-W@65=^UB(_U37Dx|)-H)F&? zm@HCn!(n$ER1$e)SX6Jq6jP<<9{&Dqi!Vo#bB}jGyF$2U#5U%2_!CiFp}!nar)iqS zFAk>BZ^ED{sXGsMwvO0t(cA_b(E;DOFQH#vC~}L~r&S#U(({>uw}8>Q(}d zL!X&{TIbjeR6VCKJ@j?x{E}H9aJ9Jv8Xv62q{QPhT0wqRx@%}X4ORD$Cd^4PHLM?f zvY*+JP{}i(mlP?I^cZe*ey4BDx>C}zUmtp9(fv5dU&jhTVkXLyR3Sn%^CYk&?zx?Z zMR0k%X^u{R&EYmM*mbwF^ip@NHy9c@zA?B?bN62);o-U%1otQPx+=8=`sU-hDgznj zL-}NA8AS44zbtkkmtWwaO-02m+-lM_7{eGoMUj(>1rNLK8GAL+q$s^dM=G}yTx~d~ zkM9dS;p2n_ytJo&O)qi1qJ2~q)Qp!wTpgO{C^leiSig)G@pmJ$Fj%kc#;Y&D1|KvR zXTGd>qR-K0o_Ot}Y|yFYVjS2W3ofwg#>yGWE{I5u$Xjk<6bqcoJ=>g*1zU&iR!tt3 z9Ut;yr+O5{9`sp}az2D7auu#)ISd)zYX?iuvKz^R9bz342w`A?K6fpjx8NQ&OSi)N z&5E^JVI9JI37Sm}g%LuD+KQ5B*sr#7VLlBsdZs${(@Gg8{54^S`c__r-d7ssFskPi z_h0skWKc&qmkKV9;6jogO^jF3k~!V#J$9_u&Q>F)HHpqFaklKV8B#@VYjzJ$j4 zx@h;-xE763@Py`@b+jXuD{eJRVw; z3!l7B6v_GqX>YT-!V2`Fr$!1n!lqIga+*%1&hOZ}sE0JQKAhP;8z0U}oYg#wp}Ad& z;R{bx-Md8|>!~k|>l$&t81YNi2Kn1=e6BhXGG4JNw391T-~H28zTUwCx$_mVnJZCa zS5I8tn`ZaeM_%%HYE1boJzK<+A#(B{K(y5L)H)1vGw;;<0S!3HfxqSM@1q8@^$5OV zwMT8{PPe8<{jmnc#NCKpc-<>g3nD|Z4+v%s_6KxT&{{8d1GX$42lpWpM%0(ZpSO9v z7U9fOP&gQ3;7Y%mD!tYOD*#7hCn^0`w#(P6qf5%bp&Wnu9ddFPU(DFx;CyVNeBhFM zrZSIMSTyCxfCKS)8R<{*Vv(?+uZJTem=e5$##V=lfb+jCke6HH0 z7c5}Wkyj;Otijh;&;xF}P*_ab_I#;N2DH3w`wsb_>L*5Z>8q>bod%z)p6&{!M*Hr~ z{t@0Qa`5%E=Ltf3L@&nDZyqs+cSe|%W|j**k7inMMO;?yT{B(YlREq@U0^tuc74HX z@S%<N}@MfySxLJ}u5 z5P5xm{aN<9=UTjZ*B#^J&eeU=*6WM5>ySv-Q)3pE$sP^tjZW!n_(f;Dv_J1kPxDTD ze_>K`P3a}G_xU&J?@T@>V2!ggSLqh$_0)FHefTJJ=lW9N3XSs7z*Ppu?+VrxK5a*= z2rcxt@^~Nbr+wFp0lkw}+o#)Gwh=zxeJ0LZi_vh&Lr%W6oR1m1=X-uLmD}Dg!G|l& zM?rhyK8Hak_06AxgFk)vySNmI^OII)(k?i?Q+Wl1un~+_0_f}W{Y;dm{ihkh?JGK4 ze*K>#L`A;@x1}!8)&rVwTDPE^$!%f~N{{Yhxc97mD=fx`Qn>9or^lkAn}$d`!$qq zmP}1#GVB}d>UcMcY4v;Y&TpS0B7?J5pEHUJWk&7WsIwhxu4dg=*(ZWmCy2ZKCsfU$ z2pv4`t(^u1g(`7XK2;!2aYet2l9LZ&^8E?dIa5$G5UHgE_WvPZF|awv$u;n3)I)-U zpSJiaDJYEiwc(Dqg_@M=ofACmzh!F`R3g(sC&&}a{^j?4~=p&P+OG0 zSGhjT;D0g0K1rgSD}UbH1(`(HKu?AAvfY7?iRhJ+GpkHZx#u)7^t)`+DR1I@L9Gj~|psV&@o%=!1Id+U$v(l5NV)1Ax zL(?*g`rlu0V;7;~&u{vhIUJT^9!f@2eUqQ#ZRCCxvUH-Y8q_ojj5u>4FO0w9wodK2 z@)|E?BnsQ!XOMA=4RE6^?sQwf<`94tv1dpCOgD%USDZR7au6o2adbizedY8KMg-9`hfkq?2- zrPs!P#0Veuu7j#<@;kreHl&R1P4eTZxJQUapd0p%jKUzR(svjo$aDO}BtggC!_{(2 zsAJsYM)%e1pFOwLAf3)8iJ$7&!Ca+qk2Bg~L*r^l(`(U4$N@qcbJ%5&1_yNBQ0Ex1 zTkmjtAPBMtu{0At{#?XZsI%%u=z4GN_tP{(2OECJOPpCRTxIZKVsV<)S%=nyik4s$ zBvJ5SMKY)Bv7zZeF^f2vzAn{x98S5JrDDbJMyqo<>4>929=7zoq=AwFu4VzR^9kl)N9nM zrc)jmyRegmz0m`9zqhZ%IEXrDS*45KF(nL{iU#Us%N~Uboz<#Sw9uI?JakivwW_1I zrHyg)D0zusS*rbOy*-XAl8tvaEL^820O(%k%`O(UlYeKc5ey*Ihn@s@N(Yi*nTz%M z2SRs|Ov7s_&z9&4pD5~9p#!M)1o20>XFwlL)&?vNPo6rs8>ZqfWF+Yp#8cwL zR`U{DG@~I0%t{UP`7)mgx%SZl&3KtC$YG3~Qab-#_wKSL`H%}FI`v1Qa+LMcUbt8hSw`i)68%`x*p>BE5|uW}9UNbaTYz+mPTE>&-z z8a>|$y+3(&{3M?@R$5m%p*V0He{S*p+r?GP7WJV|P}oD0DuVYIrpVu9qRhWX!{tGo z_JMVHMIaq+o$8hr;`wKztv{U#MiLE_8f-W{TR7Nnm*sxYFb*%EZzU)}3w4}ONnvU8 zj2{rZTxjDV^m`_&XggSD9ch(%pT#ZX{DZ$;V0+86eB-dGy>?x9w4VV!@Jy}7;}6=k zzO>YA^Xu--!lwr4v?v)-4^NonHQas`ESsN8+SXsIF{oSF3KahClM-qcDZd1dy0{OFbJf9XjS)H#bS|O{lh2cHchw%#4EaCPaKN!ez z?V3UDge&g0yo>BSW=`(Pk8Ab~dht@iUN|T||JI{XvYwyVt>+%xN1I(^EC{CMV#T4^ zQ}YM_uA2<}>~BL_1++Nd=RJj>AMtA5_63@CoaXm;D%vq50dP6GpCVA9m-8(Vm-Vo5 zD*zMjK==7FEjE{aO7R0u^UO3NTX)I~+-kd)N%lGMU-lb<-zLlKl;EUoTd07jG(86-z1G#mhhPo8ZOL(u5svf`8gw@Lq|#QVi}7u3G1(>R)SMI;$E(IvGbRVmd+Wfev#3bfkt|p1&=)wo*o!3BcC5T2L|1Zv9@|eQ9Nq!d4XNSai{|kPYe0{TURun z-hA_sz9d(oz~;ruw_8p3&djRcu-5h{U)!y)-6l@U9&r zDZMz`ar@=%L$t9nVnOVsZpcZ?QZ8-*a5abTcvaM*u zz8{!*`l6KfD+y#P3NP?2AWV2+N{7%RHkGFxQT&np(5Vb9!1g`Ja{X^!aao(%`L}bon(@ ziX`Z`K|W3=bN~(-_8Gc^(##jdq&%!gRp z920Z9c9K9z&D;wGVxX{)N^tisJsX2Gege;mVAg`ka4G$%?!7~9sWY6zQGz8A!*z-x zJw5ZXQ`NR(xYxF|rbR zXT@s`XAcHZj@R_0v&a*S0S2Ry<}629+90s0?a~pm@$J^4tyEJK*_*&mE{8dbZ|EWK zzg&DAD)peF8?w|Va&$T+FyXtR1Nivq0ZuR|p+8Y;HhS*?c+i?eJ;UC_q=L_L!~`F# z@&@{~hiVrB*=Xo)>}r>C=j(4vS+g1J(kTC6-`vf; z$(9B0GWn}Qs_!C!n)G(}vCyh~r`1!|%f#tli5*kJ>MkdAg&J2wE^P;9&)2o2Tno)b z18Wj=6;nt)O;!n!LlTWft>4Nkf0!+@zWB7kq#bXI+yi}-)Y3BBhbAS}EEWs}P^b-} zC(8EY-O=0(mY=!#2Ef6oYG5H~{yb?Zw&oCrfOZ=Yl>V;$n{U}FEDm|Rw}EuJp_Y?He13}Yl*X{;w%=uNb11`&Bkc~m0ZFhh+i1rguH0SO&7Elj`C$6M~ z=Q)jI{;pnGAdZf=xova`Q#dP9?6gMm9m`EqQkzw`#DZ~ai%(N|nm zo9cOYWSCt|;KvC~_APqlhSV>EM9%jfM;~_7>`=HgeR(;?y6%`2b5cZW?a>^^&F3WV zX7{|g4LY7nI?P(i$P;UztCg!ppMhq17en;4Mu@`B41gyx+}T0`<2+%LnoVPc(a~1@ zaGB3$&dXU$LZDR_qc*G1E)Ik%=p*%m<$4`F)N~~MxwH#SE)ADTx7TVL5h)7i z7{TVj(E6VJn#0|Me%ug%X~KrD9;h&gG3D2_RJH0zrOQO5h+iH#5s@;7{$d1_6ppt= zg;}SYlfG1dlt76|hikg&(QF?o_Pz#A^3^W>4H@Zs!l$&1x-TImqizOi+k2J(A=m^+ z47Fe_qglZjYK}*!N^3W+-^aVK;;`(Oul(N@evFIpvucd z++2g5E-bsFGP`1(!qL5A+G-dMp@8vN?b$de^6Z9B0iDvc z3SQ2v(-_e{MC5T7o#D<(&Rc+4hiD=TK)!ae`4!F9!8v3vu(kV>1iq)RQ=sN+qD0PF zzu{T#!gA`=tWC(7Vkf4^lrNn>HO86^ALoSt0C%<){t{9fvwB0;Mr{<|ex_v%zspLT zd@2sw*nh<D>Sc!PTjluqCIRH@_-{ zi@?Fi2Q0a$eU-%|6brI#^jjoyEiwn_jSrX)49;}o*H^#u1sF-Yo?eTf$lYW2b4hGU zFLbw_PR!ScOo_D)mXZ!z=<=>bWiCFM21n_EN+(a*h2^)(Nro+ml9uzKrQjNf%DBVj zZWL8%o&XNE`(B7&)~;Ch#vY+#B<+Dayf@YQ`6cCueH%gMWQ6=jk6f0R1N-{agZ;XR zLgw^1pVAagNo)s=2J3*IrJnn6jkn#t97OJx+$ocLl9H&RG=YrOkU(ONV|#mp#?QVS z2VTz`sO(l7xg&BQw)$%X%mF6$0I!*OnVe4wkezE=t$!$z{~Q?*}gWR>qQ(-y^}^m|A!<&6lGaZpehcLIK&W8p1K+;ap7|Niwsl+&yy1&9$8P zG2LjQGjnvZuYHHSi2!$`CNN?`BJH-eu~F~VxL~v=b86;|;=R1IXos)rMSr~bhNSBU zLi2-pxTiulL#(flj;c&DH*T38MRtJE7dDe#?LJ9L;{ zF~$Y7R3c_*h2NUZ#@R;&B>Y<9`c3)CzV3js_Oi_5vD^xovekQQdqEA>Eu{TV*KR4I zQt}a2VIKXj+@p}rodW{zkl;Qt>A*4GR7xma|jdiM%%G{5gw%G_hNh6G|ew(5ez zlTu%hdp1Q;Ej6R8X7t`zMf6#g;p^6h)RS%;ESZ%*e9z*_t>3!M6I0QWq0VHSA|Sik z8;hAubW@KFi3Te5zEUFFZPST;-^AoD?d`v&GiLw_be)X0u%zdiu!4e3Fy>*gz>iwZu*Z5-j=rlhXkpX=i9owFnqQbJu6l$nbaRJ54i;! zNia}~Ye=MQiFGi#Wf1rJhkg}t`wSogD^B!=6NPc+bd8_P$Vdp03Rdu@bhi|`b+dw5 zJZORU{u~fvI%S=jJcarbC0U5aoMM}M^6pLf2KC^ZtgZOC-s=4ZY98ApkD}&4&9otN z>ewd}XPA9S#ZkY#d;fe1;Je7+eR#7z%K~qxyZ`aJ{DXOa`O>Kk(#PzZ>*^g22=pmvo=^z2|n!I&3W&B=ehiFTn<0D6SD6MQ26v=d1aSUnG*dVf1&9(-?t3>xz&;0 z{1!TNrduQf2X=dIc5U0#yc^moj$V8B9Upoeanw-n?0>Ql3r)B?PEGw@eTGXIyv0fgzrb zs0S(ImkT!Y8LMg*Bgbtc_?+LTQH2Kw*T*cdJ3HBa>j|U*bosiBr*1gwT3C7YS~T4$ za@Dp3$fVq1wugO>3WN9pgM?BmFD?2{aYlr5{aINuhxu${bV%VM*kzUTFq2rxV;b^)i<+mSN>iWx`NUR3a&SJqM>P7?udG~xHPD0$P20+p&926%FF3uEp`b{Yh0zAlVz15}IeF z7cbQMgJ3?6-olxpHHmtg?-%b8kXs3f;!m;R?dhxvrP+}&!D|wLGJN_ZNCI39WEy3y zd@&aLz)rx?g6s_W#i=rsTf%+&<^nvi+#elwU*seMVCHIS_{;|%0S>z=jao|{$jeV{ z{-gNx-*DOgDIn&`FojYxMIM7Ew2epQq5Ji0Isi4T*DS0=t`_@BvuZmwx}&f%f24+) zCDT!7e^wx&iNnw|{Rfto1bf%lN-^BF4oJD8%P-%0Hg|J}laS`;C2z-%FCZ(tLs+K$?>6xTw2OUg%% zyia^LVp8fh*!B+oTn(&syTr=hTX!rB*o!E$Hjj)|6-J1gx(>NEHHL8xayOr9ai8p9 z!fyk#ZRR`KMCQ2IRwa$SNmnuQLB^un2Vu<}m1gsq&=Sc+{Wps%8eKr_#gnUp*mwZC zaF@?o@IK2^O!|2EcQO9o@_5l2O-Jv!j&ii@-+bDZ7X=PWM=1FgZy4DJ_Se7mpI~tS zS%Bc;+umvJ$r#=T`>~SXVAa-}iuA6>G3$@lWih4+GWHsa?Ojj2nC0#10LGPe)%M|v zZ#@9Z9N^RwQ4Vml$S_x$O=?e*7c#d3doKJY&DAL~^@bFc6yBv4O=Ni`$fxp7eIppk zVIN_L5^A#m65z_B!scC8Tdnc9k2e?iGiaGx2))c;Y?Ug{V+$ZVlLI_;ll@@@8GzcF zBJe*WCH^WoPXFZgQisBljf@)inP@F!0DKj~di^1TFn{y6LxGgv;qFQ-XslRa2I zR05NYtf1KlpWK~nhsDJYLxMa+8;2yIUMcv6o0CGiw+0=);;T4Z)x&cDaQPX()%KAa9kP%1 zuP8|}wsa0fTq(nVzYQ8Lx=Nkr#a>tb{{S7)mok?p=cu+kspHudf%6xr55Ow267%?b zcKhm-m$S!v=lAi>I3k}5h%XO~PYmilfrxJZ^%|)Q48-MV+WaLib_?%r3gR@OnIE`y znDeyn*`V4kHb#ryE*==$S8b6V<)v71zLR&NVPyfN*Z9SJ%)`aBYlExBp0w}O`&QtS z1)EV&aB#3mcGjP`t-1U6nLg2}iy#}MPxUS&z}9xStiS+lfpfd3{d1f*<^Yrr&L4@N z&nIAIPvAuEpRfOkd3e0L<+?2Gv-8{YWMuUU3w26>(dd7Cy{0_b0aQfZPG@Wm-%_7r z>l-GT@vmi!_ef8oI)+zV=qIrJ&66$!^l56m?0*dBT7>g&9jYs@ zRI>EfxnGyRDrNF-q{S`Ve~6L^1d-Yq=oZ5<}N zSbtW&fhPS{56xX1KPs*MUVS(<1U*N|Uw>*EI##F>9dOdG+p--<4a@;l>L!54bSPqF z4s76OGrUTQg@Id6Que=X?5aa|f3fTxJFL7f9= z%~m%~UO`yf6xZJUWTgd%F?>?8h%OH*O>$AtIj=BUq|@|if3DR5!E4)+q2c;vM$~q* z&}HuY_MufV0(z!8{3Lc=Br@*}Z=Rpi0onj`4MBrC3$G&9Z!TE3@Agzp^3G(ATidmk zmo~}B%b0gNu-A8H6&fKT<-A|(m=6WqX<3nBne~JiK)b@`SL9_4WB@0iO6pgCLAG?P zL}adOLpqW=;H*(f^F=2~N)*aXg8C4u!cx>rK+iLN@StFI&q|fr;i|RT155S{sIvk@1oyCx=JzfN9g;3P59d3du z0Z#zFaZ}pE0MA>~cR_L*OwGor2nT-Orolvoz8tr`M(4gMsoZ8RO;PWciDd17;UDZX z^{0-@Txarsg&30Re?>|Lh&=Oq(zeiz7aAG_BVcovlYG=2s^ocw)rQ8{fr=czk#HgR z*xx!{`)>KUYp4->?3vI0cCl^kst`~`5>LuTC*`i3<*kwcn%5$?QF7Q=+Ga)b2WRxj zd+rENN$OZjlj-{hELQYtizO-1xIKoY!_g@@aTDJ4F*kk+k_y2H$AS-`7viJLn8)Av#^xNe3dnaPN{g;YLKnBTXV zG*Iuy08RL<`zb|x`wz|unZnQ$e~=X?tk5pCoEv7vxJ!fS7*$$|ojpZ5Cm7l)Z}{zJ zN420ZxGjh@AC7t3y|YAbYT)botV6-8m`CK2(LTn>GPJ$$Qw8;CF_@C?Krybd2p4Y^ zuE+k%6qBz}aGLbTT#t4jLIXXRk8^KVgiw#-X;*0^eT59aj(_gX>X@W>9;IE!df;h9 ztMV(m7&n8CXMf)$xhGGIrS$AL7UMNAXLn9&_NIY0Moc}o_Q;Wk{Fdrv;-|c|Wp0Yj zNId>r_c99c%i7uSV$QLil0?pFNFT_e-w(JODIsRI}K>d8PH0aVmA?wk$Tr=?M9d50;QhMz@;SF)4ODS*s=Zg@aPJ zI6L&}ND7n?k`L;&om-=a)A2kLThQ1R@IEX=ekrb=sSz*d^VBX$%1IHCr$VD?(_pV8 zQF~r--x<|jT3uJRV^pv$;d~b~G*y4s_FCGptNO5G@~ZmqCu5nCL~rbQ?+xb=DZz+E zWB#g+jky6O>@yVC3;^rAr$<(`3QLeu{Cj{*nkUP<$jRz&oP_{xcU=frHPwPGZqz@=RS`f_^1?3Gkn1>ZE;`3 zY6XBfY|>W{bi}xe_yo6L->|vz8K9f!jV0rqSIo9k{B3hn%eaY;n*tBb5Gi)FZ;DAR zRI&4(Fh~<8ccY#&+Fh5DjK|6UKu4-6v-(1SL8 zvA@7LvRxYIX+-HW?17@{MZAhi>p6p`gCji{)d%`7Nf7Qbxt-#LdlJQsU*VTC)h5y= z`%SeOc{#QT^X19i{A*KbAG}l6B0H)W{Zj+ajoK;_eMGrMk{H$7S1cb~kEqo*X6&oA zebm1sNxoWQl-(y7!?trTryZX7OgoC0qEwe8zw4)S-G{W^fQ|_W0V7_##S^o=&(h}o z?R)`;Wk`Sr2IdqoA~)?pKqo6}^fW{vPi;b#4C2xZ0=0{V)s_NJCG@se13l|1NOOHn zXG?Pz9Z>zhNA?RB0Grk*tSHSNf`+3foP0!zr(P?g4Cpq{YS~}<6G}p0LvG{t`uKn? zElouNd7h>^X!%^>om5}eA6E6F#b5L2_e8Jjt#O|M-WyzIf6-@iUA5;k_8+Wi|EZn- zN0?xN|He zo$qoih-nV6Yzj19TVb34=1>voVUZg|#{Y?4UHXjO8w2Bn<7&OF-syf8-L94 z(TXLYPP_ODN_PN|P8i2H*-)mKmWO%h#u`O|jZrkgz&PgSI1zY5=$Qd*Q&=3$iZXE4 zWMOfP0s4u>@K3SpNok!{o=bYqVK~QnKo(1H0T|&m7p2@ru;+8(yZ}5gkow{ll72xXflINHII}Xh;S) zzmn}qEq~5L{Y>x{hJ8Rt^c=fR+;X3EI#wC)y@$C?{q0)RfX7Wm@-;AP3|eJkbvGN3 zP!(E#YPX7GV*zaJD`3C^*ux(|#`Fp!{`4F4j#2SE*<>Yq#>W}Jtjiv?3>aU_cm|{!zNb_1oUKe| zg**~*EcrYU?-ZK2MFKqAjj&3(-$9}Y)20HXiPZnlg0dr|B!<5xok#@;(2kxffiMLC z&z}lk1sx&r1h#HSPNlG$J2Phjpa!MZNo)D4Hi?$-);WLFaAhd}jkc_!4} z;_ueBbw$Co^Fshe4$lh7xKE8Ba4>?cP)y4>jA1Vl`3UGqJCuN34>#;fxVW%x zbd^`&-4>?=L@%@Sb82!%`VV*h^h_HquSRe3kO{~C0Soih=0ixHG5X#>I)%ei%MR0!M37q0R2LdJ!(6fDdp7= zB+FeGH(W!&yr&0F>H41s($8_SLVh;D)mGAD`y#8Ebc*bqvm++lx)#_P2Og& zc5)Vual@sbhAn07N!E=yknrw0+kX;KWCjl~#cnUct-pE_cnzyA^Y%q{N5Cg~fZd8R zPI`@8Of)8VPObD7Do?k4L!a>Lk_I*b#0bU9|3WvOT};T=Y5f!ijkt$dp9LtSxp|55 zaK4@p7+cGF5??FSnk@`Td3S{gH}e+Pk#JFyvLYiN{ANxl%oacIPJCeazPo-6dX>@@ zf!@!iQo*cg2Z(S@RnIK%X?2uSSa;FJymfuLNomk|xv%J9jI4qJ4o30a^k{b0J4`&x z3d8RlZ@nefwkrI{B$fWu%BJKjL;}{v&5;xQMqP6Wa~@eB{#}jz5ATj^^Z&uSZ?MJ9 zo9RF)c!}cKu}*5A9Fidj|0@NbHe;#|nEl>v4gUB-FQWJJhN(xs$6IAmUo<=k339i< zE0E#{3l1jf@34Y6sfM2qe778l0W+oA_JaQIpM&M(`qH8vykn(#Mf@@FMhR>CrA{G+ z_Duw~%GTNAtj8IQ?9lm9v_jm30d}F@`XA~h%FA-O3Ru_wFB{JkF`?c44{KBKHW*#blju=_R>JAjnK5V7YI Date: Mon, 1 Jun 2026 08:26:27 +0200 Subject: [PATCH 05/13] Translations --- .../libquickeventgui/libquickeventgui-cs_CZ.ts | 12 ++++++------ .../libquickeventgui/src/reportoptionsdialog.ui | 4 ++-- .../app/quickevent/plugins/Runs/src/runswidget.cpp | 4 ++-- quickevent/app/quickevent/quickevent-cs_CZ.ts | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libquickevent/libquickeventgui/libquickeventgui-cs_CZ.ts b/libquickevent/libquickeventgui/libquickeventgui-cs_CZ.ts index 88d067503..748bc99f7 100644 --- a/libquickevent/libquickeventgui/libquickeventgui-cs_CZ.ts +++ b/libquickevent/libquickeventgui/libquickeventgui-cs_CZ.ts @@ -112,17 +112,17 @@ Only runners - Jen běžci + Pouze závodníci - Regular vacants - Běžné vakanty + Include vacants with time + Včetně vakantů s časem - All vacants - Všechny vakanty + Include all vacants + Včetně všech vakantů @@ -187,7 +187,7 @@ Use only class from selected start - Pooužít pouze kategorie z vybraného startu + Použít pouze kategorie z vybraného startu diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.ui b/libquickevent/libquickeventgui/src/reportoptionsdialog.ui index 68cbb5080..adf57d9d1 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.ui +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.ui @@ -357,12 +357,12 @@ - Regular vacants + Include vacants with time - All vacants + Include all vacants diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp index 45b59ded6..7405398df 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp @@ -283,12 +283,12 @@ void RunsWidget::settleDownInPartWidget(::PartWidget *part_widget) auto *m_export_stlist_xml = m_stlist->addMenuInto("xml", tr("&XML")); { { - auto *a = new qfw::Action(tr("&IOF-XML 3.0")); + auto *a = new qfw::Action(tr("&IOF-XML 3.0 only runners")); connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners); }); m_export_stlist_xml->addActionInto(a); } { - auto *a = new qfw::Action(tr("IOF-XML 3.0 with ®ular vacants")); + auto *a = new qfw::Action(tr("IOF-XML 3.0 with &vacants with time")); connect(a, &qfw::Action::triggered, this, [this]() { export_startList_stage_iofxml30(quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants); }); m_export_stlist_xml->addActionInto(a); } diff --git a/quickevent/app/quickevent/quickevent-cs_CZ.ts b/quickevent/app/quickevent/quickevent-cs_CZ.ts index d2458e027..ee77fa757 100644 --- a/quickevent/app/quickevent/quickevent-cs_CZ.ts +++ b/quickevent/app/quickevent/quickevent-cs_CZ.ts @@ -6896,13 +6896,13 @@ Stskněte tlačítko pro obnovení pro zobrazení importovaných dat. - &IOF-XML 3.0 - &IOF XML 3.0 + &IOF-XML 3.0 only runners + &IOF XML 3.0 pouze závodníci - IOF-XML 3.0 with &regular vacants - IOF XML 3.0 s &běžnými vakanty + IOF-XML 3.0 with &vacants with time + IOF XML 3.0 s &vakanty s časem From 7542baef6409c2fcee3c3a6ea36c83800e8a80b3 Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 08:49:34 +0200 Subject: [PATCH 06/13] Fix the XML report and remove accidentally added files. Perform a review --- .../src/reportoptionsdialog.cpp | 4 +- patch_runsplugin.py | 153 ---------------- patch_runsplugin_sort.py | 119 ------------ patch_runsplugin_starters.py | 173 ------------------ patch_runsplugin_starters_robust.py | 42 ----- patch_runsplugin_xml.py | 140 -------------- .../plugins/Runs/src/runsplugin.cpp | 36 ++-- 7 files changed, 22 insertions(+), 645 deletions(-) delete mode 100644 patch_runsplugin.py delete mode 100644 patch_runsplugin_sort.py delete mode 100644 patch_runsplugin_starters.py delete mode 100644 patch_runsplugin_starters_robust.py delete mode 100644 patch_runsplugin_xml.py diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp index 08ad9bf6d..63865e627 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp @@ -379,7 +379,7 @@ void ReportOptionsDialog::setStartListForRelays() void ReportOptionsDialog::setCurrentStageId(int stageId) { ui->cbxStartNumber->clear(); - ui->cbxStartNumber->addItem(QString("All"), 0); + ui->cbxStartNumber->addItem(tr("All"), 0); QString query_str; if (stageId > 0) { query_str = QString("SELECT DISTINCT codes.code FROM codes, coursecodes, classdefs" @@ -394,7 +394,7 @@ void ReportOptionsDialog::setCurrentStageId(int stageId) while (q.next()) { auto code = q.value(0).toInt(); if(auto n = core::CodeDef::codeToStartNumber(code); n.has_value()) { - ui->cbxStartNumber->addItem(QString("Start %1").arg(n.value()), n.value()); + ui->cbxStartNumber->addItem(tr("Start %1").arg(n.value()), n.value()); } } } diff --git a/patch_runsplugin.py b/patch_runsplugin.py deleted file mode 100644 index 31c745963..000000000 --- a/patch_runsplugin.py +++ /dev/null @@ -1,153 +0,0 @@ -import re - -path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' -with open(path, 'r', encoding='utf-8') as f: - content = f.read() - -# 1. startListClassesTable signature -content = content.replace( - 'qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const bool insert_vacants, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format)', - 'qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &where_expr, const quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format)' -) - -# 2. Add vacants fields to query -content = content.replace( - '.select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin")', - '.select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter")' -) - -# 3. Vacants logic -old_vacants_logic = ''' if(start_interval > 0 && insert_vacants) { - for(int j=0; j 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { - for(int j=0; j 0) ? cnt / vacantEvery : 0; - int total_vacants = vacantsBefore + vacants + vacantsAfter; - for (int k = 0; k < total_vacants; ++k) { - int ix = tt2.appendRow(); - qf::core::utils::TreeTableRow tt2_row = tt2.row(ix); - tt2_row.setValue(QStringLiteral("startTimeMs"), QVariant()); - tt2_row.setValue(QStringLiteral("competitorName"), "---"); - tt2_row.setValue(QStringLiteral("registration"), QString()); - tt2_row.setValue(QStringLiteral("siId"), 0); - tt2_row.setValue(QStringLiteral("startNumber"), 0); - tt2.setRow(ix, tt2_row); - } - }''' - -content = content.replace(old_vacants_logic, new_vacants_logic) - -# 4. XML 3.0 Export -content = content.replace( - 'QString RunsPlugin::startListStageIofXml30(int stage_id, bool with_vacants)', - 'QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' -) - -content = content.replace( - 'bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, bool with_vacants)', - 'bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' -) - -content = content.replace( - 'QString str = startListStageIofXml30(stage_id, with_vacants);', - 'QString str = startListStageIofXml30(stage_id, vacants_option);' -) - -content = content.replace( - 'auto tt1 = startListClassesTable("", with_vacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart);', - 'auto tt1 = startListClassesTable("", vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart);' -) - - -# 5. UI calls mappings -content = content.replace( - 'auto tt = startListClassesTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.isStartListPrintVacants(), dlg.startTimeFormat());', - 'auto tt = startListClassesTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption(), dlg.startTimeFormat());' -) - -content = content.replace( - 'qf::core::utils::TreeTable tt1 = startListClassesTable("", false, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', - 'qf::core::utils::TreeTable tt1 = startListClassesTable("", quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' -) - -content = content.replace( - 'auto tt1 = startListClassesTable(sql_where, true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', - 'auto tt1 = startListClassesTable(sql_where, quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' -) - -content = content.replace( - 'auto tt1 = startListClassesTable("",true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);', - 'auto tt1 = startListClassesTable("", quickevent::gui::ReportOptionsDialog::VacantsOption::RegularVacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime);' -) - -with open(path, 'w', encoding='utf-8') as f: - f.write(content) diff --git a/patch_runsplugin_sort.py b/patch_runsplugin_sort.py deleted file mode 100644 index 18a194cc0..000000000 --- a/patch_runsplugin_sort.py +++ /dev/null @@ -1,119 +0,0 @@ -import sys - -path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' -with open(path, 'r', encoding='utf-8') as f: - content = f.read() - -# 1. Fix appendVacantsToClassTable -old_append = '''void RunsPlugin::appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) -{ - if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) {''' -new_append = '''void RunsPlugin::appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) -{ - int start_time_0 = tt_row.value(QStringLiteral("startTimeMin")).toInt() * 60 * 1000; - int start_time_last = tt_row.value(QStringLiteral("lastStartTimeMin")).toInt() * 60 * 1000; - int start_interval = tt_row.value(QStringLiteral("startIntervalMin")).toInt() * 60 * 1000; - if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) {''' -content = content.replace(old_append, new_append) - -# 2. Fix startListClassesTable where I forgot to remove the variables -content = content.replace( -''' for(int i=0; i rowsData; - - for(int i=0; i()->stageStartDateTime(stage_id).toSecsSinceEpoch();\n\n\tauto tt_classes', 'auto tt_classes') - -with open(path, 'w', encoding='utf-8') as f: - f.write(content) diff --git a/patch_runsplugin_starters.py b/patch_runsplugin_starters.py deleted file mode 100644 index 4b91430a0..000000000 --- a/patch_runsplugin_starters.py +++ /dev/null @@ -1,173 +0,0 @@ -import sys - -path = 'quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp' -with open(path, 'r', encoding='utf-8') as f: - content = f.read() - -# 1. Update startListStartersTable signature -content = content.replace( - 'qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr)', - 'qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option)' -) - -# 2. Extract appendVacantsToClassTable -old_vacants_logic = ''' if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { - for(int j=0; j()->currentStageId()));', 'auto tt = startListStartersTable(dlg.sqlWhereExpression(getPlugin()->currentStageId()), dlg.startListPrintVacantsOption());') -content = content.replace('dlg.setStartListPrintVacantsVisible(false);', 'dlg.setStartListPrintVacantsVisible(true);') - -# 5. Redefine startListStartersTable to flatten from startListClassesTable -old_starters = '''qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) -{ - int stage_id = selectedStageId(); - auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); - - qfs::QueryBuilder qb; - qb.select2("competitors", "registration, id, startNumber, country") - .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") - .select("COALESCE(runs.startTimeMs / 1000 / 60, 0) AS startTimeMin") - .select2("runs", "siId, startTimeMs") - .select2("classes", "name") - .from("competitors") - .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning", "INNER JOIN") - .join("competitors.classId", "classes.id") - .orderBy("runs.startTimeMs, classes.name, competitors.lastName");//.limit(1); - if(!where_expr.isEmpty()) - qb.where(where_expr); - QVariantMap qpm; - qpm["stage_id"] = stage_id; - qf::gui::model::SqlTableModel m; - m.setQueryBuilder(qb); - m.setQueryParameters(qpm); - m.reload(); - auto tt = m.toTreeTable(); - addStartTimeTextToClass(tt,start00_epoch_sec, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); - tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); - tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); - return tt; -}''' - -new_starters = '''qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) -{ - int stage_id = selectedStageId(); - auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); - - auto tt_classes = startListClassesTable(where_expr, vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); - - qf::core::utils::TreeTable tt; - tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); - tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); - - tt.appendColumn("competitors.registration", QMetaType(QMetaType::QString)); - tt.appendColumn("competitors.id", QMetaType(QMetaType::Int)); - tt.appendColumn("competitors.startNumber", QMetaType(QMetaType::Int)); - tt.appendColumn("competitors.country", QMetaType(QMetaType::QString)); - tt.appendColumn("competitorName", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeMin", QMetaType(QMetaType::Int)); - tt.appendColumn("runs.siId", QMetaType(QMetaType::Int)); - tt.appendColumn("startTimeMs", QMetaType(QMetaType::Int)); - tt.appendColumn("classes.name", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeText", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeMsText", QMetaType(QMetaType::QString)); - - for(int i=0; i rowsData; + rowsData.reserve(tt_classes.rowCount() * 20); for(int i=0; i::max(); + int tB = vB.isValid() && !vB.isNull() ? vB.toInt() : std::numeric_limits::max(); if (tA != tB) return tA < tB; QString cA = a["classes.name"].toString(); QString cB = b["classes.name"].toString(); @@ -2526,7 +2530,7 @@ void RunsPlugin::export_resultsHtmlNStages() append_list(table, trr); auto time_to_str = [](int msec) { if(msec == UNREAL_TIME_MSEC) - return QStringLiteral("---"); + return vacant_name_sentinel; quickevent::core::og::TimeMs t(msec); return t.toString(); }; @@ -2805,7 +2809,7 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report auto tt2_row = tt2.row(j); QVariantList xml_person{"PersonStart"}; - bool is_vacant = tt2_row.value(QStringLiteral("competitorName")).toString() == "---"; + bool is_vacant = tt2_row.value(QStringLiteral("competitorName")).toString() == vacant_name_sentinel; if (!is_vacant) { QVariantList person{"Person"}; @@ -2829,9 +2833,9 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report QVariantList xml_start{"Start", (iof_xml_race_number != 0) ? QVariantMap{{"raceNumber", iof_xml_race_number}} : QVariantMap{}}; auto bib_number = tt2_row.value(QStringLiteral("competitors.startNumber")); - if(!bib_number.isNull()) + if(!is_vacant && !bib_number.isNull()) append_list(xml_start, QVariantList{"BibNumber", bib_number}); - if (has_fixed_start_time) { + if (has_fixed_start_time || is_vacant) { int stime_msec = tt2_row.value("startTimeMs").toInt(); append_list(xml_start, QVariantList{"StartTime", datetime_to_string(start00.addMSecs(stime_msec))}); } From 5cdbcb0c25960c4a0a5e107c8872f29ac184d842 Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 08:56:30 +0200 Subject: [PATCH 07/13] Fix a preexisting bug where runners in free classes didn't get starttime in XML --- quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 643ba8080..18aa896a6 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -2835,10 +2835,10 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report auto bib_number = tt2_row.value(QStringLiteral("competitors.startNumber")); if(!is_vacant && !bib_number.isNull()) append_list(xml_start, QVariantList{"BibNumber", bib_number}); - if (has_fixed_start_time || is_vacant) { - int stime_msec = tt2_row.value("startTimeMs").toInt(); - append_list(xml_start, QVariantList{"StartTime", datetime_to_string(start00.addMSecs(stime_msec))}); - } + + int stime_msec = tt2_row.value(QStringLiteral("startTimeMs")).toInt(); + append_list(xml_start, QVariantList{"StartTime", datetime_to_string(start00.addMSecs(stime_msec))}); + QVariant siId = tt2_row.value(QStringLiteral("runs.siId")); if (siId.toBool()) { append_list(xml_start, QVariantList{"ControlCard", siId.toInt()}); From edcc26a54c6b2f7905b8b7f76fe4b1439966396a Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 08:58:49 +0200 Subject: [PATCH 08/13] Remove unused var --- quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 18aa896a6..c8281bf22 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -2803,8 +2803,6 @@ QString RunsPlugin::startListStageIofXml30(int stage_id, quickevent::gui::Report qf::core::utils::TreeTable tt2 = tt1_row.table(); if (tt2.rowCount() == 0 && is_iof_race) continue; // not save empty class - bool has_fixed_start_time = tt1_row.value(QStringLiteral("classdefs.startIntervalMin")).toInt() > 0 - || event_config->discipline() == Event::EventConfig::Discipline::MassStart; for(int j=0; j Date: Mon, 1 Jun 2026 09:22:15 +0200 Subject: [PATCH 09/13] Minimize the diffs --- .../plugins/Runs/src/runsplugin.cpp | 122 +++++++++--------- .../quickevent/plugins/Runs/src/runsplugin.h | 1 - 2 files changed, 60 insertions(+), 63 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index c8281bf22..35ea57108 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1488,7 +1488,61 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher m2.setQueryParameters(qpm); m2.reload(); auto tt2 = m2.toTreeTable(); - appendVacantsToClassTable(tt2, tt_row, vacants_option); + // // TODO: extract to method + { + int start_time_0 = tt_row.value(QStringLiteral("startTimeMin")).toInt() * 60 * 1000; + int start_time_last = tt_row.value(QStringLiteral("lastStartTimeMin")).toInt() * 60 * 1000; + int start_interval = tt_row.value(QStringLiteral("startIntervalMin")).toInt() * 60 * 1000; + if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + for(int j=0; j 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { - for(int j=0; j rowsData; - rowsData.reserve(tt_classes.rowCount() * 20); for(int i=0; i::max(); - int tB = vB.isValid() && !vB.isNull() ? vB.toInt() : std::numeric_limits::max(); + // // TODO: improve it + int tA = a["startTimeMs"].toInt(); + int tB = b["startTimeMs"].toInt(); + if (tA == 0) tA = std::numeric_limits::max(); + if (tB == 0) tB = std::numeric_limits::max(); if (tA != tB) return tA < tB; QString cA = a["classes.name"].toString(); QString cB = b["classes.name"].toString(); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h index d6c66d92d..3df7c594f 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h @@ -96,7 +96,6 @@ class RunsPlugin : public qf::gui::framework::Plugin QVariantMap startListRecord(int run_id); qf::core::utils::TreeTable startListClassesTable(const QString &where_expr, const quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); - void appendVacantsToClassTable(qf::core::utils::TreeTable &tt2, const qf::core::utils::TreeTableRow &tt_row, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); qf::core::utils::TreeTable startListClubsTable(const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format, const quickevent::gui::ReportOptionsDialog::StartlistOrderFirstBy order_first_by); qf::core::utils::TreeTable startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option); qf::core::utils::TreeTable startListClassesNStagesTable(const int stages_count, const QString &where_expr, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format); From 9d948f3b3eb83b67eacabab8d990afbb4fe62c41 Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 09:31:53 +0200 Subject: [PATCH 10/13] Unindent to minimize diff. --- .../plugins/Runs/src/runsplugin.cpp | 100 +++++++++--------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 35ea57108..471a424f0 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1488,59 +1488,57 @@ qf::core::utils::TreeTable RunsPlugin::startListClassesTable(const QString &wher m2.setQueryParameters(qpm); m2.reload(); auto tt2 = m2.toTreeTable(); - // // TODO: extract to method - { - int start_time_0 = tt_row.value(QStringLiteral("startTimeMin")).toInt() * 60 * 1000; - int start_time_last = tt_row.value(QStringLiteral("lastStartTimeMin")).toInt() * 60 * 1000; - int start_interval = tt_row.value(QStringLiteral("startIntervalMin")).toInt() * 60 * 1000; - if(start_interval > 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { - for(int j=0; j 0 && vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + for(int j=0; j Date: Mon, 1 Jun 2026 09:47:34 +0200 Subject: [PATCH 11/13] Clamp vacant options --- libquickevent/libquickeventgui/src/reportoptionsdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp index 63865e627..55dbc47df 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.cpp @@ -119,7 +119,9 @@ ReportOptionsDialog::BreakType ReportOptionsDialog::breakType() const ReportOptionsDialog::VacantsOption ReportOptionsDialog::startListPrintVacantsOption() const { - return static_cast(ui->cbxStartOpts_PrintVacants->currentIndex()); + int ix = ui->cbxStartOpts_PrintVacants->currentIndex(); + if (ix < 0) ix = 0; + return static_cast(ix); } bool ReportOptionsDialog::isStartListPrintStartNumbers() const From 1c75fee86d9e12050f3893cde6077c08cc8ee4b0 Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Mon, 1 Jun 2026 23:08:27 +0200 Subject: [PATCH 12/13] Try reverting back to the previous version of starters start list to keep changes to the minimum --- .../plugins/Runs/src/runsplugin.cpp | 163 ++++++++++-------- 1 file changed, 95 insertions(+), 68 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 471a424f0..a63654765 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -44,6 +44,8 @@ #include #include +#include +#include namespace qff = qf::gui::framework; namespace qfu = qf::core::utils; @@ -1627,81 +1629,106 @@ qf::core::utils::TreeTable RunsPlugin::startListClubsTable(const quickevent::gui qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &where_expr, quickevent::gui::ReportOptionsDialog::VacantsOption vacants_option) { int stage_id = selectedStageId(); - auto tt_classes = startListClassesTable(where_expr, vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); - qf::core::utils::TreeTable tt; - tt.setValue("stageId", stage_id); - tt.setValue("event", getPlugin()->eventConfig()->value("event")); - tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); + // 1. Fetch real runners natively (zero mapping boilerplate) + qfs::QueryBuilder qb; + qb.select2("competitors", "registration, id, startNumber, country") + .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") + .select("COALESCE(runs.startTimeMs / 1000 / 60, 0) AS startTimeMin") + .select2("runs", "siId, startTimeMs") + .select2("classes", "name") + .from("competitors") + .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning", "INNER JOIN") + .join("competitors.classId", "classes.id"); - tt.appendColumn("competitors.registration", QMetaType(QMetaType::QString)); - tt.appendColumn("competitors.id", QMetaType(QMetaType::Int)); - tt.appendColumn("competitors.startNumber", QMetaType(QMetaType::Int)); - tt.appendColumn("competitors.country", QMetaType(QMetaType::QString)); - tt.appendColumn("competitorName", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeMin", QMetaType(QMetaType::Int)); - tt.appendColumn("runs.siId", QMetaType(QMetaType::Int)); - tt.appendColumn("startTimeMs", QMetaType(QMetaType::Int)); - tt.appendColumn("classes.name", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeText", QMetaType(QMetaType::QString)); - tt.appendColumn("startTimeMsText", QMetaType(QMetaType::QString)); - - QList rowsData; - - for(int i=0; i::max(); - if (tB == 0) tB = std::numeric_limits::max(); - if (tA != tB) return tA < tB; - QString cA = a["classes.name"].toString(); - QString cB = b["classes.name"].toString(); - if (cA != cB) return cA < cB; - return a["competitorName"].toString() < b["competitorName"].toString(); - }); + // 2. Extract and append vacants + if (vacants_option != quickevent::gui::ReportOptionsDialog::VacantsOption::OnlyRunners) { + auto tt_classes = startListClassesTable(where_expr, vacants_option, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + + for(int i=0; i start_list; + start_list.reserve(tt.rowCount()); + for(int i=0; i::max(); + } + if (tB == 0) { + tB = std::numeric_limits::max(); + } + + if (tA != tB) { + return tA < tB; } - tt.setRow(ix, tt_row); + + bool a_is_vacant = a.value("competitorName").toString() == vacant_name_sentinel; + bool b_is_vacant = b.value("competitorName").toString() == vacant_name_sentinel; + if (a_is_vacant != b_is_vacant) { + return !a_is_vacant; // Real runners before vacants + } + + QString cA = a.value("classes.name").toString(); + QString cB = b.value("classes.name").toString(); + if (cA != cB) { + return cA < cB; + } + + return a.value("competitorName").toString() < b.value("competitorName").toString(); + }); + + QVariantMap map = tt.toVariant().toMap(); + QVariantList sorted_rows; + sorted_rows.reserve(start_list.size()); + for(const auto &r : start_list) { + sorted_rows.append(r.row()); } + map[qf::core::utils::TreeTable::KEY_ROWS] = sorted_rows; + tt = qf::core::utils::TreeTable(map); + + // 4. Format time text globally + addStartTimeTextToClass(tt, start00_epoch_sec, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + + tt.setValue("stageId", stage_id); + tt.setValue("event", getPlugin()->eventConfig()->value("event")); + tt.setValue("stageStart", getPlugin()->stageStartDateTime(stage_id)); return tt; } From 64f86470b34a6c49f6e0817faffea742df3a554a Mon Sep 17 00:00:00 2001 From: Lukas Jezek Date: Tue, 2 Jun 2026 06:54:04 +0200 Subject: [PATCH 13/13] Simplify the starterslist to the previous version --- .../app/quickevent/plugins/Runs/src/runsplugin.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index a63654765..56589663c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1667,11 +1667,11 @@ qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &whe auto tt2_row = tt2.row(j); if (tt2_row.value("competitorName").toString() == vacant_name_sentinel) { int ix = tt.appendRow(); - auto run_record = tt.row(ix); - run_record.setValue("classes.name", class_name); - run_record.setValue("competitorName", vacant_name_sentinel); - run_record.setValue("startTimeMs", tt2_row.value("startTimeMs")); - tt.setRow(ix, run_record); + auto vacant_record = tt.row(ix); + vacant_record.setValue("classes.name", class_name); + vacant_record.setValue("competitorName", vacant_name_sentinel); + vacant_record.setValue("startTimeMs", tt2_row.value("startTimeMs")); + tt.setRow(ix, vacant_record); } } }