void RunsTableModel::switchStartTimes(int r1, int r2) { qfLogFuncFrame() << r1 << r2; int col_stime = columnIndex("startTimeMs"); QF_ASSERT(col_stime >= 0, "Bad startTimeMs column!", return); int id1 = value(r1, "runs.id").toInt(); int id2 = value(r2, "runs.id").toInt(); QString err_msg; QModelIndex ix1 = index(r1, col_stime); QVariant v1 = ix1.data(Qt::EditRole); QModelIndex ix2 = index(r2, col_stime); QVariant v2 = ix2.data(Qt::EditRole); bool is_single_user = sqlConnection().driverName().endsWith(QLatin1String("SQLITE"), Qt::CaseInsensitive); if(is_single_user) { setData(ix1, v2); setData(ix2, v1); postRow(r1, true); postRow(r2, true); } else { //qf::core::sql::Transaction transaction(sqlConnection()); quickevent::core::og::TimeMs t1 = v1.value<quickevent::core::og::TimeMs>(); quickevent::core::og::TimeMs t2 = v2.value<quickevent::core::og::TimeMs>(); int msec1 = -1, msec2 = -1; qf::core::sql::Query q(sqlConnection()); QString qs = "SELECT id, startTimeMs FROM runs WHERE id IN (" QF_IARG(id1) ", " QF_IARG(id2) ")"; q.exec(qs, qf::core::Exception::Throw); while(q.next()) { int id = q.value("id").toInt(); if(id == id1) msec1 = q.value("startTimeMs").toInt(); else if(id == id2) msec2 = q.value("startTimeMs").toInt(); } qfDebug() << t1.msec() << msec1 << t2.msec() << msec2; if(msec1 == t1.msec() && msec2 == t2.msec()) { setData(ix1, v2); setData(ix2, v1); postRow(r1, qf::core::Exception::Throw); postRow(r2, qf::core::Exception::Throw); //transaction.commit(); } else { err_msg = tr("Mid-air collision switching start times, reload table and try it again."); } } emit startTimesSwitched(id1, id2, err_msg); }
bool RunsTableModel::postRow(int row_no, bool throw_exc) { bool is_single_user = sqlConnection().driverName().endsWith(QLatin1String("SQLITE"), Qt::CaseInsensitive); if(is_single_user) return Super::postRow(row_no, throw_exc); int col_stime = columnIndex("startTimeMs"); QF_ASSERT(col_stime >= 0, "Bad startTimeMs column!", return false); if(isDirty(row_no, col_stime)) { int id = value(row_no, "runs.id").toInt(); int orig_msec = origValue(row_no, col_stime).toInt(); int db_msec = 0; qf::core::sql::Transaction transaction(sqlConnection()); QString qs = "SELECT id, startTimeMs FROM runs WHERE id=" QF_IARG(id) " FOR UPDATE"; qf::core::sql::Query q(transaction.connection()); q.exec(qs, qf::core::Exception::Throw); if(q.next()) { db_msec = q.value("startTimeMs").toInt(); } if(orig_msec == db_msec) { bool ret = Super::postRow(row_no, throw_exc); transaction.commit(); return ret; } else { QString err_msg = tr("Mid-air collision setting start time, reload table and try it again."); revertRow(row_no); if(throw_exc) QF_EXCEPTION(err_msg); return false; } } return Super::postRow(row_no, throw_exc); }
void ClassesWidget::reload() { if(eventPlugin()->eventName().isEmpty()) { m_classesModel->clearRows(); m_courseCodesModel->clearRows(); return; } int stage_id = selectedStageId(); { qf::core::sql::QueryBuilder qb1; qb1.select("COUNT(*)") .from("runs") .join("runs.competitorId", "competitors.id") .where("competitors.classId=classdefs.classId") .where("NOT runs.offRace") .where("runs.stageId=" QF_IARG(stage_id)); qfs::QueryBuilder qb; qb.select2("classes", "*") .select2("classdefs", "*") .select2("courses", "id, name, length, climb") .select("(" + qb1.toString() + ") AS runsCount") .from("classes") .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId=" QF_IARG(stage_id)) .join("classdefs.courseId", "courses.id") .orderBy("classes.name");//.limit(10); /* int class_id = m_cbxClasses->currentData().toInt(); if(class_id > 0) { qb.where("competitors.classId=" + QString::number(class_id)); } */ m_classesModel->setQueryBuilder(qb); m_classesModel->reload(); } { qf::core::sql::Query q(m_classesModel->sqlConnection()); q.exec("SELECT COUNT(*) FROM classdefs WHERE stageId=" QF_IARG(stage_id)); bool ro = true; if(q.next()) ro = (q.value(0).toInt() == 0); if(ro) { qfWarning() << tr("Courses are not imported, class table is read only."); } ui->tblClasses->setReadOnly(ro); } reloadCourseCodes(); }
QVariantMap CardChecker::courseCodesForRunId(int run_id) { QVariantMap ret; if(run_id <= 0) { qfError() << "Run ID == 0"; return ret; } qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); auto runs_plugin = qobject_cast<Runs::RunsPlugin *>(fwk->plugin("Runs")); QF_ASSERT(runs_plugin != nullptr, "Bad plugin", return QVariantMap()); int course_id = runs_plugin->courseForRun(run_id); if(course_id <= 0) { qfError() << "Course ID == 0"; return ret; } { qfs::QueryBuilder qb; qb.select2("courses", "*") .from("courses") .where("courses.id=" QF_IARG(course_id)); qfs::Query q; q.exec(qb.toString(), qf::core::Exception::Throw); if(q.next()) ret = q.values(); } { qfs::QueryBuilder qb; qb.select2("coursecodes", "position") .select2("codes", "code, altCode, outOfOrder") .from("coursecodes") .join("coursecodes.codeId", "codes.id") .where("coursecodes.courseId=" QF_IARG(course_id)) .orderBy("coursecodes.position"); qfs::Query q; //qfWarning() << qb.toString(); q.exec(qb.toString(), qf::core::Exception::Throw); QVariantList codes; while (q.next()) { codes << q.values(); } ret["codes"] = codes; } return ret; }
void RunsTableItemDelegate::setHighlightedClassId(int class_id, int stage_id) { if(m_highlightedClassId == class_id && m_stageId == stage_id) return; m_stageId = stage_id; m_highlightedClassId = 0; qf::core::sql::QueryBuilder qb; qb.select2("classdefs", "startTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter") .from("classdefs") .where("stageId=" QF_IARG(stage_id)) .where("classId=" QF_IARG(class_id)); qfs::Query q(qfs::Connection::forName()); q.exec(qb.toString(), qf::core::Exception::Throw); if(q.next()) { m_highlightedClassId = class_id; m_classInterval = q.value("startIntervalMin").toInt() * 60 * 1000; m_classStart = q.value("startTimeMin").toInt() * 60 * 1000; } }
void RunsTableItemDelegate::reloadHighlightedClassId() { qf::core::sql::QueryBuilder qb; qb.select2("classdefs", "startTimeMin, lastStartTimeMin, startIntervalMin, vacantsBefore, vacantEvery, vacantsAfter") .from("classdefs") .where("stageId=" QF_IARG(m_stageId)) .where("classId=" QF_IARG(m_highlightedClassId)); qfs::Query q(qfs::Connection::forName()); q.exec(qb.toString(), qf::core::Exception::Throw); if(q.next()) { m_classInterval = q.value("startIntervalMin").toInt() * 60 * 1000; m_classStartFirst = q.value("startTimeMin").toInt() * 60 * 1000; m_classStartLast = q.value("lastStartTimeMin").toInt() * 60 * 1000; } else { m_classInterval = 0; m_classStartFirst = 0; m_classStartLast = 0; } }
int CardChecker::startTimeSec(int run_id) { int ret = 0; qfs::QueryBuilder qb; qb.select2("runs", "startTimeMs") .from("runs") .where("runs.id=" QF_IARG(run_id)); qfs::Query q; q.exec(qb.toString(), qf::core::Exception::Throw); if(q.next()) ret = q.value(0).toInt() / 1000; else qfError() << "Cannot find runs record for id:" << run_id; return ret; }
void ReceiptsWidget::reload() { int current_stage = currentStageId(); qfs::QueryBuilder qb; qb.select2("cards", "id, siId, printerConnectionId") .select2("runs", "startTimeMs, timeMs") .select2("competitors", "registration") .select2("classes", "name") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .from("cards") .join("cards.runId", "runs.id") .join("runs.competitorId", "competitors.id") .join("competitors.classId", "classes.id") .where("cards.stageId=" QF_IARG(current_stage)) .orderBy("cards.id DESC"); m_cardsModel->setQueryBuilder(qb); m_cardsModel->reload(); }
void ClassesWidget::reloadCourseCodes() { int current_course_id = 0; auto row = ui->tblClasses->selectedRow(); if(!row.isNull()) current_course_id = row.value("classdefs.courseId").toInt(); if(current_course_id == 0) { ui->lblCourseCodes->setText("---"); } else { ui->lblCourseCodes->setText(row.value("courses.name").toString()); } { qfs::QueryBuilder qb; qb.select2("codes", "*") .select2("coursecodes", "position") .from("coursecodes") .join("coursecodes.codeId", "codes.id") .where("coursecodes.courseId=" QF_IARG(current_course_id)) .orderBy("coursecodes.position"); m_courseCodesModel->setQueryBuilder(qb); m_courseCodesModel->reload(); } }
QVariantMap ReceiptsPlugin::receiptTablesData(int card_id) { qfLogFuncFrame() << card_id; QF_TIME_SCOPE("receiptTablesData()"); QVariantMap ret; CardReader::ReadCard read_card = cardReaderPlugin()->readCard(card_id); CardReader::CheckedCard checked_card = cardReaderPlugin()->checkCard(read_card); int current_stage_id = eventPlugin()->currentStageId(); int run_id = checked_card.runId(); int course_id = checked_card.courseId(); int current_standings = 0; int competitors_finished = 0; QMap<int, int> best_laps; //< position->time ///QMap<int, int> missing_codes; //< pos->code ///QSet<int> out_of_order_codes; { qf::core::model::SqlTableModel model; qf::core::sql::QueryBuilder qb; qb.select2("competitors", "*") .select2("runs", "*") .select2("classes", "name") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .from("runs") .join("runs.competitorId", "competitors.id") .join("competitors.classId", "classes.id") .where("runs.id=" QF_IARG(run_id)); model.setQuery(qb.toString()); model.reload(); if(model.rowCount() == 1) { int class_id = model.value(0, "competitors.classId").toInt(); { // find best laps for competitors class qf::core::sql::QueryBuilder qb_minlaps; // TODO: remove position field from DB in 0.1.5 qb_minlaps.select("runlaps.position, MIN(runlaps.lapTimeMs) AS minLapTimeMs") .from("competitors") .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId=" QF_IARG(current_stage_id) " AND competitors.classId=" QF_IARG(class_id), "JOIN") .joinRestricted("runs.id", "runlaps.runId", "runlaps.position > 0 AND runlaps.lapTimeMs > 0", "JOIN") .groupBy("runlaps.position"); QString qs = qb_minlaps.toString(); //qfInfo() << qs; qf::core::sql::Query q; q.exec(qs); while(q.next()) { int position = q.value("position").toInt(); if(position == 0) { qfWarning() << "position == 0 in best runlaps"; continue; } int lap = q.value("minLapTimeMs").toInt(); if(lap == 0) { qfWarning() << "minLapTimeMs == 0 in best runlaps"; continue; } best_laps[position] = lap; //qfInfo() << "bestlaps[" << pos << "] =" << lap; } } if(checked_card.isOk()) { // find current standings qf::core::sql::QueryBuilder qb; qb.select2("runs", "timeMs") .select("runs.disqualified OR NOT runs.isRunning OR runs.isRunning IS NULL OR runs.misPunch AS dis") .from("competitors") .joinRestricted("competitors.id", "runs.competitorId", "runs.stageId=" QF_IARG(current_stage_id) " AND competitors.classId=" QF_IARG(class_id)) .where("runs.finishTimeMs > 0") .orderBy("misPunch, disqualified, isRunning, runs.timeMs"); //qfInfo() << qb.toString(); qf::core::sql::Query q; q.exec(qb.toString(), qf::core::Exception::Throw); while (q.next()) { bool dis = q.value("dis").toBool(); int time = q.value("timeMs").toInt(); if(!dis) { if(time <= checked_card.timeMs()) current_standings++; } competitors_finished++; } } } qfu::TreeTable tt = model.toTreeTable(); { qf::core::sql::QueryBuilder qb; qb.select2("courses", "length, climb") .select("(SELECT COUNT(*) FROM coursecodes WHERE courseId=courses.id) AS controlCount") .from("courses") .where("courses.id=" QF_IARG(course_id)); qf::core::sql::Query q; q.exec(qb.toString()); if(q.next()) { QSqlRecord rec = q.record(); for (int i = 0; i < rec.count(); ++i) { QString fld_name = rec.fieldName(i); tt.setValue(fld_name, rec.value(i)); } } } { qf::core::sql::QueryBuilder qb; qb.select2("config", "ckey, cvalue, ctype") .from("config") .where("ckey LIKE 'event.%'"); qf::core::sql::Query q; q.exec(qb.toString()); while(q.next()) { QVariant v = qf::core::Utils::retypeStringValue(q.value("cvalue").toString(), q.value("ctype").toString()); tt.setValue(q.value("ckey").toString(), v); } } tt.setValue("stageCount", eventPlugin()->stageCount()); tt.setValue("currentStageId", eventPlugin()->currentStageId()); qfDebug() << "competitor:\n" << tt.toString(); ret["competitor"] = tt.toVariant(); } { qfu::TreeTable tt; tt.appendColumn("position", QVariant::Int); tt.appendColumn("code", QVariant::Int); tt.appendColumn("stpTimeMs", QVariant::Int); tt.appendColumn("lapTimeMs", QVariant::Int); tt.appendColumn("lossMs", QVariant::Int); QMapIterator<QString, QVariant> it(checked_card); while(it.hasNext()) { it.next(); if(it.key() != QLatin1String("punches")) tt.setValue(it.key(), it.value()); } tt.setValue("isOk", checked_card.isOk()); int position = 0; for(auto v : checked_card.punches()) { CardReader::CheckedPunch punch(v.toMap()); qfu::TreeTableRow ttr = tt.appendRow(); ++position; int code = punch.code(); ttr.setValue("position", position); ttr.setValue("code", code); ttr.setValue("stpTimeMs", punch.stpTimeMs()); int lap = punch.lapTimeMs(); ttr.setValue("lapTimeMs", lap); int best_lap = best_laps.value(position); if(lap > 0 && best_lap > 0) { int loss = lap - best_lap; ttr.setValue("lossMs", loss); } } /* { // runlaps table contains also finish time entry, it is under FINISH_PUNCH_POS // currently best_laps[999] contains best finish lap time for this class int loss = 0; int best_lap = best_laps.value(CardReader::CardReaderPlugin::FINISH_PUNCH_POS); if(best_lap > 0) loss = checked_card.finishLapTimeMs() - best_lap; //qfInfo() << "control_count:" << control_count << "finishLapTimeMs:" << checked_card.finishLapTimeMs() << "- best_lap:" << best_lap << "=" << loss; tt.setValue("finishLossMs", loss); } */ { QSet<int> correct_codes; for (int i = 0; i < checked_card.punchCount(); ++i) { correct_codes << checked_card.punchAt(i).code(); } QVariantList xc; for (int i = 0; i < read_card.punchCount(); ++i) { int code = read_card.punchAt(i).code(); if(!correct_codes.contains(code)) { xc.insert(xc.count(), QVariantList() << (i+1) << code); } } tt.setValue("extraCodes", xc); } tt.setValue("currentStandings", current_standings); tt.setValue("competitorsFinished", competitors_finished); tt.setValue("timeMs", checked_card.timeMs()); qfDebug() << "card:\n" << tt.toString(); ret["card"] = tt.toVariant(); } return ret; }
qf::core::utils::TreeTable RelaysPlugin::nlegsResultsTable(int class_id, int leg_count, int places, bool exclude_not_finish) { int max_leg = 0; qfs::Query q; { qfs::QueryBuilder qb; qb.select("relayLegCount") .from("classdefs") .where("classId=" QF_IARG(class_id)); q.execThrow(qb.toString()); if(q.next()) max_leg = q.value(0).toInt(); } if(max_leg == 0) { qfError() << "Leg count not defined for class id:" << class_id; return qf::core::utils::TreeTable(); } if(leg_count > max_leg) leg_count = max_leg; QList<Relay> relays; //QStringList relay_ids; { qfs::QueryBuilder qb; qb.select2("relays", "id, club, name, number") .select2("clubs", "name") .from("relays") .join("relays.club", "clubs.abbr") .where("relays.classId=" QF_IARG(class_id)); q.execThrow(qb.toString()); while(q.next()) { Relay r; r.relayId = q.value("relays.id").toInt(); r.name = (q.value("relays.number").toString() + ' ' + q.value("relays.club").toString() + ' ' + q.value("relays.name").toString() + ' ' + q.value("clubs.name").toString()).trimmed(); for (int i = 0; i < leg_count; ++i) r.legs << Leg(); relays << r; //relay_ids << QString::number(r.relayId); } } { qfs::QueryBuilder qb; qb.select2("competitors", "id, registration") .select2("runs", "id, relayId, leg") .select("COALESCE(competitors.lastName, '') || ' ' || COALESCE(competitors.firstName, '') AS competitorName") .from("runs") .join("runs.competitorId", "competitors.id") .joinRestricted("runs.relayId", "relays.id", "relays.classId=" QF_IARG(class_id), qfs::QueryBuilder::INNER_JOIN) //.where("runs.relayId IN (" + relay_ids.join(',') + ")") .where("runs.leg>0 AND runs.leg<=" + QString::number(leg_count)) .orderBy("runs.relayId, runs.leg"); q.execThrow(qb.toString()); while(q.next()) { int relay_id = q.value("runs.relayId").toInt(); for (int i = 0; i < relays.count(); ++i) { if(relays[i].relayId == relay_id) { Relay &relay = relays[i]; int legno = q.value("runs.leg").toInt(); Leg &leg = relay.legs[legno - 1]; leg.name = q.value("competitorName").toString(); leg.runId = q.value("runs.id").toInt(); leg.reg = q.value("competitors.registration").toString(); break; } } } } for (int legno = 1; legno <= leg_count; ++legno) { qfs::QueryBuilder qb; qb.select2("runs", "id, relayId, timeMs, disqualified") .from("runs") .joinRestricted("runs.relayId", "relays.id", "relays.classId=" QF_IARG(class_id) " AND runs.leg=" QF_IARG(legno) " AND runs.isRunning" " AND NOT runs.notCompeting" " AND runs.finishTimeMs>0" , qfs::QueryBuilder::INNER_JOIN) .orderBy("runs.disqualified, runs.timeMs"); q.execThrow(qb.toString()); int run_pos = 1; while(q.next()) { int relay_id = q.value("runs.relayId").toInt(); for (int i = 0; i < relays.count(); ++i) { if(relays[i].relayId == relay_id) { int run_id = q.value("runs.id").toInt(); Relay &relay = relays[i]; Leg &leg = relay.legs[legno - 1]; if(leg.runId != run_id) { qfError() << "internal error, leg:" << legno << "runId check:" << leg.runId << "should equal" << run_id; } else { leg.notfinish = false; leg.disq = q.value("runs.disqualified").toBool(); leg.time = q.value("timeMs").toInt(); leg.pos = leg.disq? 0: run_pos; run_pos++; } break; } } } } /// compute overal legs positions for (int legno = 1; legno <= leg_count; ++legno) { QList<QPair<int, int>> relay_stime; for (int i = 0; i < relays.count(); ++i) { Relay &relay = relays[i]; Leg &leg = relay.legs[legno - 1]; if(!leg.notfinish && !leg.disq) { if(legno == 1) leg.stime = leg.time; else if(relay.legs[legno-2].stime > 0) leg.stime = leg.time + relay.legs[legno-2].stime; } if(leg.stime > 0) relay_stime << QPair<int, int>(relay.relayId, leg.stime); } std::sort(relay_stime.begin(), relay_stime.end(), [](const QPair<int, int> &a, const QPair<int, int> &b) {return a.second < b.second;}); int pos = 0; for(const QPair<int, int> &p : relay_stime) { int relay_id = p.first; for (int i = 0; i < relays.count(); ++i) { if(relays[i].relayId == relay_id) { Relay &relay = relays[i]; Leg &leg = relay.legs[legno - 1]; leg.spos = ++pos; break; } } } } if(exclude_not_finish) { /* relays.erase(std::remove_if(relays.begin(), relays.end(), [](const Relay &r){return r.time(leg_count) == TIME_NOT_FINISH;}), relays.end()); */ QMutableListIterator<Relay> i(relays); while (i.hasNext()) { const Relay &r = i.next(); if(r.time(leg_count) == qog::TimeMs::NOT_FINISH_TIME_MSEC) i.remove(); } } /// sort relays std::sort(relays.begin(), relays.end(), [leg_count](const Relay &a, const Relay &b) { return a.time(leg_count) < b.time(leg_count); }); int time0 = 0; qf::core::utils::TreeTable tt; tt.appendColumn("pos", QVariant::Int); tt.appendColumn("name", QVariant::String); tt.appendColumn("time", QVariant::Int); tt.appendColumn("loss", QVariant::Int); for (int i = 0; i < qMin(relays.count(), places); ++i) { qf::core::utils::TreeTableRow rr = tt.appendRow(); Relay &relay = relays[i]; int time = relay.time(leg_count); if(i == 0) time0 = time; int prev_time = (i > 0)? relays[i-1].time(leg_count): 0; rr.setValue("pos", (time <= qog::TimeMs::MAX_REAL_TIME_MSEC && time > prev_time)? i+1: 0); rr.setValue("name", relay.name); rr.setValue("time", time); rr.setValue("loss", (time <= qog::TimeMs::MAX_REAL_TIME_MSEC)?time - time0: 0); qf::core::utils::TreeTable tt2; tt.appendColumn("name", QVariant::String); tt.appendColumn("reg", QVariant::String); tt.appendColumn("time", QVariant::Int); tt.appendColumn("pos", QVariant::Int); tt.appendColumn("stime", QVariant::Int); tt.appendColumn("spos", QVariant::Int); //tt.appendColumn("disq", QVariant::Bool); for (int j = 0; j < relay.legs.count(); ++j) { Leg &leg = relay.legs[j]; qf::core::utils::TreeTableRow rr2 = tt2.appendRow(); rr2.setValue("competitorName", leg.name); rr2.setValue("registration", leg.reg); rr2.setValue("time", leg.disq? qog::TimeMs::DISQ_TIME_MSEC : (leg.time == 0)? qog::TimeMs::NOT_FINISH_TIME_MSEC : leg.time); rr2.setValue("pos", leg.pos); rr2.setValue("stime", leg.stime); rr2.setValue("spos", leg.spos); //rr2.setValue("disq", leg.disq); } rr.appendTable(tt2); } //qfInfo() << tt.toString(); return tt; }