diff --git a/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.cpp b/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.cpp index 85d600258..d0a72b565 100644 --- a/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.cpp +++ b/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.cpp @@ -14,8 +14,9 @@ namespace quickevent { namespace core { namespace exporters { -StageResultsCsvExporter::StageResultsCsvExporter(QObject *parent) - : Super(parent) +StageResultsCsvExporter::StageResultsCsvExporter(bool is_iof_race, QObject *parent) + : Super(parent), + m_isIofRace(is_iof_race) { } @@ -52,7 +53,13 @@ void StageResultsCsvExporter::exportClasses(bool single_file) csv_dir.mkpath(sub_dir); if (single_file) { - QFile f_csv(csv_dir.absolutePath() + '/' + sub_dir + '/' + "results.csv"); + QString file_name = outFile(); + if (file_name.isEmpty()) + file_name = "results.csv"; + QString path = csv_dir.absolutePath() +'/'; + if (!simplePath()) + path += sub_dir + '/'; + QFile f_csv(path + file_name); qfInfo() << "Generating:" << f_csv.fileName(); if (!f_csv.open(QFile::WriteOnly)) qfError() << "Cannot open file" << f_csv.fileName() + "for writing."; @@ -102,17 +109,18 @@ void StageResultsCsvExporter::exportClass(int class_id, QTextStream &csv) QString qs = qb.toString(); qs.replace("{{stage_id}}", QString::number(currentStage())); qs.replace("{{class_id}}", QString::number(class_id)); + QString with_dns = (withDidNotStart()) ? "" : " AND runs.finishTimeMs>0"; qf::core::sql::Query q = execSql(qs); if(q.next()) { QString class_name = q.value("classes.name").toString(); qf::core::sql::QueryBuilder qb2; - qb2.select2("competitors", "registration, lastName, firstName, country, club") + qb2.select2("competitors", "registration, lastName, firstName, country, club, iofId, startNumber, licence") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .select2("runs", "*") .select2("clubs","name, abbr") .from("competitors") .join("LEFT JOIN clubs ON substr(competitors.registration, 1, 3) = clubs.abbr") - .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning AND runs.finishTimeMs>0", "JOIN") + .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning"+with_dns, "JOIN") .where("competitors.classId={{class_id}}") .orderBy("runs.notCompeting, runs.disqualified, runs.timeMs"); QString qs2 = qb2.toString(); @@ -124,11 +132,12 @@ void StageResultsCsvExporter::exportClass(int class_id, QTextStream &csv) int prev_time_ms = 0; QString spos; // keep last number when same time while(q2.next()) { - pos++; auto run_status = quickevent::core::RunStatus::fromQuery(q2); int time_ms = q2.value(QStringLiteral("timeMs")).toInt(); QString stime = og::TimeMs(time_ms).toString('.'); - if(run_status.isOk()) { + bool is_finish = q2.value(QStringLiteral("finishTimeMs")).toInt() > 0; + if(run_status.isOk() && is_finish) { + pos++; if(time_ms != prev_time_ms) spos = QString::number(pos); } @@ -143,13 +152,37 @@ void StageResultsCsvExporter::exportClass(int class_id, QTextStream &csv) if (club.isEmpty()) club = q2.value("competitors.registration").toString().left(3); } + + if (m_isIofRace) + csv << q2.value("competitors.iofId").toString() << m_separator; + else + csv << q2.value("competitors.registration").toString() << m_separator; + csv << q2.value("competitors.startNumber").toString() << m_separator; csv << class_name << m_separator; csv << spos << m_separator; csv << q2.value("competitorName").toString() << m_separator; csv << club << m_separator; - csv << q2.value("competitors.country").toString() << m_separator; + if (m_isIofRace) { + QString country_abbr; + qf::core::sql::QueryBuilder qb3; + qb3.select2("clubs","name, abbr") + .from("competitors") + .join("LEFT JOIN clubs ON competitors.country = clubs.name"); + qf::core::sql::Query q3 = execSql(qb3.toString()); + if(q3.next()) + country_abbr = q2.value("clubs.abbr").toString(); + + csv << q2.value("competitors.country").toString() << m_separator; + csv << country_abbr << m_separator; + } + else { + csv << q2.value("competitors.licence").toString() << m_separator; + csv << q2.value("competitors.iofId").toString() << m_separator; + csv << q2.value("competitors.registration").toString().left(3) << m_separator; + } csv << stime << m_separator; - csv << run_status.toHtmlExportString(); + QString rs = run_status.toHtmlExportString(); + csv << ((rs == "OK" && !is_finish) ? "ERR" : rs); // older races without proper run flags csv << Qt::endl; } } @@ -157,11 +190,24 @@ void StageResultsCsvExporter::exportClass(int class_id, QTextStream &csv) void StageResultsCsvExporter::exportCsvHeader(QTextStream &csv) { + if (m_isIofRace) + csv << "IofId" << m_separator; + else + csv << "Reg" << m_separator; + csv << "Bib" << m_separator; csv << "Class" << m_separator; csv << "Position" << m_separator; csv << "Name" << m_separator; csv << "Club" << m_separator; - csv << "Country" << m_separator; + if (m_isIofRace) { + csv << "Country" << m_separator; + csv << "CountryAbbr" << m_separator; + } + else { + csv << "Lic" << m_separator; + csv << "IofId" << m_separator; + csv << "ClubAbbr" << m_separator; + } csv << "Time" << m_separator; csv << "Status"; csv << Qt::endl; diff --git a/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.h b/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.h index b2ad68df9..704c07bee 100644 --- a/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.h +++ b/libquickevent/libquickeventcore/src/exporters/stageresultscsvexporter.h @@ -15,7 +15,11 @@ class QUICKEVENTCORE_DECL_EXPORT StageResultsCsvExporter : public FileExporter using Super = FileExporter; public: - StageResultsCsvExporter(QObject *parent = nullptr); + QF_PROPERTY_IMPL(QString, o, O, utFile) + QF_PROPERTY_IMPL(bool, s, S, implePath) + QF_PROPERTY_IMPL(bool, w, W, ithDidNotStart) +public: + StageResultsCsvExporter(bool is_iof_race = false, QObject *parent = nullptr); void generateCsvMulti(); void generateCsvSingle(); void setSeparator(QChar sep) { m_separator = sep; } @@ -25,6 +29,7 @@ class QUICKEVENTCORE_DECL_EXPORT StageResultsCsvExporter : public FileExporter void exportCsvHeader(QTextStream &csv); private: QChar m_separator = ';'; + bool m_isIofRace = false; }; }}} diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 667c07c8a..b9b6c1edc 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -68,6 +68,7 @@ add_executable(quickevent plugins/Oris/src/orisimporter.cpp plugins/Oris/src/orisplugin.cpp plugins/Oris/src/txtimporter.cpp + plugins/Oris/src/xmlimporter.cpp plugins/Receipts/src/receiptsplugin.cpp plugins/Receipts/src/receiptsprinter.cpp #plugins/Receipts/src/receiptsprinteroptions.cpp diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardcheckerfreeordercpp.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardcheckerfreeordercpp.cpp index 0625b8acd..d21853301 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardcheckerfreeordercpp.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardcheckerfreeordercpp.cpp @@ -124,7 +124,6 @@ quickevent::core::si::CheckedCard CardCheckerFreeOrderCpp::checkCard(const quick } error_mis_punch = !map_of_control_codes.isEmpty(); - checked_punches = map_of_control_codes.values(); checked_card.setMisPunch(error_mis_punch); quickevent::core::si::CheckedPunch finish_punch; diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 4e22c5e4f..c682b7e5a 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -86,11 +87,11 @@ class Model : public quickevent::core::og::SqlTableModel col_classes_name, col_competitorName, col_competitors_registration, + col_competirors_bib, col_runs_startTimeMs, col_runs_timeMs, col_runs_finishTimeMs, - col_runs_misPunch, - col_runs_disqualified, + col_runFlags, col_runs_cardLent, col_runs_cardReturned, col_cards_checkTime, @@ -102,7 +103,11 @@ class Model : public quickevent::core::og::SqlTableModel public: explicit Model(QObject *parent); + int columnCount(const QModelIndex &) const override { return col_COUNT; } QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + + using Super::value; + QVariant value(int row_ix, int column_ix) const Q_DECL_OVERRIDE; }; Model::Model(QObject *parent) @@ -114,12 +119,12 @@ Model::Model(QObject *parent) setColumn(col_classes_name, ColumnDefinition("classes.name", tr("Class"))); setColumn(col_competitorName, ColumnDefinition("competitorName", tr("Name"))); setColumn(col_competitors_registration, ColumnDefinition("competitors.registration", tr("Reg"))); + setColumn(col_competirors_bib, ColumnDefinition("competitors.startNumber", tr("Bib"))); setColumn(col_runs_startTimeMs, ColumnDefinition("runs.startTimeMs", tr("Start")).setCastType(qMetaTypeId()).setReadOnly(true)); setColumn(col_runs_timeMs, ColumnDefinition("runs.timeMs", tr("Time")).setCastType(qMetaTypeId()).setReadOnly(true)); setColumn(col_runs_finishTimeMs, ColumnDefinition("runs.finishTimeMs", tr("Finish")).setCastType(qMetaTypeId()).setReadOnly(true)); - setColumn(col_runs_misPunch, ColumnDefinition("runs.misPunch", tr("Error")).setToolTip(tr("Card mispunch")).setReadOnly(true)); - setColumn(col_runs_disqualified, ColumnDefinition("runs.disqualified", tr("DISQ")).setToolTip(tr("Disqualified"))); - setColumn(col_runs_cardLent, ColumnDefinition("cardLent", tr("RT")).setToolTip(tr("Card in rent table")).setReadOnly(true).setCastType(QMetaType::Bool)); + setColumn(col_runFlags, ColumnDefinition("runFlags", tr("Run flags")).setReadOnly(true)); + setColumn(col_runs_cardLent, ColumnDefinition("cardLent", tr("RT")).setToolTip(tr("Card in rent table")).setReadOnly(true).setCastType(QVariant::Bool)); setColumn(col_runs_cardReturned, ColumnDefinition("runs.cardReturned", tr("R")).setToolTip(tr("Card returned"))); setColumn(col_cards_checkTime, ColumnDefinition("cards.checkTime", tr("CTIME")).setToolTip(tr("Card check time")).setReadOnly(true)); setColumn(col_cards_startTime, ColumnDefinition("cards.startTime", tr("STIME")).setToolTip(tr("Card start time")).setReadOnly(true)); @@ -160,6 +165,45 @@ QVariant Model::data(const QModelIndex &index, int role) const } return Super::data(index, role); } + + +QVariant Model::value(int row_ix, int column_ix) const +{ + if(column_ix == col_runFlags) { + qf::core::utils::TableRow row = tableRow(row_ix); + bool is_disqualified = row.value(QStringLiteral("runs.disqualified")).toBool(); + bool mis_punch = row.value(QStringLiteral("runs.misPunch")).toBool(); + bool bad_check = row.value(QStringLiteral("runs.badCheck")).toBool(); + bool not_start = row.value(QStringLiteral("runs.notStart")).toBool(); + bool not_finish = row.value(QStringLiteral("runs.notFinish")).toBool(); + bool is_disqualified_by_organizer = row.value(QStringLiteral("runs.disqualifiedByOrganizer")).toBool(); + bool over_time = row.value(QStringLiteral("runs.overTime")).toBool(); + bool not_competing = row.value(QStringLiteral("runs.notCompeting")).toBool(); + QStringList sl; + if(not_competing) + sl << tr("NC", "NotCompeting"); + if(mis_punch) + sl << tr("MP", "MisPunch"); + if(bad_check) + sl << tr("BC", "BadCheck"); + if(not_start) + sl << tr("DNS", "DidNotStart"); + if(not_finish) + sl << tr("DNF", "DidNotFinish"); + if(is_disqualified_by_organizer) + sl << tr("DO", "disqualifiedByOrganizer"); + if(over_time) + sl << tr("OT", "OverTime"); + if(is_disqualified && !mis_punch && !bad_check && !not_start && !not_finish && !is_disqualified_by_organizer && !over_time) + sl << tr("DSQ", "Disqualified"); + if(sl.isEmpty()) + return QStringLiteral(""); + else + return sl.join(','); + } + return Super::value(row_ix, column_ix); +} + } CardReaderWidget::CardReaderWidget(QWidget *parent) @@ -205,6 +249,17 @@ CardReaderWidget::CardReaderWidget(QWidget *parent) } ui->tblCards->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tblCards, &qfw::TableView::customContextMenuRequested, this, &CardReaderWidget::onCustomContextMenuRequest); + + connect(ui->tblCards, &qfw::TableView::editCellRequest, this, [this](QModelIndex index) { + auto col = index.column(); + if(col == Model::col_runFlags) { + Runs::RunFlagsDialog dlg(this); + dlg.load(m_cardsModel, ui->tblCards->toTableModelRowNo(ui->tblCards->currentIndex().row())); + if(dlg.exec()) { + dlg.save(); + } + } + }, Qt::QueuedConnection); } CardReaderWidget::~CardReaderWidget() @@ -407,11 +462,12 @@ void CardReaderWidget::reload() int current_stage = getPlugin()->currentStageId(); qfs::QueryBuilder qb; qb.select2("cards", "id, siId, runId, checkTime, startTime, finishTime, runIdAssignError") - .select2("runs", "id, startTimeMs, timeMs, finishTimeMs, misPunch, disqualified, cardReturned") - .select2("competitors", "registration") + .select2("runs", "id, startTimeMs, timeMs, finishTimeMs, misPunch, disqualified, badCheck, notStart, notFinish, disqualifiedByOrganizer, overTime, notCompeting, cardReturned") + .select2("competitors", "registration, startNumber") .select2("classes", "name") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .select("lentcards.siid IS NOT NULL OR runs.cardLent AS cardLent") + .select("'' AS runFlags") .from("cards") .joinRestricted("cards.siId", "lentcards.siid", "NOT lentcards.ignored") .join("cards.runId", "runs.id") diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h index 21089393e..5b3746bb4 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h @@ -16,8 +16,9 @@ namespace Ui { class CardReaderWidget; } +namespace quickevent::core::og { class SqlTableModel; } + namespace qf { -namespace core { namespace model { class SqlTableModel; } } namespace qmlwidgets { class Action; namespace framework { class PartWidget; class Plugin; } @@ -108,7 +109,7 @@ private slots: private: Ui::CardReaderWidget *ui; qf::qmlwidgets::Action *m_actAssignCard = nullptr; - qf::core::model::SqlTableModel *m_cardsModel = nullptr; + quickevent::core::og::SqlTableModel *m_cardsModel = nullptr; quickevent::gui::audio::Player *m_audioPlayer = nullptr; siut::DeviceDriver *f_siDriver = nullptr; siut::CommPort *m_commPort = nullptr; diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp index cd8190a08..c289a296d 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.cpp @@ -175,6 +175,7 @@ CompetitorWidget::CompetitorWidget(QWidget *parent) : } connect(ui->edFind, &FindRegistrationEdit::registrationSelected, this, &CompetitorWidget::onRegistrationSelected); + connect(ui->btnSwitchNames, &QPushButton::clicked, this, &CompetitorWidget::onSwitchNames); dataController()->setDocument(new Competitors::CompetitorDocument(this)); m_runsModel = new CompetitorRunsModel(this); @@ -424,4 +425,12 @@ bool CompetitorWidget::saveData() return false; } +void CompetitorWidget::onSwitchNames() +{ + auto *doc = dataController()->document(); + QString fn = doc->value("firstname").toString(); + QString ln = doc->value("lastname").toString(); + doc->setValue("firstname",ln); + doc->setValue("lastname",fn); +} diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.h b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.h index 0a7c570f8..dcb0a7b21 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.h @@ -26,6 +26,7 @@ class CompetitorWidget : public qf::qmlwidgets::framework::DataDialogWidget private slots: void onRegistrationSelected(const QVariantMap &values); + void onSwitchNames(); private: Q_SLOT bool loadRunsTable(); Q_SLOT bool saveRunsTable(); diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.ui b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.ui index 2b33b0505..6df5be0a5 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.ui +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitorwidget.ui @@ -58,23 +58,64 @@ Competitor - - + + + + competitors.registration + + + + + - &Class + &Last name - cbxClass + lineEdit_3 - - + + - First na&me + Co&untry - lineEdit_2 + lineEdit_7 + + + + + + + Licenc&e + + + lineEdit_5 + + + + + + + competitors.classId + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + competitors.siId + + + + + + + competitors.country @@ -88,54 +129,67 @@ - - + + - IO&F ID + &Note - edIofId + lineEdit_8 - - + + + + competitors.club + + + + + - Clu&b + &Class - lineEdit_6 + cbxClass - - + + - &Note + IO&F ID - lineEdit_8 + edIofId - - + + - competitors.classId + competitors.licence - - - - competitors.firstName + + + + Clu&b + + + lineEdit_6 - - - - competitors.registration + + + + First na&me + + + lineEdit_2 @@ -152,10 +206,10 @@ - - + + - competitors.club + competitors.firstName @@ -166,16 +220,6 @@ - - - - &Last name - - - lineEdit_3 - - - @@ -186,26 +230,6 @@ - - - - Licenc&e - - - lineEdit_5 - - - - - - - Co&untry - - - lineEdit_7 - - - @@ -213,29 +237,29 @@ - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - competitors.siId - - - - - - - competitors.licence - - - - - - - competitors.country - - + + + + + + Switch Names + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -318,6 +342,7 @@ chkFind edFind cbxClass + btnSwitchNames lineEdit_2 lineEdit_3 lineEdit_4 diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp index 4a3b83b91..aac3f8313 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.cpp @@ -253,6 +253,16 @@ bool EventConfig::isOneTenthSecResults() const return static_cast(value(QStringLiteral("oneTenthSecResults")).toInt()); } +bool EventConfig::isIofRace() const +{ + return value(QStringLiteral("event.iofRace")).toInt() != 0; +} + +int EventConfig::iofXmlRaceNumber() const +{ + return value(QStringLiteral("event.iofXmlRaceNumber")).toInt(); +} + /* const QSet &EventConfig::knownKeys() { diff --git a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h index 51285e90e..d7e52b1c3 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventconfig.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventconfig.h @@ -16,6 +16,8 @@ class EventConfig : public QObject Q_PROPERTY(int stageCount READ stageCount) Q_PROPERTY(bool isHandicap READ isHandicap) Q_PROPERTY(bool isRelays READ isRelays) + Q_PROPERTY(bool isIofRace READ isIofRace) + Q_PROPERTY(bool iofXmlRaceNumber READ iofXmlRaceNumber) //Q_PROPERTY(QString eventName READ eventName) public: enum class Sport {OB = 1, LOB, MTBO, TRAIL}; @@ -44,6 +46,8 @@ class EventConfig : public QObject int handicapLength() const; bool isHandicap() const {return handicapLength() > 0;} bool isRelays() const {return disciplineId() == (int)Discipline::Relays || disciplineId() == (int)Discipline::Teams;} + bool isIofRace() const; + int iofXmlRaceNumber() const; QString eventName() const; QString eventPlace() const; QString director() const; diff --git a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp index 79972a25f..10718cc1d 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.cpp @@ -66,6 +66,8 @@ void EventDialogWidget::loadParams(const QVariantMap ¶ms) ui->ed_importId->setText(params.value("importId").toString()); ui->ed_cardChecCheckTimeSec->setValue(params.value("cardChechCheckTimeSec").toInt()); ui->ed_oneTenthSecResults->setCurrentIndex(params.value("oneTenthSecResults").toInt()); + ui->ed_iofRace->setChecked(params.value("iofRace").toInt() != 0); + ui->ed_xmlRaceNumber->setValue(params.value("iofXmlRaceNumber").toInt()); } QVariantMap EventDialogWidget::saveParams() @@ -86,5 +88,7 @@ QVariantMap EventDialogWidget::saveParams() ret["importId"] = ui->ed_importId->text().toInt(); ret["cardChechCheckTimeSec"] = ui->ed_cardChecCheckTimeSec->value(); ret["oneTenthSecResults"] = ui->ed_oneTenthSecResults->currentIndex(); + ret["iofRace"] = (int)ui->ed_iofRace->isChecked(); + ret["iofXmlRaceNumber"] = (int)ui->ed_xmlRaceNumber->value(); return ret; } diff --git a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui index e8d357dfc..358959aff 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/eventdialogwidget.ui @@ -81,6 +81,30 @@ + + + + &Time + + + ed_date + + + + + + + h:mm:ss + + + + + + @@ -162,26 +186,6 @@ - - - - Import ID - - - ed_handicapLength - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - @@ -216,30 +220,6 @@ - - - - &Time - - - ed_date - - - - - - - h:mm:ss - - - - - - @@ -284,6 +264,26 @@ + + + + Import ID + + + ed_handicapLength + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + @@ -340,6 +340,38 @@ + + + + + Race data + + + + + + + IOF Eventor race - user for IOF XML exports + + + Is IOF Race (data from Eventor) + + + + + + + Race number (xml) + + + + + + + <html><head/><body><p>IOF Eventor race number - if nonzero, user as &lt;Race&gt; node and for attribute of &lt;Start&gt; node for IOF XML exports</p></body></html> + + + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp index a00ae2ff4..091555cfc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp @@ -36,6 +36,8 @@ using Runs::RunsPlugin; namespace Event { namespace services { +constexpr int HR_12_MSEC = 12 * 60 * 60 * 1000; + EmmaClient::EmmaClient(QObject *parent) : Super(EmmaClient::serviceName(), parent) { @@ -329,7 +331,10 @@ void EmmaClient::onExportTimerTimeOut() if (ss.exportStartTypeTxt()) { qfInfo() << "EmmaClient startlist creation called"; - exportStartListRacomTxt(); + if(settings().startExportType() == EmmaClientSettings::StartExportType::CSV) + exportStartListRacomCsv(); + else + exportStartListRacomTxt(); } if (ss.exportFinishTypeTxt()) { @@ -405,7 +410,7 @@ void EmmaClient::exportStartListRacomTxt() bool is_relays = getPlugin()->eventConfig()->isRelays(); int current_stage = getPlugin()->currentStageId(); qfs::QueryBuilder qb; - qb.select2("runs", "startTimeMs, siId, competitorId, isrunning, leg") + qb.select2("runs", "startTimeMs, siId, competitorId, isrunning, leg, finishTimeMs") .select2("competitors","firstName, lastName, registration") .select2("classes","name") .select2("cards", "id, startTime") @@ -447,7 +452,8 @@ void EmmaClient::exportStartListRacomTxt() QString name = q2.value("competitors.lastName").toString() + " " + q2.value("competitors.firstName").toString(); QString class_name = q2.value("classes.name").toString(); class_name.remove(" "); - if (class_name.isEmpty()) + // don't export runners without class and SI card + if (class_name.isEmpty() || si == 0) continue; QString reg = q2.value("competitors.registration").toString(); name = name.leftJustified(22,QChar(' '),true); @@ -481,36 +487,19 @@ void EmmaClient::exportStartListRacomTxt() { // has start in si card (P, T, HDR) & not used in relays start_time_card *= 1000; // msec + if (start00 > HR_12_MSEC) // if start00 is PM, move start time + start_time_card += HR_12_MSEC; start_time_card -= start00; - if (start_time_card < 0) // 12h format - start_time_card += (12*60*60*1000); msec = start_time_card; } - QString tm2; - //TODO zmenit na format mmm.ss,zzzz - if (msec < 0) - continue; // emma client has problem with negative times + // new emma client can handle negative times + int min = (msec / 60000); - if(min < 10) - tm2 += "00"; - else if(min < 100) - tm2 += "0"; - tm2 += QString::number(min); - tm2 += '.'; - int sec = (msec % 60000 / 1000); - if(sec < 10) - tm2 += "0"; - tm2 += QString::number(sec); - tm2 += ','; - int zzzz = msec % 1000 * 10; - if(zzzz < 10) - tm2 += "000"; - else if(zzzz < 100) - tm2 += "00"; - else if(zzzz < 1000) - tm2 += "000"; - tm2 += QString::number(zzzz); + int sec = (abs(msec) % 60000 / 1000); + int zzzz = abs(msec) % 1000 * 10; + + QString tm2 = QString("%1.%2,%3").arg(min,3,10,QChar('0')).arg(sec,2,10,QChar('0')).arg(zzzz,4,10,QChar('0')); if (id != 0) // filter bad data { @@ -520,6 +509,148 @@ void EmmaClient::exportStartListRacomTxt() } } +void EmmaClient::exportStartListRacomCsv() +{ + EmmaClientSettings ss = settings(); + QString export_dir = ss.exportDir(); + QFile f(export_dir + '/' + ss.fileNameBase() + ".start.csv"); + if(!f.open(QFile::WriteOnly)) { + qfError() << "Canot open file:" << f.fileName() << "for writing."; + return; + } + + QTextStream ts(&f); + ts.setGenerateByteOrderMark(true); // BOM +#if QT_VERSION_MAJOR >= 6 + ts.setEncoding(QStringConverter::encodingForName("UTF-8").value()); +#else + ts.setCodec("UTF-8"); +#endif + bool is_relays = getPlugin()->eventConfig()->isRelays(); + int current_stage = getPlugin()->currentStageId(); + const QString separator(";"); + + ts << "ID" << separator << "Class" << separator << "SI" << separator + << "Name" << separator << "Starttime" << separator << "Club" << separator <<"Bib"; + if (is_relays) + ts << separator << "Leg"; + ts << "\n"; + + qfs::QueryBuilder qb; + qb.select2("runs", "startTimeMs, siId, competitorId, isrunning, leg") + .select2("competitors","firstName, lastName, registration, club, startNumber") + .select2("classes","name") + .select2("cards", "id, startTime") + .from("runs") + .join("runs.competitorId","competitors.id") + .join("runs.id", "cards.runId") + .where("runs.stageId=" QF_IARG(current_stage)); + if(is_relays) { + qb.select2("relays","number"); + qb.join("runs.relayId", "relays.id"); + qb.join("relays.classId", "classes.id"); + qb.orderBy("runs.leg, relays.number ASC"); + } + else { + qb.join("competitors.classId","classes.id"); + qb.orderBy("runs.id ASC"); + } + + int start00 = getPlugin()->stageStartMsec(current_stage); + qfDebug() << qb.toString(); + qfs::Query q2; + q2.execThrow(qb.toString()); + int last_id = -1; + QMap start_times_relays; + while(q2.next()) { + int id = q2.value("runs.competitorId").toInt(); + if (id == last_id) + continue; + bool is_running = (q2.value("runs.isrunning").isNull()) ? false : q2.value("runs.isrunning").toBool(); + if (!is_running) + continue; + last_id = id; + int si = q2.value("runs.siId").toInt(); + int start_time = q2.value("runs.startTimeMs").toInt(); + int bib = q2.value("competitors.startNumber").toInt(); + bool start_time_card_null = q2.value("runs.startTimeMs").isNull(); + int start_time_card = q2.value("cards.startTime").toInt(); + if (start_time_card == 61166 || start_time_card_null) + start_time_card = 0; + QString name = q2.value("competitors.lastName").toString() + " " + q2.value("competitors.firstName").toString(); + QString class_name = q2.value("classes.name").toString(); + // don't export runners without class and SI card + if (class_name.isEmpty() || si == 0) + continue; + int id_or_bib = id; + int leg_no = q2.value("runs.leg").toInt(); + if (is_relays) { + // EmmaClient uses for all relays start time of 1st leg + int rel_num = q2.value("relays.number").toInt(); + if (rel_num > 0) + bib = rel_num; + if (leg_no == 1 && rel_num > 0) { + start_times_relays.insert(rel_num,start_time); + } else if (rel_num > 0) { + auto it = start_times_relays.find(rel_num); + if (it != start_times_relays.end()) + start_time = it.value(); + } + id_or_bib = (rel_num > 0) ? rel_num : 10000+id; // when no number, use id with offset + } + + int msec = start_time; + if (start_time_card != 0 && !start_time_card_null && !is_relays) + { + // has start in si card (P, T, HDR) & not used in relays + start_time_card *= 1000; // msec + if (start00 > HR_12_MSEC) // if start00 is PM, move start time + start_time_card += HR_12_MSEC; + start_time_card -= start00; + msec = start_time_card; + } + + int min = (msec / 60000); + int sec = (abs(msec) % 60000 / 1000); + int zzzz = abs(msec) % 1000 * 10; + + QString tm2 = QString("%1.%2,%3").arg(min,3,10,QChar('0')).arg(sec,2,10,QChar('0')).arg(zzzz,4,10,QChar('0')); + + QString club = q2.value("competitors.club").toString(); + QString reg = q2.value("competitors.registration").toString(); + if (club.isEmpty() && !reg.isEmpty()) { + qf::core::sql::Query q; + q.exec(QStringLiteral("SELECT name FROM clubs WHERE abbr='%1'").arg(reg.left(3)), qf::core::Exception::Throw); + if (q.next()) { + club = q.value(0).toString(); + } + } + + if (id != 0){ // filter bad data + // columns are : + // ID, Class, SI, Name, Starttime, Club, Bib, [Leg] + ts << id_or_bib; + ts << separator; + ts << class_name; + ts << separator; + ts << si; + ts << separator; + ts << name; + ts << separator; + ts << tm2; + ts << separator; + ts << club; + ts << separator; + ts << bib; + if (is_relays) { + ts << separator; + ts << leg_no; + } + ts << "\n"; + } + } +} + void EmmaClient::loadSettings() { Super::loadSettings(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.h b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.h index 0d48808e9..b22f58183 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.h @@ -10,7 +10,12 @@ namespace services { class EmmaClientSettings : public ServiceSettings { using Super = ServiceSettings; - +public: + enum StartExportType { + TXT = 0, + CSV + }; +private: QF_VARIANTMAP_FIELD2(bool, e, setE, xportStartListTypeXml3, 0) QF_VARIANTMAP_FIELD2(bool, e, setE, xportResultTypeXml3, 0) QF_VARIANTMAP_FIELD(QString, e, setE, xportDir) @@ -18,6 +23,7 @@ class EmmaClientSettings : public ServiceSettings QF_VARIANTMAP_FIELD2(int, e, setE, xportIntervalSec, 0) QF_VARIANTMAP_FIELD2(bool, e, setE, xportStartTypeTxt, 0) QF_VARIANTMAP_FIELD2(bool, e, setE, xportFinishTypeTxt, 0) + QF_VARIANTMAP_FIELD2(int, s, setS, tartExportType, 0) public: EmmaClientSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; @@ -41,6 +47,7 @@ class EmmaClient : public Service void exportStartListIofXml3(); void exportFinishRacomTxt(); void exportStartListRacomTxt(); + void exportStartListRacomCsv(); bool preExport(); void loadSettings() override; private: diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp index 0ab3fb447..58cd66fa4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.cpp @@ -33,6 +33,8 @@ EmmaClientWidget::EmmaClientWidget(QWidget *parent) ui->chExportResultsXml30->setCheckState((ss.exportResultTypeXml3()) ? Qt::Checked : Qt::Unchecked); if (ui->edFileNameBase->text().isEmpty()) ui->edFileNameBase->setText(getPlugin()->eventName()); + if (ss.startExportType() == EmmaClientSettings::StartExportType::CSV) + ui->rbRacomStartCsv->setChecked(true); } connect(ui->btChooseExportDir, &QPushButton::clicked, this, &EmmaClientWidget::onBtChooseExportDirClicked); @@ -99,6 +101,10 @@ bool EmmaClientWidget::saveSettings() ss.setExportFinishTypeTxt(ui->chExportFinishTxt->isChecked()); ss.setExportStartListTypeXml3(ui->chExportStartListXml30->isChecked());; ss.setExportResultTypeXml3(ui->chExportResultsXml30->isChecked());; + EmmaClientSettings::StartExportType type = EmmaClientSettings::StartExportType::TXT; + if (ui->rbRacomStartCsv->isChecked()) + type = EmmaClientSettings::StartExportType::CSV; + ss.setStartExportType(type); if (ss.fileNameBase().isEmpty()) ss.setFileNameBase(getPlugin()->eventName()); @@ -125,7 +131,10 @@ void EmmaClientWidget::onBtExportStartListTxtClicked() EmmaClient *svc = service(); if(svc) { saveSettings(); - svc->exportStartListRacomTxt(); + if (svc->settings().startExportType() == EmmaClientSettings::StartExportType::CSV) + svc->exportStartListRacomCsv(); + else + svc->exportStartListRacomTxt(); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.ui index 0104e4e16..a84afb2f0 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclientwidget.ui @@ -6,10 +6,16 @@ 0 0 - 528 - 444 + 560 + 520 + + + 550 + 0 + + Emma Client @@ -184,6 +190,57 @@ + + + + + + Startlist export type + + + + + + + Text + + + true + + + + + + + CSV + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p>Startlist CVS export is only supported by EmmaClient after 2023-08.<br/>Support can be verified in EmmaClient in 'New Racom Connection' dialog,<br/>if it contain checkbox 'Use extended csv file read (2023)'</p></body></html> + + + Qt::MarkdownText + + + @@ -211,6 +268,8 @@ chExportResultsXml30 btExportStartListXml30 btExportResultsXml30 + rbRacomStartTxt + rbRacomStartCsv chExportStartListTxt chExportFinishTxt btExportSplitsTxt diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp index 4ed5f2ca8..b0d9a394a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp @@ -260,6 +260,7 @@ void OResultsClient::onCompetitorChanged(int competitor_id) "runs.notFinish, " "runs.notCompeting, " "runs.startTimeMs, " + "runs.finishTimeMs, " "runs.timeMs " "FROM runs " "INNER JOIN competitors ON competitors.id = runs.competitorId " @@ -280,6 +281,7 @@ void OResultsClient::onCompetitorChanged(int competitor_id) bool isDidNotFinish = q.value("notFinish").toBool(); bool isNotCompeting = q.value("notCompeting").toBool(); int start_time = q.value("startTimeMs").toInt(); + int finish_time = q.value("finishTimeMs").toInt(); int running_time = q.value("timeMs").toInt(); int status_code = mop_run_status_code(running_time, isDisq, isDisqByOrganizer, isMissPunch, isBadCheck, isDidNotStart, isDidNotFinish, isNotCompeting); @@ -292,7 +294,7 @@ void OResultsClient::onCompetitorChanged(int competitor_id) competitor.insert("bib", start_num); if(start_time != 0) competitor.insert("st", mop_start(start_time)); - if(running_time != 0) + if(finish_time > start_time && running_time != 0) competitor.insert("rt", running_time / 100); diff --git a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp index 2dfaac317..3fe86b07e 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp @@ -1,6 +1,7 @@ #include "orisplugin.h" #include "orisimporter.h" #include "txtimporter.h" +#include "xmlimporter.h" #include #include @@ -22,6 +23,7 @@ OrisPlugin::OrisPlugin(QObject *parent) //setPersistentSettingsId("Oris"); m_orisImporter = new OrisImporter(this); m_txtImporter = new TxtImporter(this); + m_xmlImporter = new XmlImporter(this); connect(this, &OrisPlugin::installed, this, &OrisPlugin::onInstalled); } @@ -91,6 +93,24 @@ void OrisPlugin::onInstalled() qfw::Action *a = act_import_txt->addActionInto("competitorsRanking", tr("&Ranking CSV (ORIS format)")); connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRankingCsv); } + act_import_txt->addSeparatorInto(); + { + qfw::Action *a = act_import_txt->addActionInto("runsCzeCSV", tr("Import CSV (key is CZE registration)")); + connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRunsCzeCSV); + } + { + qfw::Action *a = act_import_txt->addActionInto("runsIdCSV", tr("Import CSV (key is runs.id)")); + connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRunsIdCSV); + } + { + qfw::Action *a = act_import_txt->addActionInto("runsIofCSV", tr("Import CSV (key is Iof ID)")); + connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRunsIofCSV); + } + { + qfw::Action *a = act_import->addActionInto("iofXmlEntry", tr("Import IOF XML 3.0")); + connect(a, &qfw::Action::triggered, m_xmlImporter, &XmlImporter::importXML30); + } + } } diff --git a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h index 40682b920..abf984881 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h +++ b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h @@ -5,6 +5,7 @@ class OrisImporter; class TxtImporter; +class XmlImporter; namespace Oris { @@ -19,6 +20,7 @@ class OrisPlugin : public qf::qmlwidgets::framework::Plugin//, public qf::qmlwid private: OrisImporter *m_orisImporter = nullptr; TxtImporter *m_txtImporter = nullptr; + XmlImporter *m_xmlImporter = nullptr; }; } diff --git a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp index 7be9506bc..324c25442 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp @@ -244,3 +244,363 @@ void TxtImporter::importParsedCsv(const QList &csv) getPlugin()->emitReloadDataRequest(); getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED); } + +int TxtImporter::getStartTimeMSec(QString str, int start00_msec) +{ + bool ok; + double dbl_time = str.toDouble(&ok); + int st_time = 0; + if(ok) { + st_time = static_cast(dbl_time) * 60 + ((static_cast(dbl_time * 100)) % 100); + st_time *= 1000; + } else { // time hh:mm:ss + QTime t = QTime::fromString(str,(str.size() > 7) ?"hh:mm:ss" : "h:mm:ss"); + st_time = t.hour() * 3600 + t.minute() * 60 + t.second(); + st_time *= 1000; + st_time -= start00_msec; + } + return st_time; +} + + +void TxtImporter::importRunsCzeCSV() +{ + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + qf::qmlwidgets::dialogs::MessageBox mbx(fwk); + mbx.setIcon(QMessageBox::Information); + mbx.setText(tr("Import comma separated values UTF8 text files with header.
Separator is semicolon(;).
Updates only existing runners (key is Czech registration).")); + mbx.setInformativeText(tr("Each row should have following columns: " + "
    " + "
  1. Registration - key
  2. " + "
  3. SI
  4. " + "
  5. Class
  6. " + "
  7. Bib
  8. " + "
  9. Start time (in format: mmm.ss from zero time or hh:mm:ss)
  10. " + "
Only first column is mandatory, others can be empty.")); + mbx.setDoNotShowAgainPersistentKey("importRunsCzeCSV"); + int res = mbx.exec(); + if(res != QMessageBox::Ok) + return; + QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open file"), QString(), tr("CSV files (*.csv *.txt)")); + if(fn.isEmpty()) + return; + + QMap classes_map; // classes.name->classes.id + qf::core::sql::Query q; + q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw); + while(q.next()) { + classes_map[q.value(1).toString()] = q.value(0).toInt(); + } + + int stage_id = getPlugin()->currentStageId(); + auto start00_day_msec = getPlugin()->stageStartTime(stage_id).msecsSinceStartOfDay(); + + try { + QFile f(fn); + if(!f.open(QFile::ReadOnly)) + QF_EXCEPTION(tr("Cannot open file '%1' for reading.").arg(fn)); + QTextStream ts(&f); + qf::core::utils::CSVReader reader(&ts); + reader.setSeparator(';'); + enum {ColRegistration = 0, ColSI, ColClass, ColBib, ColStarttime}; + + qfLogScope("importRunsCzeCSV"); + qf::core::sql::Transaction transaction; + qf::core::sql::Query q1a,q1b,q2,q3,q4; + q.prepare("SELECT id FROM competitors WHERE registration=:registration", qf::core::Exception::Throw); + q1a.prepare("UPDATE competitors SET siId=:si WHERE id=:id", qf::core::Exception::Throw); + q1b.prepare("UPDATE runs SET siId=:si WHERE competitorId=:id", qf::core::Exception::Throw); + q2.prepare("UPDATE competitors SET classId=:class WHERE id=:id", qf::core::Exception::Throw); + q3.prepare("UPDATE competitors SET startNumber=:bib WHERE id=:id", qf::core::Exception::Throw); + q4.prepare("UPDATE runs SET startTimeMs=:starttime WHERE competitorId=:id", qf::core::Exception::Throw); + + int n = 0; + while (!ts.atEnd()) { + QStringList line = reader.readCSVLineSplitted(); + if(line.count() <= 1) + QF_EXCEPTION(tr("Fields separation error, invalid CSV format, Error reading CSV line: [%1]").arg(line.join(';').mid(0, 100))); + if(n++ == 0) // skip column names + continue; + QString registration = line.value(ColRegistration).trimmed(); + if(registration.isEmpty()) { + QF_EXCEPTION(tr("Error reading CSV line: [%1]").arg(line.join(';'))); + } + q.bindValue(":registration", registration); + q.exec(qf::core::Exception::Throw); + if(q.next()) { + // if registration found in db - start update data + int competitor_id = q.value(0).toInt(); + + int si = line.value(ColSI).toInt(); + QString class_name = line.value(ColClass).trimmed(); + int bib = line.value(ColBib).toInt(); + QString starttime = line.value(ColStarttime).trimmed(); + + qfDebug() << registration << "-> (" << si << "," << class_name << "," << bib << "," << starttime << ")"; + if (si != 0) { + q1a.bindValue(":si", si); + q1a.bindValue(":id", competitor_id); + q1a.exec(qf::core::Exception::Throw); + q1b.bindValue(":si", si); + q1b.bindValue(":id", competitor_id); + q1b.exec(qf::core::Exception::Throw); + } + if (!class_name.isEmpty()) { + int class_id = classes_map.value(class_name); + if(class_id == 0) + QF_EXCEPTION(tr("Undefined class name: '%1'").arg(class_name)); + + q2.bindValue(":class", class_id); + q2.bindValue(":id", competitor_id); + q2.exec(qf::core::Exception::Throw); + } + if (bib != 0) { + q3.bindValue(":bib", bib); + q3.bindValue(":id", competitor_id); + q3.exec(qf::core::Exception::Throw); + } + if (!starttime.isEmpty()) { + int st_time = getStartTimeMSec(starttime, start00_day_msec); + q4.bindValue(":starttime", st_time); + q4.bindValue(":id", competitor_id); + q4.exec(qf::core::Exception::Throw); + } + } + else + qfWarning() << registration << "not found in database."; + } + transaction.commit(); + qfInfo() << fn << n << "lines imported"; + } + catch (const qf::core::Exception &e) { + qf::qmlwidgets::dialogs::MessageBox::showException(fwk, e); + } +} + +void TxtImporter::importRunsIdCSV() +{ + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + qf::qmlwidgets::dialogs::MessageBox mbx(fwk); + mbx.setIcon(QMessageBox::Information); + mbx.setText(tr("Import comma separated values UTF8 text files with header.
Separator is semicolon(;).
Updates only existing runners (key is id in module(table) runs).")); + mbx.setInformativeText(tr("Each row should have following columns: " + "
    " + "
  1. Runs Id - key
  2. " + "
  3. SI
  4. " + "
  5. Class
  6. " + "
  7. Bib
  8. " + "
  9. Start time (in format: mmm.ss from zero time or hh:mm:ss)
  10. " + "
Only first column is mandatory, others can be empty.")); + mbx.setDoNotShowAgainPersistentKey("importRunsIdCSV"); + int res = mbx.exec(); + if(res != QMessageBox::Ok) + return; + QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open file"), QString(), tr("CSV files (*.csv *.txt)")); + if(fn.isEmpty()) + return; + + QMap classes_map; // classes.name->classes.id + qf::core::sql::Query q; + q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw); + while(q.next()) { + classes_map[q.value(1).toString()] = q.value(0).toInt(); + } + + int stage_id = getPlugin()->currentStageId(); + auto start00_day_msec = getPlugin()->stageStartTime(stage_id).msecsSinceStartOfDay(); + + try { + QFile f(fn); + if(!f.open(QFile::ReadOnly)) + QF_EXCEPTION(tr("Cannot open file '%1' for reading.").arg(fn)); + QTextStream ts(&f); + qf::core::utils::CSVReader reader(&ts); + reader.setSeparator(';'); + enum {ColRunsId = 0, ColSI, ColClass, ColBib, ColStarttime}; + + qfLogScope("importRunsIdCSV"); + qf::core::sql::Transaction transaction; + qf::core::sql::Query q1a,q1b,q2,q3,q4; + q.prepare("SELECT id, competitorId FROM runs WHERE id=:id", qf::core::Exception::Throw); + q1a.prepare("UPDATE competitors SET siId=:si WHERE id=:id", qf::core::Exception::Throw); + q1b.prepare("UPDATE runs SET siId=:si WHERE id=:id", qf::core::Exception::Throw); + q2.prepare("UPDATE competitors SET classId=:class WHERE id=:id", qf::core::Exception::Throw); + q3.prepare("UPDATE competitors SET startNumber=:bib WHERE id=:id", qf::core::Exception::Throw); + q4.prepare("UPDATE runs SET startTimeMs=:starttime WHERE id=:id", qf::core::Exception::Throw); + + int n = 0; + while (!ts.atEnd()) { + QStringList line = reader.readCSVLineSplitted(); + if(line.count() <= 1) + QF_EXCEPTION(tr("Fields separation error, invalid CSV format, Error reading CSV line: [%1]").arg(line.join(';').mid(0, 100))); + if(n++ == 0) // skip column names + continue; + QString runs_id = line.value(ColRunsId).trimmed(); + if(runs_id.isEmpty()) { + QF_EXCEPTION(tr("Error reading CSV line: [%1]").arg(line.join(';'))); + } + q.bindValue(":id", runs_id); + q.exec(qf::core::Exception::Throw); + if(q.next()) { + // if runsId found in db - start update data + int competitor_id = q.value(1).toInt(); + + int si = line.value(ColSI).toInt(); + QString class_name = line.value(ColClass).trimmed(); + int bib = line.value(ColBib).toInt(); + QString starttime = line.value(ColStarttime).trimmed(); + + qfDebug() << runs_id << "-> (" << si << "," << class_name << "," << bib << "," << starttime << ")"; + if (si != 0) { + q1a.bindValue(":si", si); + q1a.bindValue(":id", competitor_id); + q1a.exec(qf::core::Exception::Throw); + q1b.bindValue(":si", si); + q1b.bindValue(":id", runs_id); + q1b.exec(qf::core::Exception::Throw); + } + if (!class_name.isEmpty()) { + int class_id = classes_map.value(class_name); + if(class_id == 0) + QF_EXCEPTION(tr("Undefined class name: '%1'").arg(class_name)); + + q2.bindValue(":class", class_id); + q2.bindValue(":id", competitor_id); + q2.exec(qf::core::Exception::Throw); + } + if (bib != 0) { + q3.bindValue(":bib", bib); + q3.bindValue(":id", competitor_id); + q3.exec(qf::core::Exception::Throw); + } + if (!starttime.isEmpty()) { + int st_time = getStartTimeMSec(starttime, start00_day_msec); + q4.bindValue(":starttime", st_time); + q4.bindValue(":id", runs_id); + q4.exec(qf::core::Exception::Throw); + } + } + else + qfWarning() << runs_id << "not found in database."; + } + transaction.commit(); + qfInfo() << fn << n << "lines imported"; + } + catch (const qf::core::Exception &e) { + qf::qmlwidgets::dialogs::MessageBox::showException(fwk, e); + } +} + +void TxtImporter::importRunsIofCSV() +{ + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + qf::qmlwidgets::dialogs::MessageBox mbx(fwk); + mbx.setIcon(QMessageBox::Information); + mbx.setText(tr("Import comma separated values UTF8 text files with header.
Separator is semicolon(;).
Updates only existing runners (key is IOF ID).")); + mbx.setInformativeText(tr("Each row should have following columns: " + "
    " + "
  1. IOF ID - key
  2. " + "
  3. SI
  4. " + "
  5. Class
  6. " + "
  7. Bib
  8. " + "
  9. Start time (in format: mmm.ss from zero time or hh:mm:ss)
  10. " + "
Only first column is mandatory, others can be empty.")); + mbx.setDoNotShowAgainPersistentKey("importRunsIofCSV"); + int res = mbx.exec(); + if(res != QMessageBox::Ok) + return; + QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open file"), QString(), tr("CSV files (*.csv *.txt)")); + if(fn.isEmpty()) + return; + + QMap classes_map; // classes.name->classes.id + qf::core::sql::Query q; + q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw); + while(q.next()) { + classes_map[q.value(1).toString()] = q.value(0).toInt(); + } + + int stage_id = getPlugin()->currentStageId(); + auto start00_day_msec = getPlugin()->stageStartTime(stage_id).msecsSinceStartOfDay(); + + try { + QFile f(fn); + if(!f.open(QFile::ReadOnly)) + QF_EXCEPTION(tr("Cannot open file '%1' for reading.").arg(fn)); + QTextStream ts(&f); + qf::core::utils::CSVReader reader(&ts); + reader.setSeparator(';'); + enum {ColIofId = 0, ColSI, ColClass, ColBib, ColStarttime}; + + qfLogScope("importRunsCzeCSV"); + qf::core::sql::Transaction transaction; + qf::core::sql::Query q1a,q1b,q2,q3,q4; + q.prepare("SELECT id FROM competitors WHERE iofId=:iofId", qf::core::Exception::Throw); + q1a.prepare("UPDATE competitors SET siId=:si WHERE iofId=:iofId", qf::core::Exception::Throw); + q1b.prepare("UPDATE runs SET siId=:si WHERE competitorId=:id", qf::core::Exception::Throw); + q2.prepare("UPDATE competitors SET classId=:class WHERE iofId=:iofId", qf::core::Exception::Throw); + q3.prepare("UPDATE competitors SET startNumber=:bib WHERE iofId=:iofId", qf::core::Exception::Throw); + q4.prepare("UPDATE runs SET startTimeMs=:starttime WHERE competitorId=:id", qf::core::Exception::Throw); + + int n = 0; + while (!ts.atEnd()) { + QStringList line = reader.readCSVLineSplitted(); + if(line.count() <= 1) + QF_EXCEPTION(tr("Fields separation error, invalid CSV format, Error reading CSV line: [%1]").arg(line.join(';').mid(0, 100))); + if(n++ == 0) // skip column names + continue; + int iof_id = line.value(ColIofId).toInt(); + if(iof_id == 0) { + QF_EXCEPTION(tr("Error reading CSV line: [%1]").arg(line.join(';'))); + } + q.bindValue(":iofId", iof_id); + q.exec(qf::core::Exception::Throw); + if(q.next()) { + // if registration found in db - start update data + int competitor_id = q.value(0).toInt(); + + int si = line.value(ColSI).toInt(); + QString class_name = line.value(ColClass).trimmed(); + int bib = line.value(ColBib).toInt(); + QString starttime = line.value(ColStarttime).trimmed(); + + qfDebug() << iof_id << "-> (" << si << "," << class_name << "," << bib << "," << starttime << ")"; + if (si != 0) { + q1a.bindValue(":si", si); + q1a.bindValue(":iofId", iof_id); + q1a.exec(qf::core::Exception::Throw); + q1b.bindValue(":si", si); + q1b.bindValue(":id", competitor_id); + q1b.exec(qf::core::Exception::Throw); + } + if (!class_name.isEmpty()) { + int class_id = classes_map.value(class_name); + if(class_id == 0) + QF_EXCEPTION(tr("Undefined class name: '%1'").arg(class_name)); + + q2.bindValue(":class", class_id); + q2.bindValue(":iofId", iof_id); + q2.exec(qf::core::Exception::Throw); + } + if (bib != 0) { + q3.bindValue(":bib", bib); + q3.bindValue(":iofId", iof_id); + q3.exec(qf::core::Exception::Throw); + } + if (!starttime.isEmpty()) { + int st_time = getStartTimeMSec(starttime, start00_day_msec); + q4.bindValue(":starttime", st_time); + q4.bindValue(":id", competitor_id); + q4.exec(qf::core::Exception::Throw); + } + } + else + qfWarning() << iof_id << "not found in database."; + } + transaction.commit(); + qfInfo() << fn << n << "lines imported"; + } + catch (const qf::core::Exception &e) { + qf::qmlwidgets::dialogs::MessageBox::showException(fwk, e); + } +} diff --git a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h index c62c5d625..4658f0b2b 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h +++ b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h @@ -24,8 +24,12 @@ class TxtImporter : public QObject Q_INVOKABLE void importCompetitorsCSOS(); Q_INVOKABLE void importCompetitorsCSV(); Q_INVOKABLE void importRankingCsv(); + Q_INVOKABLE void importRunsCzeCSV(); + Q_INVOKABLE void importRunsIdCSV(); + Q_INVOKABLE void importRunsIofCSV(); protected: void importParsedCsv(const QList &csv); + int getStartTimeMSec(QString str, int start00_msec); }; #endif // TXTIMPORTER_H diff --git a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp new file mode 100644 index 000000000..545b65c02 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp @@ -0,0 +1,852 @@ +#include "xmlimporter.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qfw = qf::qmlwidgets; +namespace qfd = qf::qmlwidgets::dialogs; +using qf::qmlwidgets::framework::getPlugin; +using Event::EventPlugin; + +XmlImporter::XmlImporter(QObject *parent) + : QObject(parent) +{ +} + +bool XmlImporter::readPersonNode (SPerson &s, QXmlStreamReader &reader, [[maybe_unused]] const XmlCreators creator) +{ + bool result = false; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Person") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Id") { + if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + s.iofId = reader.readElementText().toInt(); + else if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "CZE") + s.regCz = reader.readElementText(); + else if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "ORIS") + s.orisId = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + result = (s.iofId.has_value() || !s.regCz.isEmpty()); + } + else if (reader.name().toString() == "Nationality") { + if (reader.attributes().hasAttribute("code")) + s.nationalityCode = reader.attributes().value("code").toString(); + s.nationalityName = reader.readElementText(); + } + else if (reader.name().toString() == "Name") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Family") + s.nameFamily = reader.readElementText(); + else if (reader.name().toString() == "Given") + s.nameGiven = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + } + else if (reader.name().toString() == "Organisation" && reader.attributes().hasAttribute("type")) { + if (reader.attributes().value("type").toString() == "NationalFederation") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Country") { + if (reader.attributes().hasAttribute("code")) + s.countryCode = reader.attributes().value("code").toString(); + s.countryName = reader.readElementText(); + } + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + s.clubIdIof = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + } + } + else if (reader.attributes().value("type").toString() == "Club") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") { + if (reader.attributes().hasAttribute("code")) + s.clubCode = reader.attributes().value("code").toString(); + s.clubName = reader.readElementText(); + } + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + s.clubIdIof = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + else if (reader.name().toString() == "ControlCard" && reader.attributes().hasAttribute("punchingSystem") && reader.attributes().value("punchingSystem").toString() == "SI") { + s.siNumber = reader.readElementText().toInt(); + } + else if (reader.name().toString() == "Class") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + s.className = reader.readElementText(); + else if (reader.name().toString() == "ShortName") + s.classNameShort = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else if (reader.name().toString() == "RaceNumber") + s.enterRaces.insert(reader.readElementText().toInt()); + else if (reader.name().toString() == "Leg") + s.legNumber = reader.readElementText().toInt(); + else if (reader.name().toString() == "Extensions") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Note") + s.noteOris = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + + return result; +} + +bool XmlImporter::readRaceNode(SRace &s, QXmlStreamReader &reader) +{ + QDateTime dt; + dt.setTimeSpec(Qt::UTC); + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + s.name = reader.readElementText(); + else if (reader.name().toString() == "RaceNumber") + s.number = reader.readElementText().toInt(); + else if (reader.name().toString() == "StartTime") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Date") + dt.setDate(QDate::fromString(reader.readElementText(),Qt::ISODate)); + else if (reader.name().toString() == "Time") { + dt.setTime(QTime::fromString(reader.readElementText(),Qt::ISODate)); + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + s.datetime = dt.toTimeSpec(Qt::LocalTime); + return s.number != 0 && s.datetime.isValid(); +} + +bool XmlImporter::importEntries(QXmlStreamReader &reader, const XmlCreators creator) +{ + QMap classes_map; // classes.name->classes.id + qf::core::sql::Query q; + q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw); + while(q.next()) { + classes_map[q.value(1).toString()] = q.value(0).toInt(); + } + if (classes_map.size() == 0){ + qfError() << "Undefined classes for entries"; + return false; + } + + QMap clubs_map; + if (creator == XmlCreators::Eventor) { + q.exec("SELECT abbr, importId FROM clubs", qf::core::Exception::Throw); + while(q.next()) { + clubs_map[q.value(1).toInt()] = q.value(0).toString(); + } + if (clubs_map.size() == 0){ + qfError() << "Undefined clubs for entries"; + return false; + } + } + + int selected_race = 0; // if entries has more races in (defined in Event), selected race + // load from XML & insert to db + int items_processed = 0; + int relays_processed = 0; + qf::core::sql::Transaction transaction; + while(reader.readNextStartElement()) { + if(reader.name().toString() == "PersonEntry") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + + if (selected_race != 0 && person.enterRaces.size() > 0 && !person.enterRaces.contains(selected_race)) { + qfInfo() << "Skip entry" << person.nameGiven << person.nameFamily << "- not participate in this race"; + } + else { + Competitors::CompetitorDocument doc; + doc.setEmitDbEventsOnSave(false); + doc.loadForInsert(); + int class_id = classes_map.value(person.className); + if(class_id == 0) { + class_id = classes_map.value(person.classNameShort); + if(class_id == 0) { + qfError() << "Undefined class name:" << person.className << "for runner:" << person.nameGiven << " " << person.nameFamily; + transaction.rollback(); + return false; + } + } + doc.setValue("classId", class_id); + if(person.siNumber > 0) + doc.setSiid(person.siNumber); + doc.setValue("firstName", person.nameGiven); + doc.setValue("lastName", person.nameFamily); + if (creator == XmlCreators::Oris) { + doc.setValue("registration", person.regCz); + doc.setValue("note", person.noteOris); + if (person.orisId.has_value()) + doc.setValue("importId",person.orisId.value()); + } + else if (creator == XmlCreators::Eventor) { + if (!person.clubIdIof.has_value()) { + // without club & federation + doc.setValue("registration",person.nationalityCode); + } + else { + QString club_abbr = (person.clubIdIof.has_value()) ? clubs_map[person.clubIdIof.value()] : ""; + if (!club_abbr.isEmpty()) + doc.setValue("registration",club_abbr); + if (!person.clubName.isEmpty()) + doc.setValue("club",person.clubName); + else if (!person.countryName.isEmpty()) + doc.setValue("club",person.countryName); + } + doc.setValue("country",person.nationalityName); + if (person.iofId.has_value()) + doc.setValue("importId",person.iofId.value()); + } + if (person.iofId.has_value()) + doc.setValue("iofId", person.iofId.value()); + doc.save(); + items_processed++; + } + } + else + qfWarning() << "Failed to read runner entry on pos" << items_processed << " [" << person.className << "," + << person.nameFamily << "," << person.nameGiven << "," + << ((person.iofId.has_value()) ? person.iofId.value(): -1 ) << "," << person.regCz << "," + << person.countryCode << "]"; + } + else if (reader.name().toString() == "TeamEntry") { + QString relay_name; + QString relay_club; + QString relay_number; + std::map legs; + QString country_code, country,class_name; + QString club_name,club_country_code,club_country; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") { + relay_name = reader.readElementText(); + relay_number = relay_name.right(1); + } + else if (reader.name().toString() == "TeamEntryPerson") { + SPerson person; + if (readPersonNode(person,reader,creator)) { + if (person.legNumber > 0) + legs.insert({person.legNumber,person}); + } + } + else if (reader.name().toString() == "Organisation" && reader.attributes().hasAttribute("type")) { + if (reader.attributes().value("type").toString() == "NationalFederation") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + club_name = reader.readElementText(); + else if (reader.name().toString() == "Country") { + if (reader.attributes().hasAttribute("code")) + relay_club = club_country_code = reader.attributes().value("code").toString(); + club_country = reader.readElementText(); + } + else + reader.skipCurrentElement(); + } + } + else if (reader.attributes().value("type").toString() == "Club" || reader.attributes().value("type").toString() == "Other") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") { + club_name = reader.readElementText(); + } + else if (reader.name().toString() == "Country") { + if (reader.attributes().hasAttribute("code")) + country_code = reader.attributes().value("code").toString(); + country = reader.readElementText(); // need to be here, for next element + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + else if (reader.name().toString() == "Class") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") { + class_name = reader.readElementText(); + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + // add relay entry + int class_id = classes_map.value(class_name); + if(class_id == 0) { + qfError() << "Undefined class name:" << class_name << "for relay:" << relay_name; + transaction.rollback(); + return false; + } + qfInfo() << relay_club << "|" << relay_name << "|" << relay_number << "|" << class_name << "|" << class_id; + q.execThrow("SELECT id FROM relays WHERE" + " name='" + relay_number + "'" + " AND club='" + relay_club + "'" + " AND classId=" + QString::number(class_id)); + int relay_id; + if(q.next()) { + relay_id = q.value(0).toInt(); + q.execThrow("UPDATE relays SET importId=2 WHERE id=" + QString::number(relay_id)); + } + else { + q.execThrow("INSERT INTO relays (classId, club, name, importId) VALUES (" + + QString::number(class_id) + ", " + + "'" + relay_club + "', " + + "'" + relay_number + "', " + + "2" + + ")"); + relay_id = q.lastInsertId().toInt(); + } + relays_processed++; + for (auto&leg : legs) { + if (creator == XmlCreators::Eventor) { + qfInfo() << '\t' << leg.first << leg.second.nameFamily << leg.second.nameGiven << ((leg.second.iofId.has_value()) ? leg.second.iofId.value() : 0 ) << leg.second.siNumber; + if (!leg.second.iofId.has_value()) + continue; + + Competitors::CompetitorDocument doc; + doc.setEmitDbEventsOnSave(false); + doc.loadForInsert(); + doc.setValue("classId", class_id); + if(leg.second.siNumber > 0) + doc.setSiid(leg.second.siNumber); + doc.setValue("firstName", leg.second.nameGiven); + doc.setValue("lastName", leg.second.nameFamily); + + if (!leg.second.clubIdIof.has_value()) { + // without club & federation + doc.setValue("registration",leg.second.nationalityCode); + } + else { + QString club_abbr = (leg.second.clubIdIof.has_value()) ? clubs_map[leg.second.clubIdIof.value()] : ""; + if (!club_abbr.isEmpty()) + doc.setValue("registration",club_abbr); + if (!leg.second.clubName.isEmpty()) + doc.setValue("club",leg.second.clubName); + else if (!leg.second.countryName.isEmpty()) + doc.setValue("club",leg.second.countryName); + } + doc.setValue("country",leg.second.nationalityName); + if (leg.second.iofId.has_value()) + doc.setValue("importId",leg.second.iofId.value()); + if (leg.second.iofId.has_value()) + doc.setValue("iofId", leg.second.iofId.value()); + if (!doc.save()) + continue; + items_processed++; + int run_id = doc.lastInsertedRunsIds().first(); + q.execThrow("UPDATE runs SET" + " relayId=" + QString::number(relay_id) + "," + " leg=" + QString::number(leg.first) + "," + " importId=2" + " WHERE id=" + QString::number(run_id) + ); + } + else + qfInfo() << '\t' << "not supported" << leg.first << leg.second.nameFamily << leg.second.nameGiven << leg.second.regCz << leg.second.siNumber; + } + } + else if (reader.name().toString() == "Event") { + QMap races; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Race") { + SRace race; + if (readRaceNode(race,reader)) { + QString race_str = QString("[%1] %2 %3").arg(race.number).arg(race.name).arg(race.datetime.date().toString(Qt::ISODate)); + races[race_str] = race.number; + } + } + else + reader.skipCurrentElement(); + } + if (races.size() > 0) { + // when defined some races ... + if (races.size() == 1) + selected_race = races.first(); + else { + QStringList items; + auto it = races.begin(); + while (it != races.end()) { + items << it.key(); + it++; + } + bool ok; + QString item = QInputDialog::getItem(qf::qmlwidgets::framework::MainWindow::frameWork(), tr("Select which race import)"), + tr("Races:"), items, 0, false, &ok); + if (ok && !item.isEmpty()) + selected_race = races[item]; + else + return false; + } + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0 || relays_processed > 0) { + transaction.commit(); + qfInfo() << "Imported entry for" << items_processed << "runners and for" << relays_processed << "relays."; + getPlugin()->emitReloadDataRequest(); + getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED); + } + + return items_processed > 0; +} + +bool XmlImporter::importStartlist(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load from XML & insert to db + int items_processed = 0; + qf::core::sql::Transaction transaction; + while(reader.readNextStartElement()) { + if(reader.name().toString() == "PersonEntry") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + //insert to db + items_processed++; + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported entry for" << items_processed << "runners."; + getPlugin()->emitReloadDataRequest(); + getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED); + } + + return items_processed > 0; +} + +bool XmlImporter::importClasses(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load data from XML + bool is_relays = getPlugin()->eventConfig()->isRelays(); + qf::core::sql::Transaction transaction; + int items_processed = 0; + while(reader.readNextStartElement()) { + if(reader.name().toString() == "Class") { + QString class_name; + int class_id = 0; + int legs_cnt = 0; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + class_name = reader.readElementText(); + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "ORIS" && creator == XmlCreators::Oris) + class_id = reader.readElementText().toInt(); + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF" && creator == XmlCreators::Eventor) + class_id = reader.readElementText().toInt(); + else if (reader.name().toString() == "Leg") { + legs_cnt++; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + qfInfo() << "Leg name:"<< reader.readElementText(); // need to be here, for next element + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + + // insert to db + if (class_id != 0 && !class_name.isEmpty()) { + try { + Classes::ClassDocument doc; + qfInfo() << "adding class id:" << class_id << "name:" << class_name; + doc.loadForInsert(); + doc.setValue("id", class_id); + doc.setValue("name", class_name); + doc.save(); + + if (legs_cnt > 0 && is_relays) { + qf::core::sql::Query q; + q.execThrow("UPDATE classdefs SET" + " relayLegCount=" + QString::number(legs_cnt) + + " WHERE classId=" + QString::number(class_id) + + " AND stageId=1" + ); + } + items_processed++; + } + catch (const qf::core::Exception &e) { + qfError() << "Import Class " << class_name << " ERROR:" << e.message(); + } + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "classes."; +// getPlugin()->emitReloadDataRequest(); -- not exists + } + + return (items_processed > 0); +} + +QString XmlImporter::genFakeCzClubAbbr(QString country) +{ + if (country.isEmpty()) + return ""; + QString c = QString(country[0]); + int pos = fakeCzClubMap[c]; + fakeCzClubMap[c]++; + QString result = QString("%1%2").arg(c).arg(pos,2,36,QLatin1Char('0')).toUpper(); + if (result == country) + return genFakeCzClubAbbr(country); + else + return result; +} + + +bool XmlImporter::importClubs(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load data from XML + qf::core::sql::Transaction transaction; + qf::core::sql::Query q; + q.exec("DELETE FROM clubs", qf::core::Exception::Throw); + q.prepare("INSERT INTO clubs (name, abbr, importId) VALUES (:name, :abbr, :importId)", qf::core::Exception::Throw); + int items_processed = 0; + if (creator == XmlCreators::Eventor) + fakeCzClubMap.clear(); + while(reader.readNextStartElement()) { + if(reader.name().toString() == "Organisation" && reader.attributes().hasAttribute("type")) { + QString name; + + // ORIS + QString abbr_cz; + QString code_cz; + int id_cz = -1; + // Eventor + int id_iof = -1; + QString country; + QString country_code; + bool federation_iof = false; + + if (reader.attributes().value("type").toString() == "NationalFederation") { + federation_iof = true; + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "IOF") + id_iof = reader.readElementText().toInt(); + else if (reader.name().toString() == "Name") + name = reader.readElementText(); + else if (reader.name().toString() == "Country") { + if (reader.attributes().hasAttribute("code")) + country_code = reader.attributes().value("code").toString(); + country = reader.readElementText(); + } + else + reader.skipCurrentElement(); + } + } + else if (reader.attributes().value("type").toString() == "Club" || reader.attributes().value("type").toString() == "Other") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "ORIS") + id_cz = reader.readElementText().toInt(); + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "IOF") + id_iof = reader.readElementText().toInt(); + else if (reader.name().toString() == "Name") { + if (reader.attributes().hasAttribute("code")) + code_cz = reader.attributes().value("code").toString(); + name = reader.readElementText(); + } + else if (reader.name().toString() == "Extensions") + { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Abbreviation") + abbr_cz = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else if (reader.name().toString() == "Country") { + if (reader.attributes().hasAttribute("code")) + country_code = reader.attributes().value("code").toString(); + reader.readElementText(); // need to be here, for next element + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + + // insert to db + if (!name.isEmpty() || (federation_iof && !country.isEmpty())) { + if (creator == XmlCreators::Oris) { + q.bindValue(":name", name); + q.bindValue(":abbr", abbr_cz); + q.bindValue(":importId", id_cz); + } + else if (creator == XmlCreators::Eventor) { + if (federation_iof) { + q.bindValue(":abbr", country_code); + q.bindValue(":name", country); + } + else { + q.bindValue(":name", name); + q.bindValue(":abbr", genFakeCzClubAbbr(country_code)); + } + q.bindValue(":importId", id_iof); + } + q.exec(qf::core::Exception::Throw); + items_processed++; + } + } + else + reader.skipCurrentElement(); + } + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "clubs."; + } + + if (creator == XmlCreators::Eventor){ + int max_item = -1; + QString key; + auto it = fakeCzClubMap.begin(); + while (it != fakeCzClubMap.end()) { + if (it.value() > max_item) { + max_item = it.value(); + key = it.key(); + } + it++; + } + qfInfo() << "Maximum clubs from one country was" << max_item << "in" << key; + fakeCzClubMap.clear(); + } + return items_processed > 0; +} + +bool XmlImporter::importRegistration(QXmlStreamReader &reader, const XmlCreators creator) +{ + QMap clubs_map; + qf::core::sql::Query q; + if (creator == XmlCreators::Eventor) { + q.exec("SELECT abbr, importId FROM clubs", qf::core::Exception::Throw); + while(q.next()) { + clubs_map[q.value(1).toInt()] = q.value(0).toString(); + } + if (clubs_map.size() == 0){ + qfError() << "Undefined clubs for registration"; + return false; + } + } + qf::core::sql::Transaction transaction; + q.exec("DELETE FROM registrations", qf::core::Exception::Throw); + q.prepare("INSERT INTO registrations (firstName, lastName, registration, licence, clubAbbr, country, siId, importId) VALUES (:firstName, :lastName, :registration, :licence, :clubAbbr, :country, :siId, :importId)", qf::core::Exception::Throw); + // load data from XML + int items_processed = 0; + while(reader.readNextStartElement()) { + if(reader.name().toString() == "Competitor") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + q.bindValue(":firstName", person.nameGiven); + q.bindValue(":lastName", person.nameFamily); + if (creator == XmlCreators::Oris) { + q.bindValue(":licence", ""); + if (person.orisId.has_value()) + q.bindValue(":importId", person.orisId.value()); + else + q.bindValue(":importId", 0); + q.bindValue(":siId", person.siNumber); + if(!person.regCz.isEmpty()) { + q.bindValue(":registration", person.regCz); + q.bindValue(":clubAbbr", person.regCz.mid(0, 3)); + } + } + else if (creator == XmlCreators::Eventor) { + QString club_abbr = (person.clubIdIof.has_value()) ? clubs_map.value(person.clubIdIof.value()) : ""; + if(!club_abbr.isEmpty()) { + q.bindValue(":clubAbbr", club_abbr); + q.bindValue(":registration", person.regCz); + } + if (person.iofId.has_value()) + q.bindValue(":importId", person.iofId.value()); + else + q.bindValue(":importId", 0); + } + q.bindValue(":country",person.nationalityCode); + q.exec(qf::core::Exception::Throw); + items_processed++; + } + else + qfWarning() << "Failed to read runner registration on pos" << items_processed; + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "registered runners."; + getPlugin()->emitDbEvent(EventPlugin::DBEVENT_REGISTRATIONS_IMPORTED, QVariant(), true); + } + + return items_processed > 0; +} + +bool XmlImporter::importEvent(QXmlStreamReader &reader, const XmlCreators creator) +{ + if (creator == XmlCreators::Oris) { + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + qfd::MessageBox::showWarning(fwk, QString(tr("EventList from ORIS is not yet supported. It requires a different type of handling."))); + return false; // ORIS has only list with all races - need to select/find current race + } + QString race_name; + int event_id = -1; + QMap races; + Event::EventConfig::Discipline discipline_id = Event::EventConfig::Discipline::Classic; + + while(reader.readNextStartElement()) { + if(reader.name().toString() == "Event") { + while(reader.readNextStartElement()) { + if (reader.name().toString() == "Name") + race_name = reader.readElementText(); + else if (reader.name().toString() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + event_id = reader.readElementText().toInt(); + else if (reader.name().toString() == "Form") { + auto form = reader.readElementText(); + if (form == "Relay") + discipline_id = Event::EventConfig::Discipline::Relays; + else if (form == "Team") + discipline_id = Event::EventConfig::Discipline::Teams; + // default Individual == Event::EventConfig::Discipline::Classic + } + else if (reader.name().toString() == "Race") { + SRace race; + if (readRaceNode(race,reader)) { + QString race_str = QString("[%1] %2 %3").arg(race.number).arg(race.name).arg(race.datetime.date().toString(Qt::ISODate)); + races[race_str] = race; + } + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + SRace event_race; // if event has more races in (defined in Event), selected race + if (races.size() > 0) { + // when defined some races ... + if (races.size() == 1) + event_race = races.first(); + else { + QStringList items; + auto it = races.begin(); + while (it != races.end()) { + items << it.key(); + it++; + } + bool ok; + QString item = QInputDialog::getItem(qf::qmlwidgets::framework::MainWindow::frameWork(), tr("Select which race import)"), + tr("Races:"), items, 0, false, &ok); + if (ok && !item.isEmpty()) + event_race = races[item]; + else + return false; + } + } + else + return false; + try { + QVariantMap ecfg; + ecfg["stageCount"] = 1; + ecfg["name"] = (event_race.name.isEmpty()) ? race_name : event_race.name; + ecfg["description"] = (event_race.name.isEmpty())? QString() : race_name; + ecfg["date"] = event_race.datetime.date(); + ecfg["place"] = QString(); + ecfg["mainReferee"] = QString(); + ecfg["director"] = QString(); + ecfg["sportId"] = static_cast(Event::EventConfig::Sport::OB); + ecfg["disciplineId"] = static_cast(discipline_id); + ecfg["importId"] = event_id; + ecfg["time"] = event_race.datetime.time(); + ecfg["iofRace"] = 1; + ecfg["iofXmlRaceNumber"] = (races.size() > 1) ? event_race.number : 0; + return getPlugin()->createEvent(QString(), ecfg); + } + catch (qf::core::Exception &e) { + qfError() << "Import Event ERROR:" << e.message(); + } + return true; +} + +bool XmlImporter::importXML30() +{ + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open IOF XML 3.0 file"), QString(), tr("IOF XML v3 files (*.xml)")); + if(fn.isEmpty()) + return false; + QFile file(fn); + if(!file.open(QFile::ReadOnly | QFile::Text)) + return false; + + QXmlStreamReader reader(&file); + if (reader.readNextStartElement()) { + XmlCreators creator = XmlCreators::QuickEvent; + if (reader.attributes().hasAttribute("creator")) { + QString name = reader.attributes().value("creator").toString(); + if (name == "ORIS") + creator = XmlCreators::Oris; + else if (name == "Eventor") + creator = XmlCreators::Eventor; + } + + if (reader.name().toString() == "EntryList") + return importEntries(reader, creator); + else if (reader.name().toString() == "StartList") + return importStartlist(reader, creator); + else if (reader.name().toString() == "ClassList") + return importClasses(reader, creator); + else if (reader.name().toString() == "OrganisationList") // clubs + return importClubs(reader, creator); + else if (reader.name().toString() == "CompetitorList") // registration + return importRegistration(reader, creator); + else if (reader.name().toString() == "EventList") + return importEvent(reader, creator); + else + qfd::MessageBox::showWarning(fwk, QString(tr("Unsuported IOF XML 3.0 type (%1)")).arg(reader.name())); + } + return false; +} + diff --git a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h new file mode 100644 index 000000000..ae44634d0 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h @@ -0,0 +1,74 @@ +#ifndef XMLIMPORTER_H +#define XMLIMPORTER_H + +#include +#include +#include +#include +#include +#include + +class XmlImporter : public QObject +{ + Q_OBJECT +public: + enum class XmlCreators + { + QuickEvent = 0, + Oris, + Eventor, + }; + struct SPerson + { + QString nameGiven; + QString nameFamily; + + QString className; + QString classNameShort; // backup, if not defined className + + std::optional iofId; + QString regCz; // ORIS + QString noteOris; // ORIS + std::optional orisId; // ORIS + + int siNumber = 0; + + QString nationalityCode; // Person + QString nationalityName; // Person + QString clubCode; // Organisation - Club + QString clubName; // Organisation - Club + QString countryCode; // Organisation - NationalFederation + QString countryName; // Organisation - NationalFederation + std::optional clubIdIof; // Eventor (Club or NationalFederation ID) + + QSet enterRaces; + + int legNumber = 0; + }; + + struct SRace + { + int number = 0; + QString name; + QDateTime datetime; + }; + + explicit XmlImporter(QObject *parent = nullptr); + + Q_INVOKABLE bool importXML30(); +protected: + bool readPersonNode(SPerson &s, QXmlStreamReader &reader, const XmlCreators creator); + bool readRaceNode(SRace &s, QXmlStreamReader &reader); + + bool importEntries(QXmlStreamReader &reader, const XmlCreators creator); + bool importStartlist(QXmlStreamReader &reader, const XmlCreators creator); + bool importClasses(QXmlStreamReader &reader, const XmlCreators creator); + bool importClubs(QXmlStreamReader &reader, const XmlCreators creator); + bool importRegistration(QXmlStreamReader &reader, const XmlCreators creator); + bool importEvent(QXmlStreamReader &reader, const XmlCreators creator); + + QString genFakeCzClubAbbr(QString country); + QMap fakeCzClubMap; +}; + +#endif // XMLIMPORTER_H diff --git a/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp b/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp index 9d423db1c..333e0fb9f 100644 --- a/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp +++ b/quickevent/app/quickevent/plugins/Relays/src/relaysplugin.cpp @@ -98,6 +98,12 @@ void RelaysPlugin::processRunnerFinished(const quickevent::core::si::CheckedCard namespace { +struct Organization { + int id = 0; + QString name; + QString shortName; +}; + struct Leg { QString fullName; @@ -105,6 +111,8 @@ struct Leg QString lastName; QString reg; QString iofId; + Organization org; + QString country; int runId = 0; //int courseId = 0; int time = 0; @@ -116,7 +124,7 @@ struct Leg quickevent::core::RunStatus runStatus; bool isFinishedOk() const { - return ogTime() < quickevent::core::og::TimeMs::UNREAL_TIME_MSEC; + return ogTime() < quickevent::core::og::TimeMs::MAX_REAL_TIME_MSEC; } int ogTime() const { @@ -129,11 +137,6 @@ struct Leg } }; -struct Organization { - QString name; - QString shortName; -}; - struct Relay { QString name; @@ -152,15 +155,18 @@ struct Relay else return leg.ogTime(); } - return ret; + return (legs.count() < leg_cnt) ? 0 : ret; } QString status(int leg_cnt) const - { + { // status after N legs (summary status) for (int i = 0; i < qMin(legs.count(), leg_cnt); ++i) { const Leg &leg = legs[i]; - return leg.runStatus.toXmlExportString(); + if(!leg.runStatus.isOk()) + return leg.runStatus.toXmlExportString(); } - return QStringLiteral("DidNotStart"); // relay leg not found + if (legs.count() < leg_cnt) + return QStringLiteral("DidNotStart"); // relay leg not found + return QStringLiteral("OK"); } }; } @@ -201,6 +207,16 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsResultsTable(const QString &where_ return tt; } +QPair getClubFromName(QString name) +{ + qf::core::sql::Query q; + q.exec(QStringLiteral("SELECT importId, abbr, name FROM clubs WHERE name='%1'").arg(name), qf::core::Exception::Throw); + if (q.next()) + return qMakePair(q.value(0).toInt(), q.value(1).toString()); + return qMakePair(0,""); +} + + qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, int leg_count, int max_places, bool exclude_not_finish) { int max_leg = 0; @@ -226,7 +242,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in { qfs::QueryBuilder qb; qb.select2("relays", "id, club, name, number") - .select2("clubs", "name, abbr") + .select2("clubs", "id, name, abbr, importId") .select("COALESCE(relays.club, '') || ' ' || COALESCE(relays.name, '') AS relayName") .from("relays") .join("relays.club", "clubs.abbr") @@ -238,6 +254,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in r.relayNumber = q.value("relays.number").toInt(); r.name = q.value("relayName").toString(); r.org = { + .id = q.value("clubs.importId").toInt(), .name = q.value("clubs.name").toString(), .shortName = q.value("clubs.abbr").toString() }; @@ -252,7 +269,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in qfs::QueryBuilder qb; qb.select2("competitors", "id, registration, iofId") .select2("runs", "id, relayId, leg, isRunning") - .select2("competitors", "firstName, lastName") + .select2("competitors", "firstName, lastName, club, country") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .from("runs") .join("runs.competitorId", "competitors.id") @@ -277,6 +294,11 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in leg.reg = q.value("competitors.registration").toString(); leg.iofId = q.value("competitors.iofId").toString(); //leg.courseId = getPlugin()->courseForRun(leg.runId); + leg.org.name = q.value("competitors.club").toString(); + auto club = getClubFromName(leg.org.name); + leg.org.id = club.first; + leg.org.shortName = club.second; + leg.country = q.value("competitors.country").toString(); break; } } @@ -343,7 +365,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in } std::sort(relay_stime.begin(), relay_stime.end(), [](const LegTime &a, const LegTime &b) {return a.stime < b.stime;}); int pos = 0; - int winner_time = relay_stime.begin()->stime; + int winner_time = (relay_stime.size() != 0) ? relay_stime.begin()->stime : 0; for(const auto &p : relay_stime) { int relay_id = p.relayId; for (int i = 0; i < relays.count(); ++i) { @@ -393,6 +415,7 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in tt_row.setValue("pos", (time <= qog::TimeMs::MAX_REAL_TIME_MSEC && time > prev_time)? i+1: 0); tt_row.setValue("name", relay.name); tt_row.setValue("relayNumber", relay.relayNumber); + tt_row.setValue("orgId", relay.org.id); tt_row.setValue("orgName", relay.org.name); tt_row.setValue("orgShortName", relay.org.shortName); tt_row.setValue("id", relay.relayId); @@ -433,6 +456,10 @@ qf::core::utils::TreeTable RelaysPlugin::nLegsClassResultsTable(int class_id, in tt2_row.setValue("spos", leg.spos); tt2_row.setValue("runId", leg.runId); tt2_row.setValue("sstatus", relay.status(j+1)); + tt2_row.setValue("orgId", leg.org.id); + tt2_row.setValue("orgName", leg.org.name); + tt2_row.setValue("orgShortName", leg.org.shortName); + tt2_row.setValue("country", leg.country); //tt2_row.setValue("courseId", leg.courseId); tt2.setRow(ix2, tt2_row); qfDebug() << '\t' << leg.pos << leg.fullName; @@ -525,6 +552,8 @@ QString RelaysPlugin::resultsIofXml30() { QDateTime start00 = getPlugin()->stageStartDateTime(1); qfDebug() << "creating table"; + Event::EventConfig *event_config = getPlugin()->eventConfig(); + bool is_iof_race = event_config->isIofRace(); //auto tt_classes = getPlugin()->nLegsResultsTable("classes.name='D105'", 999, 999999, false); auto tt_classes = getPlugin()->nLegsResultsTable(QString(), 999, 999999, false); QVariantList result_list{ @@ -540,7 +569,8 @@ QString RelaysPlugin::resultsIofXml30() { QVariantList event_lst{"Event"}; QVariantMap event = tt_classes.value("event").toMap(); - event_lst.insert(event_lst.count(), QVariantList{"Id", QVariantMap{{"type", "ORIS"}}, event.value("importId")}); + if (!is_iof_race) + event_lst.insert(event_lst.count(), QVariantList{"Id", QVariantMap{{"type", "ORIS"}}, event.value("importId")}); event_lst.insert(event_lst.count(), QVariantList{"Name", event.value("name")}); event_lst.insert(event_lst.count(), QVariantList{"StartTime", QVariantList{"Date", event.value("date")}, @@ -593,13 +623,27 @@ QString RelaysPlugin::resultsIofXml30() QVariantList{"Name", tt_teams_row.value(QStringLiteral("name")) } ); - append_list(team_result, - QVariantList{"Organisation", - QVariantList{"Name", tt_teams_row.value(QStringLiteral("orgName"))}, - QVariantList{"ShortName", tt_teams_row.value(QStringLiteral("orgShortName"))}, + QString orgName = tt_teams_row.value(QStringLiteral("orgName")).toString(); + if (!orgName.isEmpty()) { + if (is_iof_race) { + append_list(team_result, + QVariantList{"Organisation", + QVariantList{"Id", QVariantMap{{"type", "IOF"}},tt_teams_row.value(QStringLiteral("orgId"))}, + QVariantList{"Name", tt_teams_row.value(QStringLiteral("orgName"))}, + // relay doesn't have country - Eventor national races use club as country + QVariantList{"Country", QVariantMap{{"code", tt_teams_row.value(QStringLiteral("orgShortName"))}},tt_teams_row.value(QStringLiteral("orgName"))}, + } + ); } - ); - + else { + append_list(team_result, + QVariantList{"Organisation", + QVariantList{"Name", tt_teams_row.value(QStringLiteral("orgName"))}, + QVariantList{"ShortName", tt_teams_row.value(QStringLiteral("orgShortName"))}, + } + ); + } + } int relay_number = tt_teams_row.value(QStringLiteral("relayNumber")).toInt(); append_list(team_result, QVariantList{"BibNumber", relay_number } @@ -617,7 +661,8 @@ QString RelaysPlugin::resultsIofXml30() QVariantList member_result{"TeamMemberResult"}; QVariantList person{"Person"}; append_list(person, QVariantList{"Id", QVariantMap{{"type", "QuickEvent"}}, tt_leg_row.value(QStringLiteral("runId"))}); - append_list(person, QVariantList{"Id", QVariantMap{{"type", "CZE"}}, tt_leg_row.value(QStringLiteral("registration"))}); + if (!is_iof_race) + append_list(person, QVariantList{"Id", QVariantMap{{"type", "CZE"}}, tt_leg_row.value(QStringLiteral("registration"))}); auto iof_id = tt_leg_row.value(QStringLiteral("iofId")); if (!iof_id.isNull()) append_list(person, QVariantList{"Id", QVariantMap{{"type", "IOF"}}, iof_id}); @@ -627,6 +672,30 @@ QString RelaysPlugin::resultsIofXml30() append_list(person, QVariantList{"Name", QVariantList{"Family", family}, QVariantList{"Given", given}}); append_list(member_result, person); + QString club_name = tt_leg_row.value(QStringLiteral("orgName")).toString(); + QString club_abbr = tt_leg_row.value(QStringLiteral("orgShortName")).toString(); + if (is_iof_race && !club_name.isEmpty()){ + append_list(member_result, QVariantList{"Organisation", + QVariantList{"Id", QVariantMap{{"type", "IOF"}},tt_leg_row.value(QStringLiteral("orgId"))}, + QVariantList{"Name", club_name}, + } + ); + } + else if (!club_name.isEmpty() && !club_abbr.isEmpty()) { + append_list(member_result, QVariantList{"Organisation", + QVariantList{"Name", club_name}, + QVariantList{"ShortName", club_abbr}, + } + ); + } + else { + append_list(member_result, QVariantList{"Organisation", + QVariantList{"Name", club_name}, + QVariantList{"ShortName", tt_leg_row.value(QStringLiteral("registration")).toString().left(3)} + } + ); + } + QVariantList person_result{"Result"}; append_list(person_result, QVariantList{"Leg", k+1 } ); append_list(person_result, QVariantList{"BibNumber", QString::number(relay_number) + '.' + QString::number(k+1)}); @@ -677,8 +746,10 @@ QString RelaysPlugin::resultsIofXml30() q.execThrow(qb.toString()); if(q.next()) { append_list(course, QVariantList{"Name", q.value(0)}); - append_list(course, QVariantList{"Length", q.value(1)}); - append_list(course, QVariantList{"Climb", q.value(2)}); + if (!q.value(1).toString().isEmpty()) + append_list(course, QVariantList{"Length", q.value(1)}); + if (!q.value(2).toString().isEmpty()) + append_list(course, QVariantList{"Climb", q.value(2)}); } else { qfWarning() << "Cannot load course for id:" << course_id; @@ -738,7 +809,6 @@ QString RelaysPlugin::startListIofXml30() { QDateTime start00 = getPlugin()->stageStartDateTime(1); qfDebug() << "creating table"; - //auto tt_classes = getPlugin()->nLegsResultsTable("classes.name='D105'", 999, 999999, false); qf::core::utils::TreeTable tt_classes = startListByClassesTableData(QString()); QVariantList start_list{ "StartList", @@ -828,6 +898,7 @@ QString RelaysPlugin::startListIofXml30() auto iof_id = tt_leg_row.value(QStringLiteral("iofId")); if (!iof_id.isNull()) append_list(person, QVariantList{"Id", QVariantMap{{"type", "IOF"}}, iof_id}); + append_list(person, QVariantList{"Id", QVariantMap{{"type", "QuickEvent"}}, tt_leg_row.value(QStringLiteral("runs.id"))}); auto family = tt_leg_row.value(QStringLiteral("lastName")); auto given = tt_leg_row.value(QStringLiteral("firstName")); append_list(person, QVariantList{"Name", QVariantList{"Family", family}, QVariantList{"Given", given}}); @@ -872,7 +943,7 @@ QString RelaysPlugin::startListIofXml30() append_list(start_list, class_start); } qf::core::utils::HtmlUtils::FromXmlListOptions opts; - opts.setDocumentTitle(tr("Relays IOF-XML 3.0 results")); + opts.setDocumentTitle(tr("Relays IOF-XML 3.0 startlist")); return qf::core::utils::HtmlUtils::fromXmlList(start_list, opts); } diff --git a/quickevent/app/quickevent/plugins/Runs/src/findrunneredit.cpp b/quickevent/app/quickevent/plugins/Runs/src/findrunneredit.cpp index 15f207acc..2601edfee 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/findrunneredit.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/findrunneredit.cpp @@ -56,20 +56,24 @@ QVariant FindRunnersModel::data(const QModelIndex &index, int role) const static auto col_class_name = fields.fieldIndex(QStringLiteral("classes.name")); static auto col_name = fields.fieldIndex(QStringLiteral("competitorName")); static auto col_searchkey = fields.fieldIndex(QStringLiteral("competitorNameAscii7")); + static int col_bib = fields.fieldIndex(QStringLiteral("competitors.startNumber")); static int col_registration = fields.fieldIndex(QStringLiteral("registration")); static auto col_siid = fields.fieldIndex(QStringLiteral("siid")); static auto SI = QStringLiteral("SI:"); + static auto BIB = QStringLiteral("Bib:"); if(role == Qt::DisplayRole) { return table_row.value(col_class_name).toString() + ' ' + table_row.value(col_name).toString() + ' ' + table_row.value(col_registration).toString() + ' ' - + SI + table_row.value(col_siid).toString(); + + SI + table_row.value(col_siid).toString() + ' ' + + BIB + table_row.value(col_bib).toString(); } else if(role == Qt::EditRole) { return table_row.value(col_class_name).toString() + ' ' + table_row.value(col_searchkey).toString() + ' ' + table_row.value(col_registration).toString() + ' ' + SI + table_row.value(col_siid).toString() + ' ' + + BIB + table_row.value(col_bib).toString() + ' ' + table_row.value(col_name).toString(); } } diff --git a/quickevent/app/quickevent/plugins/Runs/src/findrunnerwidget.ui b/quickevent/app/quickevent/plugins/Runs/src/findrunnerwidget.ui index e96ef8e78..d893bcaf9 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/findrunnerwidget.ui +++ b/quickevent/app/quickevent/plugins/Runs/src/findrunnerwidget.ui @@ -17,7 +17,7 @@ - Name, registration, SI + Name, Registration, SI, Bib true diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 763a94536..7c0c9b569 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -71,7 +71,7 @@ const qf::core::utils::Table &RunsPlugin::runnersTable(int stage_id) { if(m_runnersTableCacheStageId != stage_id) { qfs::QueryBuilder qb; - qb.select2("competitors", "registration") + qb.select2("competitors", "registration, startNumber") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .select2("runs", "id, siId, competitorId") .select("runs.id AS runId") @@ -596,10 +596,10 @@ qf::core::utils::TreeTable RunsPlugin::stageResultsTable(int stage_id, const QSt { qf::core::sql::QueryBuilder qb; - qb.select2("competitors", "registration, iofId, lastName, firstName, startNumber") + qb.select2("competitors", "registration, iofId, lastName, firstName, startNumber, club, country") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .select2("runs", "*") - .select2("clubs", "name, abbr") + .select2("clubs", "name, abbr, importId") .from("competitors") .join("LEFT JOIN clubs ON substr(competitors.registration, 1, 3) = clubs.abbr") .joinRestricted("competitors.id" @@ -792,6 +792,10 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) QDateTime stage_start_date_time = getPlugin()->stageStartDateTime(stage_id);//.toTimeSpec(Qt::OffsetFromUTC); //qfInfo() << stage_start_date_time << datetime_to_string(stage_start_date_time); qf::core::utils::TreeTable tt1 = stageResultsTable(stage_id, QString(), 0, false, true); + Event::EventConfig *event_config = getPlugin()->eventConfig(); + bool is_iof_race = event_config->isIofRace(); + int iof_xml_race_number = event_config->iofXmlRaceNumber(); + QVariantList result_list{ "ResultList", QVariantMap{ @@ -833,6 +837,15 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) }, } ); + + if (iof_xml_race_number != 0) { + event_lst.insert(event_lst.count(), + QVariantList{"Race", + QVariantList{"RaceNumber", iof_xml_race_number}, + QVariantList{"Name", event_config->eventName()} + } + ); + } result_list.insert(result_list.count(), event_lst); } @@ -852,7 +865,8 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) } ); qf::core::utils::TreeTable tt2 = tt1_row.table(); - //int pos = 0; + if (tt2.rowCount() == 0 && is_iof_race) + continue; // not save empty class results int course_id = tt1_row.value(QStringLiteral("courses.id")).toInt(); quickevent::core::CourseDef course_def = courseForCourseId(course_id); QVariantList codes = course_def.codes(); @@ -864,7 +878,9 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) //pos++; QVariantList person_result{"PersonResult"}; QVariantList person{"Person"}; - person.insert(person.count(), QVariantList{"Id", QVariantMap{{"type", "CZE"}}, tt2_row.value(QStringLiteral("competitors.registration"))}); + if (!is_iof_race) + person.insert(person.count(), + QVariantList{"Id", QVariantMap{{"type", "CZE"}}, tt2_row.value(QStringLiteral("competitors.registration"))}); auto iof_id = tt2_row.value(QStringLiteral("competitors.iofId")); if (!iof_id.isNull()) person.insert(person.count(), QVariantList{"Id", QVariantMap{{"type", "IOF"}}, iof_id}); @@ -874,31 +890,44 @@ QString RunsPlugin::resultsIofXml30Stage(int stage_id) QVariantList{"Given", tt2_row.value(QStringLiteral("competitors.firstName"))}, } ); + if (is_iof_race) { + auto nationality = tt2_row.value(QStringLiteral("competitors.country")); + QString nat_code = getClubAbbrFromName(nationality.toString()); + person.insert(person.count(), QVariantList{"Nationality", QVariantMap{{"code", nat_code}}, nationality}); + } person_result.insert(person_result.count(),person); - auto club_abbr = tt2_row.value(QStringLiteral("clubs.abbr")).toString(); - if (!club_abbr.isEmpty()) { + if (is_iof_race){ person_result.insert(person_result.count(), QVariantList{"Organisation", + QVariantList{"Id", QVariantMap{{"type", "IOF"}},tt2_row.value(QStringLiteral("clubs.importId"))}, QVariantList{"Name", tt2_row.value(QStringLiteral("clubs.name"))}, - QVariantList{"ShortName", club_abbr}, } ); } else { - person_result.insert(person_result.count(), - QVariantList{"Organisation", - QVariantList{"Name", QString()}, - QVariantList{"ShortName", - tt2_row.value(QStringLiteral("competitors.registration")).toString().left(3) + auto club_abbr = tt2_row.value(QStringLiteral("clubs.abbr")).toString(); + if (!club_abbr.isEmpty()) { + person_result.insert(person_result.count(), + QVariantList{"Organisation", + QVariantList{"Name", tt2_row.value(QStringLiteral("clubs.name"))}, + QVariantList{"ShortName", club_abbr}, } - } - ); + ); + } + else { + person_result.insert(person_result.count(), + QVariantList{"Organisation", + QVariantList{"Name", QString()}, + QVariantList{"ShortName", + tt2_row.value(QStringLiteral("competitors.registration")).toString().left(3) + } + } + ); + } } - auto run_status = quickevent::core::RunStatus::fromTreeTableRow(tt2_row); - - QVariantList result{"Result"}; + QVariantList result{"Result", (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()) result.insert(result.count(), QVariantList{"BibNumber", bib_number}); @@ -1185,10 +1214,10 @@ 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") + qb2.select2("competitors", "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") + .select2("clubs","name, abbr, importId") .from("competitors") .join("LEFT JOIN clubs ON substr(competitors.registration, 1, 3) = clubs.abbr") .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId={{stage_id}} AND runs.isRunning", "INNER JOIN") @@ -1363,7 +1392,7 @@ qf::core::utils::TreeTable RunsPlugin::startListStartersTable(const QString &whe auto start00_epoch_sec = getPlugin()->stageStartDateTime(stage_id).toSecsSinceEpoch(); qfs::QueryBuilder qb; - qb.select2("competitors", "registration, id, startNumber") + 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") @@ -2383,6 +2412,15 @@ void RunsPlugin::exportResultsHtmlStageWithLaps(const QString &laps_file_name, c } } +QString RunsPlugin::getClubAbbrFromName(QString name) +{ + qf::core::sql::Query q; + q.exec(QStringLiteral("SELECT abbr, name FROM clubs WHERE name='%1'").arg(name), qf::core::Exception::Throw); + if (q.next()) + return q.value(0).toString(); + return ""; +} + QString RunsPlugin::startListStageIofXml30(int stage_id) { QDateTime start00 = getPlugin()->stageStartDateTime(stage_id); @@ -2391,6 +2429,8 @@ QString RunsPlugin::startListStageIofXml30(int stage_id) bool print_vacants = !last_handicap_stage; //console.debug("print_vacants", print_vacants); auto tt1 = startListClassesTable("", print_vacants, quickevent::gui::ReportOptionsDialog::StartTimeFormat::RelativeToClassStart); + bool is_iof_race = event_config->isIofRace(); + int iof_xml_race_number = event_config->iofXmlRaceNumber(); QVariantList xml_root{"StartList" , QVariantMap { @@ -2433,6 +2473,15 @@ QString RunsPlugin::startListStageIofXml30(int stage_id) }, } ); + + if (iof_xml_race_number != 0) { + append_list(xml_event, + QVariantList{"Race", + QVariantList{"RaceNumber", iof_xml_race_number}, + QVariantList{"Name", event_config->eventName()} + } + ); + } append_list(xml_root, xml_event); for(int i=0; i= 6 + csv.setEncoding(QStringConverter::encodingForName("UTF-8").value()); +#else + csv.setCodec("UTF-8"); +#endif +#ifdef Q_OS_WINDOWS + // enable BOM for Windows + csv.setGenerateByteOrderMark(true); +#endif + + auto tt1 = startListClassesTable("",true, quickevent::gui::ReportOptionsDialog::StartTimeFormat::DayTime); + int id = 0; + csv << "IOF ID;First Name;Last Name;Country;Start;Category;Bib;CountryFull;SI"; + csv << Qt::endl; + for(int i=0; i())); setColumn(col_runs_penaltyTimeMs, ColumnDefinition("runs.penaltyTimeMs", tr("Penalty")).setCastType(qMetaTypeId())); setColumn(col_runFlags, ColumnDefinition("runFlags", tr("Run flags")).setReadOnly(true)); - setColumn(col_cardFlags, ColumnDefinition("runFlags", tr("Card flags")).setReadOnly(true)); + setColumn(col_cardFlags, ColumnDefinition("cardFlags", tr("Card flags")).setReadOnly(true)); //setColumn(col_runs_notCompeting, ColumnDefinition("runs.notCompeting", tr("NC")).setToolTip(tr("Not competing"))); //setColumn(col_runs_cardRentRequested, ColumnDefinition("runs.cardLent", tr("RR")).setToolTip(tr("Card rent requested"))); //setColumn(col_cardInLentTable, ColumnDefinition("cardInLentTable", tr("RT", "cardInLentTable")).setToolTip(tr("Card in rent table"))); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp index 09d6b7a73..1897be347 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -272,6 +273,11 @@ void RunsWidget::settleDownInPartWidget(::PartWidget *part_widget) connect(a, &qfw::Action::triggered, this, &RunsWidget::export_startList_stage_csv_sime); m_export_stlist_csv->addActionInto(a); } + { + auto *a = new qfw::Action(tr("&Startlist for TV Graphics")); + connect(a, &qfw::Action::triggered, this, &RunsWidget::export_startList_stage_tv_graphics); + m_export_stlist_csv->addActionInto(a); + } } qfw::Action *a_export_results = a_export->addMenuInto("results", tr("Results")); @@ -294,6 +300,10 @@ void RunsWidget::settleDownInPartWidget(::PartWidget *part_widget) qfw::Action *a = a_export_results_stage->addActionInto("csos", tr("CSOS")); connect(a, &qfw::Action::triggered, this, &RunsWidget::export_results_stage_csos); } + { + qfw::Action *a = a_export_results_stage->addActionInto("csv", tr("&CSV")); + connect(a, &qfw::Action::triggered, this, &RunsWidget::export_results_stage_csv); + } } qfw::Action *a_export_results_overall = a_export_results->addMenuInto("overall", tr("Overall")); { @@ -526,7 +536,7 @@ void RunsWidget::import_start_times_ob2000() qfInfo() << ba; continue; } - int st_time = ((int)d) * 60 + (((int)(d * 100)) % 100); + int st_time = static_cast(d) * 60 + ((static_cast(d * 100)) % 100); st_time *= 1000; QString reg = sl.value(sl.count() - 2) + sl.value(sl.count() - 1); reg = reg.toUpper().trimmed(); @@ -984,3 +994,27 @@ void RunsWidget::export_startList_stage_csv_sime() getPlugin()->exportStartListCurrentStageCsvSime(fn, dlg.isStartListPrintStartNumbers(), dlg.sqlWhereExpression()); } } + +void RunsWidget::export_startList_stage_tv_graphics() +{ + QString fn = getSaveFileName("startlist_tv.csv", selectedStageId()); + if(fn.isEmpty()) + return; + getPlugin()->exportStartListCurrentStageTvGraphics(fn); +} + +void RunsWidget::export_results_stage_csv() +{ + QString fn = getSaveFileName("results.csv", selectedStageId()); + if(fn.isEmpty()) + return; + + bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); + QFileInfo fi(fn); + exp.setOutDir(fi.absolutePath()); + exp.setOutFile(fi.fileName()); + exp.setSimplePath(true); + exp.setWithDidNotStart(true); + exp.generateCsvSingle(); +} diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index ffd9ec439..58ee7cdc6 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -49,10 +49,12 @@ class RunsWidget : public QFrame public: void export_startList_stage_iofxml30(); void export_startList_stage_csv_sime(); + void export_startList_stage_tv_graphics(); void export_results_stage_iofxml30(); void export_results_stage_csos(); void export_results_overall_csos(); + void export_results_stage_csv(); private slots: void on_btDraw_clicked(); diff --git a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp index f55f1ecf1..63098c371 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.cpp @@ -95,20 +95,24 @@ bool ResultsExporter::exportResults() return true; } else if(ss.outputFormat() == static_cast(ResultsExporterSettings::OutputFormat::CSVMulti)) { - quickevent::core::exporters::StageResultsCsvExporter exp; + bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); exp.setOutDir(ss.exportDir()); if (!ss.csvSeparator().isNull()) exp.setSeparator(ss.csvSeparator()); + exp.setWithDidNotStart(ss.exportCsvWithDNS()); exp.generateCsvMulti(); whenFinishedRunCmd(); return true; } else if(ss.outputFormat() == static_cast(ResultsExporterSettings::OutputFormat::CSV)) { - quickevent::core::exporters::StageResultsCsvExporter exp; + bool is_iof_race = getPlugin()->eventConfig()->isIofRace(); + quickevent::core::exporters::StageResultsCsvExporter exp(is_iof_race); exp.setOutDir(ss.exportDir()); if (!ss.csvSeparator().isNull()) exp.setSeparator(ss.csvSeparator()); + exp.setWithDidNotStart(ss.exportCsvWithDNS()); exp.generateCsvSingle(); whenFinishedRunCmd(); diff --git a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.h b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.h index 4673da1cd..81819af85 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.h +++ b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporter.h @@ -20,6 +20,7 @@ class ResultsExporterSettings : public Event::services::ServiceSettings QF_VARIANTMAP_FIELD2(int, o, setO, utputFormat, static_cast(OutputFormat::HtmlMulti)) QF_VARIANTMAP_FIELD2(bool, e, setE, xportMultiFile, true) QF_VARIANTMAP_FIELD2(QChar, c, setC, svSeparator, ';') + QF_VARIANTMAP_FIELD2(bool, e, setE, xportCsvWithDNS, false) public: ResultsExporterSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; diff --git a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.cpp index e19351a88..782a0af23 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.cpp @@ -36,12 +36,12 @@ ResultsExporterWidget::ResultsExporterWidget(QWidget *parent) ui->lstOutputFormat->setCurrentIndex(ui->lstOutputFormat->findData(of)); if (ss.csvSeparator() == QChar::Tabulation) ui->rbCsvSeparatorTab->setChecked(true); - else - { + else { ui->rbCsvSeparatorChar->setChecked(true); if (!ss.csvSeparator().isNull()) ui->edCsvSeparator->setText(ss.csvSeparator()); } + ui->cbCsvExportWithDNS->setChecked(ss.exportCsvWithDNS()); } connect(ui->btChooseExportDir, &QPushButton::clicked, this, &ResultsExporterWidget::onBtChooseExportDirClicked); @@ -90,6 +90,7 @@ bool ResultsExporterWidget::saveSettings() ss.setCsvSeparator(QChar::Null); else ss.setCsvSeparator(ui->edCsvSeparator->text().at(0)); + ss.setExportCsvWithDNS(ui->cbCsvExportWithDNS->isChecked()); svc->setSettings(ss); if(!dir.isEmpty()) { if(!QDir().mkpath(dir)) diff --git a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.ui b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.ui index b0cc73359..8affdf83b 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.ui +++ b/quickevent/app/quickevent/plugins/Runs/src/services/resultsexporterwidget.ui @@ -14,23 +14,17 @@ Results Exporter - - + + - - - - Qt::Vertical - - - - 20 - 40 - + + + + When finished, run cmd - + - + QFrame::NoFrame @@ -77,19 +71,31 @@ - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Output format - - sec + + + + + + CSV Export with DNS && runners without finish time - - 600 + + + + + + CSV separator - - 15 + + + + + + Export dir @@ -114,36 +120,8 @@ - - - - Export dir - - - - - - - - - - When finished, run cmd - - - - - - - Output format - - - - - - - CSV separator - - + + @@ -169,6 +147,35 @@ + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sec + + + 600 + + + 15 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -180,6 +187,7 @@ rbCsvSeparatorChar edCsvSeparator rbCsvSeparatorTab + cbCsvExportWithDNS btExportResults