MeOS version 3.7SD.1205 RC1

This commit is contained in:
Erik Melin 2020-08-01 22:55:57 +02:00
parent b79eec9686
commit 5e242c81c0
47 changed files with 3323 additions and 1058 deletions

View File

@ -368,6 +368,7 @@ protected:
int getDISize() const {return 0;}
void changedObject() {}
public:
void merge(const oBase &input) final {}
int getIndex() const {return index;}
void init(RunnerDB *db_, int index_) {db=db_, index=index_; Id = index;}

View File

@ -1029,6 +1029,12 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
gdi.popX();
gdi.fillRight();
gdi.addButton("AutomaticDraw", "Automatisk lottning", ClassesCB);
/*if (oe->getStartGroups(true).size() > 0) {
gdi.addButton("DrawStartGroups", "Lotta med startgrupper", ClassesCB);
gdi.popX();
gdi.dropLine(3);
}*/
gdi.addButton("DrawAll", "Manuell lottning", ClassesCB).setExtra(1);
gdi.addButton("Simultaneous", "Gemensam start", ClassesCB);
@ -1268,7 +1274,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
EditChanged=false;
}
else {
firstStart = gdi.getText("FirstStart");
firstStart = gdi.getText("FirstStart", true);
minInterval = gdi.getText("MinInterval");
vacances = gdi.getText("Vacances");
//pairwise = gdi.isChecked("Pairwise");
@ -1299,9 +1305,19 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
by = gdi.getHeight();
gdi.enableEditControls(true);
}
bool hasGroups = oe->getStartGroups(true).size() > 0;
if (!hasGroups) {
loadReadyToDistribute(gdi, bx, by);
}
else {
gdi.fillRight();
gdi.addButton("DrawGroupsManual", "Lotta", ClassesCB);
gdi.addButton("EraseStartAll", "Radera starttider...", ClassesCB);
gdi.refresh();
}
}
else if (bi.id == "HelpDraw") {
gdioutput *gdi_new = getExtraWindow("help", true);
@ -1363,10 +1379,62 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
}
gdi.enableEditControls(false);
oe->optimizeStartOrder(gdi, drawInfo, cInfo);
vector<pair<int, wstring>> outLines;
oe->optimizeStartOrder(outLines, drawInfo, cInfo);
for (auto &ol : outLines)
gdi.addString("", ol.first, ol.second);
showClassSettings(gdi);
}
else if (bi.id == "DrawGroupsManual") {
set<int> classes;
gdi.getSelection("Classes", classes);
if (classes.empty())
throw meosException("Ingen klass vald.");
readDrawInfo(gdi, drawInfo);
if (drawInfo.baseInterval <= 0 || drawInfo.baseInterval == NOTIME)
throw meosException("Ogiltigt basintervall.");
if (drawInfo.minClassInterval <= 0 || drawInfo.minClassInterval == NOTIME)
throw meosException("Ogiltigt minimalt intervall.");
if (drawInfo.minClassInterval < drawInfo.baseInterval) {
throw meosException("Startintervallet får inte vara kortare än basintervallet.");
}
if (drawInfo.minClassInterval % drawInfo.baseInterval != 0) {
throw meosException("Ett startintervall måste vara en multipel av basintervallet.");
}
vector<ClassDrawSpecification> spec;
for (int id : classes) {
//int classID, int leg, int firstStart, int interval, int vacances, oEvent::VacantPosition vp)
int nVac = int(drawInfo.vacancyFactor * oe->getClass(id)->getNumRunners(true, true, true) + 0.5);
nVac = min(max(drawInfo.minVacancy, nVac), drawInfo.maxVacancy);
spec.emplace_back(id, 0, 0, 120, nVac, oEvent::VacantPosition::Mixed);
}
oEvent::DrawMethod method = (oEvent::DrawMethod)gdi.getSelectedItem("Method").first;
bool moveRunners = gdi.isChecked("MoveRunners");
oe->drawListStartGroups(spec, method, 1, oEvent::DrawType::DrawAll, moveRunners, &drawInfo);
oe->addAutoBib();
clearPage(gdi, false);
gdi.addButton("Cancel", "Återgå", ClassesCB);
oListParam par;
oListInfo info;
par.listCode = EStdStartList;
par.selection = classes;
oe->generateListInfo(par, info);
oe->generateList(gdi, false, info, true);
gdi.refresh();
}
else if (bi.id == "LoadSettings") {
set<int> classes;
gdi.getSelection("Classes", classes);
@ -1434,7 +1502,11 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
else if (bi.id == "DrawAdjust") {
readClassSettings(gdi);
gdi.restore("ReadyToDistribute");
oe->optimizeStartOrder(gdi, drawInfo, cInfo);
vector<pair<int, wstring>> outLines;
oe->optimizeStartOrder(outLines, drawInfo, cInfo);
for (auto &ol : outLines)
gdi.addString("", ol.first, ol.second);
showClassSettings(gdi);
}
else if (bi.id == "DrawAllAdjust") {
@ -1447,10 +1519,12 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
oe->addAutoBib();
loadPage(gdi);
}
else if (bi.id=="DoDraw" || bi.id=="DoDrawAfter" || bi.id=="DoDrawBefore"){
else if (bi.id=="DoDraw" || bi.id=="DoDrawAfter" || bi.id=="DoDrawBefore" || bi.id == "DoDrawGroups"){
if (!checkClassSelected(gdi))
return false;
bool withGroups = bi.id == "DoDrawGroups";
DWORD cid=ClassId;
pClass pc = oe->getClass(cid);
oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first);
@ -1517,7 +1591,9 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::MeOS) {
vector<ClassDrawSpecification> spec;
spec.emplace_back(cid, leg, t, interval, vacanses, vp);
if (withGroups)
oe->drawListStartGroups(spec, method, pairSize, dtype);
else
oe->drawList(spec, method, pairSize, dtype);
}
else if (method == oEvent::DrawMethod::Clumped)
@ -1704,6 +1780,12 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
selectClass(gdi, pc->getId());
}
}
else if (bi.id == "StartGroups") {
loadStartGroupSettings(gdi, true);
}
else if (bi.id == "DrawStartGroups") {
drawStartGroups(gdi);
}
else if (bi.id=="Bibs") {
save(gdi, true);
if (!checkClassSelected(gdi))
@ -3395,6 +3477,9 @@ bool TabClass::loadPage(gdioutput &gdi)
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::MultipleRaces))
func.push_back(ButtonData("QualificationFinal", "Kval/final-schema", false));
if (showAdvanced)
func.push_back(ButtonData("StartGroups", "Startgrupper", true));
RECT funRect;
funRect.right = gdi.getCX() - 7;
funRect.top = gdi.getCY() - 2;
@ -3411,14 +3496,20 @@ bool TabClass::loadPage(gdioutput &gdi)
int xlimit = gdi.getWidth() - button_w/2;
for (size_t k = 0; k < func.size(); k++) {
TextInfo ti;
ti.xp = 0;
ti.yp = 0;
ti.text = lang.tl(func[k].label);
gdi.calcStringSize(ti);
if (gdi.getCX() + ti.realWidth > xlimit) {
gdi.popX();
gdi.dropLine(2.5);
}
ButtonInfo &bi = gdi.addButton(func[k].id, func[k].label, ClassesCB);
if (!func[k].global)
bi.isEdit(true);
funRect.left = max<int>(funRect.left, gdi.getCX() + 7);
if ( gdi.getCX() > xlimit && k+1 < func.size()) {
gdi.popX();
gdi.dropLine(2.5);
}
}
gdi.dropLine(2.5);
@ -3701,8 +3792,11 @@ void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClas
gdi.fillRight();
if (method != oEvent::DrawMethod::Simultaneous)
if (method != oEvent::DrawMethod::Simultaneous) {
gdi.addButton("DoDraw", "Lotta klassen", ClassesCB, "Lotta om hela klassen");
if (oe->getStartGroups(true).size() > 0)
gdi.addButton("DoDrawGroups", "Lotta med startgrupper", ClassesCB, "Lotta om hela klassen");
}
else
gdi.addButton("DoDraw", "Tilldela", ClassesCB, "Tilldela starttider");
@ -4501,7 +4595,7 @@ void TabClass::readDrawInfo(gdioutput &gdi, DrawInfo &drawInfoOut) {
int minVacancy = gdi.getTextNo("VacancesMin");
setDefaultVacant(gdi.getText("Vacances"));
double vacancyFactor = 0.01*_wtof(gdi.getText("Vacances").c_str());
double extraFactor = 0.01*_wtof(gdi.getText("Extra").c_str());
double extraFactor = 0.01*_wtof(gdi.getText("Extra", true).c_str());
drawInfoOut.changedVacancyInfo = drawInfoOut.maxVacancy != maxVacancy ||
@ -4522,9 +4616,9 @@ void TabClass::readDrawInfo(gdioutput &gdi, DrawInfo &drawInfoOut) {
drawInfoOut.coursesTogether = gdi.isChecked("CoursesTogether");
drawInfoOut.minClassInterval = convertAbsoluteTimeMS(gdi.getText("MinInterval"));
drawInfoOut.maxClassInterval = convertAbsoluteTimeMS(gdi.getText("MaxInterval"));
drawInfoOut.maxClassInterval = convertAbsoluteTimeMS(gdi.getText("MaxInterval", true));
drawInfoOut.nFields = gdi.getTextNo("nFields");
drawInfoOut.firstStart = oe->getRelativeTime(gdi.getText("FirstStart"));
drawInfoOut.firstStart = oe->getRelativeTime(gdi.getText("FirstStart", true));
}
void TabClass::writeDrawInfo(gdioutput &gdi, const DrawInfo &drawInfoIn) {
@ -4746,14 +4840,25 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
const set<int> &clsId) {
showClassSelection(gdi, bx, by, DrawClassesCB);
bool hasGroups = oe->getStartGroups(true).size() > 0;
gdi.setSelection("Classes", clsId);
int xb = 0, yb = 0;
if (hasGroups) {
gdi.addString("", fontMediumPlus, "Lotta med startgrupper");
gdi.dropLine(1.5);
xb = gdi.getCX() - gdi.scaleLength(5);
yb = gdi.getCY() - gdi.scaleLength(5);
}
gdi.addString("", 1, "Grundinställningar");
gdi.pushX();
gdi.fillRight();
if (!hasGroups)
gdi.addInput("FirstStart", firstStart, 10, 0, L"Första start:");
gdi.addInput("nFields", L"10", 10, 0, L"Max parallellt startande:");
gdi.popX();
@ -4775,6 +4880,8 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
gdi.dropLine(4);
gdi.fillDown();
gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) != 0);
if (!hasGroups)
gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false);
gdi.dropLine(0.5);
@ -4783,6 +4890,8 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
gdi.fillRight();
gdi.addInput("BaseInterval", L"1:00", 10, 0, L"Basintervall (min):");
gdi.addInput("MinInterval", minInterval, 10, 0, L"Minsta intervall i klass:");
if (!hasGroups)
gdi.addInput("MaxInterval", minInterval, 10, 0, L"Största intervall i klass:");
gdi.popX();
@ -4797,8 +4906,25 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
gdi.addInput("VacancesMin", zeroVac ? L"0" : L"1", 6, 0, L"Min. vakanser (per klass):");
gdi.addInput("VacancesMax", zeroVac ? L"0" : L"10", 6, 0, L"Max. vakanser (per klass):");
if (!hasGroups)
gdi.addInput("Extra", L"0%", 6, 0, L"Förväntad andel efteranmälda:");
if (hasGroups) {
gdi.fillDown();
gdi.popX();
gdi.dropLine(4);
gdi.addString("", 1, "Lottning");
gdi.dropLine(0.4);
createDrawMethod(gdi);
gdi.addCheckbox("MoveRunners", "Flytta deltagare från överfulla grupper", 0, true);
int xr = gdi.getWidth();
int bt = gdi.getCY();
RECT rc = { xb, yb, xr, bt };
gdi.addRectangle(rc, colorLightCyan);
}
gdi.dropLine(4);
gdi.fillDown();
gdi.popX();
@ -4903,3 +5029,136 @@ void TabClass::fillResultModules(gdioutput &gdi, pClass pc) {
gdi.selectItemByData("Module", current);
hideEditResultModule(gdi, current);
}
class StartGroupHandler : public GuiHandler {
oEvent &oe;
TabClass *tc;
bool lock = false;
public:
StartGroupHandler(TabClass *tc, oEvent *oe) : oe(*oe), tc(tc) {}
void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) override {
if (type == GuiEventType::GUI_INPUT) {
int id = info.getExtraInt();
InputInfo &ii = dynamic_cast<InputInfo &>(info);
if (ii.id[0] == 'g') {
int idNew = _wtoi(ii.text.c_str());
if (idNew != id) {
if (oe.getStartGroup(idNew).first == -1) {
auto d = oe.getStartGroup(id);
oe.setStartGroup(idNew, d.first, d.second);
oe.setStartGroup(id, -1, -1);
string rowIx = ii.id.substr(5);
gdi.getBaseInfo("group" + rowIx).setExtra(idNew);
gdi.getBaseInfo("first" + rowIx).setExtra(idNew);
gdi.getBaseInfo("last" + rowIx).setExtra(idNew);
gdi.getBaseInfo("D" + rowIx).setExtra(idNew);
ii.setBgColor(colorDefault);
}
else {
ii.setBgColor(colorLightRed);
}
}
}
else if (ii.id[0] == 'f') {
auto d = oe.getStartGroup(id);
d.first = oe.getRelativeTime(ii.text);
oe.setStartGroup(id, d.first, d.second);
}
else if (ii.id[0] == 'l') {
auto d = oe.getStartGroup(id);
d.second = oe.getRelativeTime(ii.text);
oe.setStartGroup(id, d.first, d.second);
}
}
else if (type == GuiEventType::GUI_BUTTON) {
if (info.id == "AddGroup") {
int id = 1;
int firstStart = 3600;
int length = 3600;
for (auto &g : oe.getStartGroups(false)) {
id = max(id, g.first+1);
firstStart = max(firstStart, g.second.second);
if (g.second.first < g.second.second)
length = min(length, g.second.second - g.second.first);
}
oe.setStartGroup(id, firstStart, firstStart + length);
tc->loadStartGroupSettings(gdi, false);
}
else if (info.id[0] == 'D') {
int id = info.getExtraInt();
oe.setStartGroup(id, -1, -1);
tc->loadStartGroupSettings(gdi, false);
}
else if (info.id == "Save") {
oe.synchronize();
oe.updateStartGroups();
oe.synchronize(true);
tc->loadPage(gdi);
}
else if (info.id == "Cancel") {
tc->loadPage(gdi);
}
}
}
};
void TabClass::loadStartGroupSettings(gdioutput &gdi, bool reload) {
clearPage(gdi, false);
auto &sg = oe->getStartGroups(reload);
if (!startGroupHandler)
startGroupHandler = make_shared<StartGroupHandler>(this, oe);
GuiHandler *sgh = startGroupHandler.get();
gdi.addString("", boldLarge, "Startgrupper");
int row = 0;
gdi.dropLine(0.5);
gdi.addString("", 10, L"help:startgroup");
int idPos = gdi.getCX();
int firstPos = idPos + gdi.scaleLength(120);
int lastPos = firstPos + gdi.scaleLength(120);
int bPos = lastPos + gdi.scaleLength(120);
bool first = true;
for (auto &g : sg) {
if (first) {
int y = gdi.getCY();
gdi.addString("", y, idPos, 0, "Id");
gdi.addString("", y, firstPos, 0, "Start");
gdi.addString("", y, lastPos, 0, "Slut");
first = false;
}
int cy = gdi.getCY();
string srow = itos(row++);
gdi.addInput(idPos, cy, "group" + srow, itow(g.first), 8).setHandler(sgh).setExtra(g.first);
gdi.addInput(firstPos, cy, "first" + srow, oe->getAbsTime(g.second.first), 10).setHandler(sgh).setExtra(g.first);
gdi.addInput(lastPos, cy, "last" + srow, oe->getAbsTime(g.second.second), 8).setHandler(sgh).setExtra(g.first);
gdi.addButton(bPos, cy, "D" + srow, L"Ta bort").setHandler(sgh).setExtra(g.first);
}
if (sg.size() == 1)
gdi.addString("", 10, "Tips: ställ in rätt tid innan du lägger till fler grupper.");
gdi.dropLine();
gdi.fillRight();
gdi.addButton("AddGroup", "Ny startgrupp").setHandler(sgh);
gdi.addButton("Save", "Spara").setHandler(sgh);
gdi.addButton("Cancel", "Avbryt").setHandler(sgh);
gdi.refresh();
}
void TabClass::drawStartGroups(gdioutput &gdi) {
clearPage(gdi, false);
if (!startGroupHandler)
startGroupHandler = make_shared<StartGroupHandler>(this, oe);
gdi.addString("", boldLarge, "Lotta med startgrupper");
gdi.addButton("Cancel", "Stäng", ClassesCB);
gdi.refresh();
}

View File

@ -163,7 +163,12 @@ class TabClass :
vector<string> currentResultModuleTags;
void fillResultModules(gdioutput &gdi, pClass pc);
shared_ptr<GuiHandler> startGroupHandler;
public:
void loadStartGroupSettings(gdioutput &gdi, bool reload);
void drawStartGroups(gdioutput &gdi);
void clearCompetitionData();

View File

@ -163,7 +163,7 @@ bool TabCompetition::importFile(HWND hWnd, gdioutput &gdi)
return false;
gdi.setWaitCursor(true);
if (oe->open(fileName, true)) {
if (oe->open(fileName, true, false)) {
gdi.setWindowTitle(oe->getTitleName());
resetSaveTimer();
return true;
@ -562,7 +562,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
gdi.refresh();
}
else if (bi.id=="Test") {
checkRentCards(gdi);
//mergeCompetition(gdi);
}
else if (bi.id=="Report") {
gdi.clearPage(true);
@ -892,7 +892,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
oEvent nextStage(gdi);
if (!file.empty())
success = nextStage.open(file.c_str(), false);
success = nextStage.open(file.c_str(), false, false);
if (success)
success = nextStage.getNameId(0) == oe->getDCI().getString("PostEvent");
@ -1871,8 +1871,11 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
save(gdi, true);
exportFileAs(hWndMain, gdi);
}
else if (bi.id == "Merge") {
mergeCompetition(gdi);
}
else if (bi.id=="Duplicate") {
oe->duplicate();
oe->duplicate(L"");
gdi.alert("Skapade en lokal kopia av tävlingen.");
}
else if (bi.id=="Import") {
@ -2310,7 +2313,7 @@ int TabCompetition::restoreCB(gdioutput &gdi, int type, void *data) {
if (ti.id == "") {
wstring fi(bi.FullPath);
if (!oe->open(fi, false)) {
if (!oe->open(fi, false, false)) {
gdi.alert("Kunde inte öppna tävlingen.");
}
else {
@ -2404,6 +2407,7 @@ void TabCompetition::loadAboutPage(gdioutput &gdi) const
"\n\nRussian Translation by Paul A. Kazakov and Albert Salihov"
"\n\nOriginal French Translation by Jerome Monclard"
"\n\nAdaption to French conditions and extended translation by Pierre Gaufillet"
"\n\nMore French translations and documentation by Titouan Savart"
"\n\nCzech Translation by Marek Kustka"
"\n\nSpanish Translation by Manuel Pedre"
"\n\nHelp with English documentation: Torbjörn Wikström");
@ -2658,6 +2662,9 @@ bool TabCompetition::loadPage(gdioutput &gdi)
}
gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "SaveAs", "Säkerhetskopiera",
CompetitionCB, "", false, false);
gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "Merge", "Slå ihop tävlingar",
CompetitionCB, "", false, false);
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Network)) {
gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "ConnectMySQL", "Databasanslutning",
CompetitionCB, "", false, false);
@ -3448,6 +3455,18 @@ void TabCompetition::entryForm(gdioutput &gdi, bool isGuide) {
gdi.addInput("FileName", L"", 48, 0, L"Anmälningar (IOF (xml) eller OE-CSV)");
gdi.dropLine();
gdi.addButton("BrowseEntries", "Bläddra...", CompetitionCB).setExtra(L"FileName");
gdi.popX();
gdi.dropLine(2.5);
gdi.addInput("FileNameService", L"", 48, 0, L"Tjänster (IOF XML)");
gdi.dropLine();
gdi.addButton("BrowseEntries", "Bläddra...", CompetitionCB).setExtra(L"FileNameService");
gdi.popX();
gdi.dropLine(2.5);
gdi.addInput("FileNameServiceReq", L"", 48, 0, L"Tjänstebeställningar (IOF XML)");
gdi.dropLine();
gdi.addButton("BrowseEntries", "Bläddra...", CompetitionCB).setExtra(L"FileNameServiceReq");
gdi.popX();
gdi.dropLine(3.2);
@ -3467,23 +3486,22 @@ void TabCompetition::entryForm(gdioutput &gdi, bool isGuide) {
}
FlowOperation TabCompetition::saveEntries(gdioutput &gdi, bool removeRemoved, bool isGuide) {
wstring filename[5];
filename[0] = gdi.getText("FileNameCmp");
filename[1] = gdi.getText("FileNameCls");
filename[2] = gdi.getText("FileNameClb");
filename[3] = gdi.getText("FileName");
filename[4] = gdi.getText("FileNameRank");
//csvparser csv;
vector<string> fields = { "FileNameCmp", "FileNameCls", "FileNameClb", "FileName",
"FileNameRank", "FileNameService", "FileNameServiceReq"};
for (int i = 0; i<5; i++) {
vector<wstring> filename;
for (string &fn : fields)
filename.push_back(gdi.getText(fn));
for (size_t i = 0; i<filename.size(); i++) {
if (filename[i].empty())
continue;
gdi.addString("", 0, L"Behandlar: X#" + filename[i]);
csvparser::CSV type = csvparser::iscsv(filename[i]);
if (i == 4 && (type == csvparser::CSV::OE || type == csvparser::CSV::Unknown)) {
if ((fields[i] == "FileNameRank") && (type == csvparser::CSV::OE || type == csvparser::CSV::Unknown)) {
// Ranking
const wchar_t *File = filename[i].c_str();
@ -4149,34 +4167,176 @@ void TabCompetition::checkReadyForResultExport(gdioutput &gdi, const set<int> &c
}
}
void TabCompetition::checkRentCards(gdioutput &gdi) {
gdi.clearPage(false);
void TabCompetition::mergeCompetition(gdioutput &gdi) {
wstring fn = gdi.browseForOpen({ make_pair(L"csv", L"*.csv") }, L"csv");
if (!fn.empty()) {
csvparser csv;
list<vector<wstring>> data;
csv.parse(fn, data);
set<int> rentCards;
for (auto &c : data) {
if (c.size() > 0) {
int cn = _wtoi(c[0].c_str());
rentCards.insert(cn);
class MergeHandler : public GuiHandler {
TabCompetition *tc;
public:
MergeHandler(TabCompetition *tc) : tc(tc) {}
void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final {
if (type == GuiEventType::GUI_BUTTON) {
ButtonInfo bi = dynamic_cast<ButtonInfo &>(info);
if (bi.id == "Cancel") {
if (tc->mergeEvent)
tc->mergeEvent->clear();
tc->loadPage(gdi);
}
else if (bi.id == "Merge") {
if (!tc->mergeEvent)
return;
int numAdd, numRemove, numUpdate;
wstring anno = lang.tl(L"Kopia (X)#MRG " + wstring(getLocalTime()));
tc->oe->duplicate(anno);
tc->oe->merge(*tc->mergeEvent, numAdd, numRemove, numUpdate);
gdi.clearPage(true);
gdi.addString("", fontMediumPlus, "Sammanslagning klar.");
gdi.dropLine();
gdi.addString("", 1, "Sammanfattning, uppdateradet poster");
gdi.addString("", 0, "Tillagda: X#" + itos(numAdd));
gdi.addString("", 0, "Uppdaterade: X#" + itos(numUpdate));
gdi.addString("", 0, "Borttagna: X#" + itos(numRemove));
gdi.dropLine();
gdi.addString("", 0, L"Skapade lokal säkerhetskopia (X) innan sammanslagning#" + anno);
tc->mergeEvent->clear();
gdi.dropLine(3);
gdi.addButton("Cancel", "Återgå").setHandler(this);
gdi.refresh();
}
else if (bi.id == "Browse") {
wstring fn = gdi.browseForOpen({ make_pair(L"xml", L"*.xml") }, L"xml");
if (fn.empty())
return;
tc->mergeFile = fn;
gdi.setText("File", fn);
gdi.enableInput("Read");
}
else if (bi.id == "Read") {
tc->mergeEvent = make_shared<oEvent>(gdi);
if (!tc->mergeEvent->open(tc->mergeFile, true, true))
return;
gdi.restore("merge", false);
gdi.fillDown();
gdi.addStringUT(1, tc->mergeEvent->getName());
gdi.dropLine();
bool error = false;
if (tc->mergeEvent->getNameId(0) == tc->oe->getNameId(0)) {
if (tc->mergeEvent->getMergeTag() == tc->oe->getMergeTag()) {
gdi.addString("", 1, "Fel: En tävling kan inte slås ihop med sig själv.").setColor(colorRed);
error = true;
}
else {
gdi.addString("", 1, "Samma bastävling").setColor(colorDarkGreen);
wstring info = tc->oe->getMergeInfo(tc->mergeEvent->getMergeTag());
string mod = tc->mergeEvent->getLastModified();
if (info.empty()) {
gdi.addString("", 0, "Denna datakälla är aldrig tidigare infogad");
}
else {
string merged = gdi.narrow(info);
if (mod <= merged) {
gdi.addString("", 1, "Fel: Denna tävlingsversion är redan infogad.").setColor(colorRed);
error = true;
}
else {
TimeStamp ts;
ts.setStamp(merged);
gdi.addString("", 0, "Tidigare infogad version: X#" + ts.getStampStringN());
}
}
vector<pRunner> runners;
oe->getRunners(0, 0, runners);
int bcf = oe->getBaseCardFee();
for (pRunner r : runners) {
if (rentCards.count(r->getCardNo()) && r->getDCI().getInt("CardFee") == 0) {
gdi.addStringUT(0, r->getCompleteIdentification());
r->getDI().setInt("CardFee", bcf);
if (!error) {
TimeStamp ts;
ts.setStamp(mod);
gdi.addString("", 0, "Infoga version: X#" + ts.getStampStringN());
}
}
}
else {
gdi.addString("", 1, "Varning: Olika bastävlingar").setColor(colorRed);
gdi.addString("", 0, "Sammanslagning fungerar om samma uppsättning banor/kontroller används");
}
gdi.dropLine();
gdi.addButton("Cancel", "OK", CompetitionCB);
gdi.pushX();
gdi.fillRight();
if (!error) {
gdi.addString("", 1, "Klasser: ");
vector<pClass> cls;
tc->mergeEvent->getClasses(cls, false);
int xw, yw;
gdi.getTargetDimension(xw, yw);
int limit = (xw * 2) / 3;
for (pClass c : cls) {
gdi.addStringUT(0, c->getName());
if (gdi.getCX() >= limit) {
gdi.popX();
gdi.dropLine();
}
}
gdi.fillDown();
gdi.popX();
gdi.dropLine();
gdi.addString("", 1, "Antal deltagare: X#" + itos(tc->mergeEvent->getNumRunners()));
}
gdi.dropLine();
gdi.fillRight();
if (!error)
gdi.addButton("Merge", "Slå ihop").setHandler(tc->mergeHandler.get());
gdi.addButton("Cancel", "Avbryt").setHandler(tc->mergeHandler.get());
gdi.refresh();
}
}
}
};
mergeHandler = make_shared<MergeHandler>(this);
gdi.clearPage(false);
gdi.addString("", boldLarge, "Slå ihop tävlingar");
gdi.setRestorePoint("merge");
gdi.dropLine();
gdi.addString("", 10, "help:merge");
gdi.dropLine();
gdi.pushX();
gdi.fillRight();
gdi.addInput("File", mergeFile, 32, nullptr, L"Tävling:");
gdi.dropLine(0.8);
gdi.addButton("Browse", "Bläddra...").setHandler(mergeHandler.get());
gdi.dropLine(4);
gdi.popX();
gdi.fillRight();
gdi.addButton("Read", "Fortsätt").setHandler(mergeHandler.get());
gdi.addButton("Cancel", "Avbryt").setHandler(mergeHandler.get());
gdi.setInputStatus("Read", !mergeFile.empty());
gdi.refresh();
gdi.refresh();
}

View File

@ -134,9 +134,10 @@ class TabCompetition :
void listBackups(gdioutput &gdi);
void checkRentCards(gdioutput &gdi);
shared_ptr<GuiHandler> mergeHandler;
shared_ptr<oEvent> mergeEvent;
void mergeCompetition(gdioutput &gdi);
wstring mergeFile;
protected:
void clearCompetitionData();

View File

@ -263,7 +263,7 @@ void TabCourse::save(gdioutput &gdi, int canSwitchViewMode) {
pc->setName(name);
bool changedCourse = pc->importControls(gdi.narrow(gdi.getText("Controls")), true);
bool changedCourse = pc->importControls(gdi.narrow(gdi.getText("Controls")), true, true);
pc->setLength(gdi.getTextNo("Length"));
pc->getDI().setInt("Climb", gdi.getTextNo("Climb"));
pc->setNumberMaps(gdi.getTextNo("NumberMaps"));

View File

@ -100,6 +100,16 @@ const string &TimeStamp::getStamp() const
return stampCode;
}
const string &TimeStamp::getStamp(const string &sqlStampIn) const {
stampCode.resize(8 + 6 + 1);
int outIx = 0;
for (char c : sqlStampIn) {
if (c >= '0' && c <= '9' && outIx < 8 + 6)
stampCode[outIx++] = c;
}
return stampCode;
}
wstring TimeStamp::getStampString() const
{
__int64 ft64=(__int64(Time)+minYearConstant*365*24*3600)*10000000;
@ -113,6 +123,19 @@ wstring TimeStamp::getStampString() const
return bf;
}
string TimeStamp::getStampStringN() const
{
__int64 ft64 = (__int64(Time) + minYearConstant * 365 * 24 * 3600) * 10000000;
FILETIME &ft = *(FILETIME*)&ft64;
SYSTEMTIME st;
FileTimeToSystemTime(&ft, &st);
char bf[32];
sprintf_s(bf, "%d-%02d-%02d %02d:%02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
return bf;
}
void TimeStamp::setStamp(const string &s)
{
if (s.size()<14)

View File

@ -37,8 +37,10 @@ class TimeStamp {
public:
void setStamp(const string &s);
const string &getStamp() const;
const string &getStamp(const string &sqlStampIn) const;
wstring getStampString() const;
string getStampStringN() const;
int getAge() const;
unsigned int getModificationTime() const {return Time;}

View File

@ -588,7 +588,7 @@ bool csvparser::importOCAD_CSV(oEvent &event, const wstring &file, bool addClass
}
else {
// Reset control
pc->importControls("", false);
pc->importControls("", true, false);
pc->setLength(int(Length*1000));
}

View File

@ -2171,7 +2171,7 @@ Klasserna X och Y har samma externa id. Använd tabelläget för att ändra id =
Vill du koppla isär X från inläst bricka Y? = Would you like to disconnect X from the read out card Y?
RunnerRogainingPointGross = Rogaining points before reduction
Samlade poäng = Collected points
Tidsavdrag = Deduction
Tidsavdrag = Reduction
X p = X p
Bricka X används också av = Card X is also used by
reused card = reused card
@ -2494,3 +2494,29 @@ Lottat = Drawn
Sist = Last
Fakturadatum = Fakturadatum
Youth Cup X = Youth Cup X
Ny startgrupp = New starting group
Slut = End
Startgrupper = Starting groups
help:startgroup = Starting groups are used to control start list drawing. Competitors in a group will begin starting at the start time of the group.
Tips: ställ in rätt tid innan du lägger till fler grupper = Hint: Enter the correct times before adding more groups.
Familj = Family
Startgrupp = Starting group
Slå ihop tävlingar = Merge competitions
help:merge = It is possible to merge competitions and results, provided that they are based on the same set of courses and controls. Different groups of participants can complete the competition on different occasions and then the different competitions can be merged into one competition with a common list of results. Another possibility is to have different locations for different classes. If it is not possible to set up a common network, you can periodically exchange competition files to incorporate changes.\n\n1. Prepare the whole competition.\n2. Save a copy and import it to the outsourced computers (or local area networks).\n3. To transfer changes, export the contest from the outsourced computer (or computers) and merge it with this feature. Then export a copy from the main computer and make the corresponding import on the outsourced computers. \n4. The procedure can be repeated several times to continuously transfer the results.\n\nNote: If you make changes to (for example) the same participant in several places, some of the changes will be overwritten without warning. Make sure that each outsourced place only changes in its part of the competition.\n\nTip: Make a transfer as soon as the outsourced competitions have started before any change has been made, to test that everything has been set up correctly.
Denna datakälla är aldrig tidigare infogad = This data source has never been merged
Fel: Denna tävlingsversion är redan infogad = Error: This version has already been merged
Infoga version: X = Merge version: X
Samma bastävling = Same base competition
Sammanslagning fungerar om samma uppsättning banor/kontroller används = The merge will work if the same set of courses and controls are used
Varning: Olika bastävlingar = Warning: Different base competitions
Borttagna: X = Borttagna: X
Fel: En tävling kan inte slås ihop med sig själv = Error: Cannot merge a competition with itself
Sammanfattning, uppdateradet poster = Summary, updated data entities
Sammanslagning klar = Merge complete
Skapade lokal säkerhetskopia (X) innan sammanslagning = Created local backup (X) before merge
Tillagda: X = Added: X
Uppdaterade: X = Modified: X
Tjänstebeställningar (IOF XML) = Service Requests (IOF XML)
Tjänster (IOF XML) = Services (IOF XML)
Flytta deltagare från överfulla grupper = Move competitors from full groups
Lotta med startgrupper = Draw with Starting Groups

View File

@ -605,6 +605,9 @@ public:
const wstring &getText(const char *id, bool acceptMissing = false) const;
BaseInfo &getBaseInfo(const string &id) const {
return getBaseInfo(id.c_str());
}
BaseInfo &getBaseInfo(const char *id) const;
BaseInfo &getBaseInfo(const wchar_t *id) const {
return getBaseInfo(narrow(id).c_str());

View File

@ -1064,6 +1064,10 @@ void IOF30Interface::readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &
xo.getObjects("PersonServiceRequest", req);
entrySourceId = 0;
auto &sg = oe.getStartGroups(true);
bool importStartGroups = sg.size() > 0;
for (auto &rx : req) {
xmlobject xPers = rx.getObject("Person");
pRunner r = 0;
@ -1075,9 +1079,13 @@ void IOF30Interface::readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &
if (xreq) {
auto xServ = xreq.getObject("Service");
string type;
if (xServ && xServ.getObjectString("type", type)=="StartGroup") {
if (xServ && (xServ.getObjectString("type", type)=="StartGroup" || importStartGroups)) {
int id = xServ.getObjectInt("Id");
if (!importStartGroups)
r->getDI().setInt("Heat", id);
if (sg.count(id))
r->setStartGroup(id);
}
}
}
@ -1389,7 +1397,41 @@ void IOF30Interface::readEvent(gdioutput &gdi, const xmlobject &xo,
DI.setString("LateEntryFactor", lf);
}
}
oe.synchronize();
xmlList xService;
xo.getObjects("Service", xService);
services.clear();
for (auto &s : xService) {
int id = s.getObjectInt("Id");
if (id > 0) {
xmlList nameList;
s.getObjects("Name", nameList);
for (auto s : nameList) {
services.emplace_back(id, s.getw());
}
}
}
// This is a "hack" to interpret services of the from "XXXX 14:00 - 15:00 XXXX" as a start group.
for (auto &srv : services) {
vector<wstring> parts;
split(srv.name, L" -‒–—‐", parts);
vector<int> times;
for (auto &p : parts) {
for (auto &c : p) {
if (c == '.')
c = ':';
}
int t = oe.getRelativeTime(p);
if (t > 0)
times.push_back(t);
}
if (times.size() == 2 && times[0] < times[1])
oe.setStartGroup(srv.id, times[0], times[1]);
}
oe.updateStartGroups();
}
void IOF30Interface::setupClassConfig(int classId, const xmlobject &xTeam, map<int, vector<LegInfo> > &teamClassConfig) {
@ -3894,7 +3936,7 @@ pCourse IOF30Interface::readCourse(const xmlobject &xcrs) {
if (pc) {
pc->setName(name);
pc->setLength(len);
pc->importControls("", false);
pc->importControls("", true, false);
for (size_t i = 0; i<ctrlCode.size(); i++) {
pc->addControl(ctrlCode[i]->getId());
}

View File

@ -48,6 +48,14 @@ typedef oClub * pClub;
typedef oTeam * pTeam;
typedef oCourse *pCourse;
struct XMLService {
int id;
wstring name;
XMLService(int id, const wstring &name) : id(id), name(name) {}
XMLService() {}
};
class IOF30Interface {
oEvent &oe;
@ -67,6 +75,8 @@ class IOF30Interface {
set<wstring> matchedClasses;
list<XMLService> services;
struct LegInfo {
int maxRunners;
int minRunners;

View File

@ -116,7 +116,7 @@ HHOOK g_hhk; //- handle to the hook procedure.
HWND hMainTab=NULL;
list<TabObject> *tabList=0;
list<TabObject> *tabList = nullptr;
void scrollVertical(gdioutput *gdi, int yInc, HWND hWnd);
static int currentFocusIx = 0;
@ -481,7 +481,7 @@ int APIENTRY WinMain(HINSTANCE hInstance,
tabAutoRegister(0);
tabList->clear();
delete tabList;
tabList=0;
tabList = nullptr;
delete autoTask;
autoTask = 0;
@ -996,6 +996,9 @@ void createTabs(bool force, bool onlyMain, bool skipTeam, bool skipSpeaker,
skipRunners==skipRunnersP && skipControls==skipControlsP && skipCourses == skipCoursesP)
return;
if (!tabList)
return;
onlyMainP = onlyMain;
skipTeamP = skipTeam;
skipSpeakerP = skipSpeaker;

View File

@ -755,7 +755,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
if (syncUpdate(queryset, "oEvent", oe) == opStatusFail)
return opStatusFail;
}
writeTime = true;
try {
con.query().exec("DELETE FROM oCard");
{
list<oCard>::iterator it = oe->Cards.begin();
@ -831,6 +832,12 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
++it;
}
}
}
catch (...) {
writeTime = false;
throw;
}
writeTime = false;
return retValue;
}
@ -1566,7 +1573,7 @@ OpFailStatus MeosSQL::storeCourse(const Row &row, oCourse &c,
OpFailStatus success = opStatusOK;
c.Name = fromUTF((string)row["Name"]);
c.importControls(string(row["Controls"]), false);
c.importControls(string(row["Controls"]), false, false);
c.Length = row["Length"];
c.importLegLengths(string(row["Legs"]), false);
@ -1997,8 +2004,12 @@ OpFailStatus MeosSQL::syncRead(bool forceRead, oRunner *r)
}
string MeosSQL::andWhereOld(oBase *ob) {
if (ob->sqlUpdated.empty())
if (ob->sqlUpdated.empty()) {
if (ob->counter != 0)
return " AND Counter!=" + itos(ob->counter);
else
return "";
}
else
return " AND (Counter!=" + itos(ob->counter) + " OR Modified!='" + ob->sqlUpdated + "')";
}
@ -2804,6 +2815,9 @@ mysqlpp::ResNSel MeosSQL::updateCounter(const char *oTable, int id, mysqlpp::Que
query.reset();
query << "UPDATE " << oTable << " SET Counter=" << counter;
if (writeTime)
query << ", Modified=Modified";
if (updateqry != 0)
query << "," << updateqry->str();
@ -2870,6 +2884,10 @@ OpFailStatus MeosSQL::syncUpdate(mysqlpp::Query &updateqry,
if (setId)
query << ", Id=" << ob->Id;
if (writeTime) {
query << ", Modified='" << ob->getTimeStampN() << "'";
}
mysqlpp::ResNSel res=query.execute();
if (res) {
if (ob->Id > 0 && ob->Id!=(int)res.insert_id) {
@ -3184,6 +3202,7 @@ bool MeosSQL::syncListClass(oEvent *oe) {
if (!c) {
oClass oc(oe, Id);
oc.setImplicitlyCreated();
st = syncRead(true, &oc, false);
c = oe->addClass(oc);
if (c != 0) {

View File

@ -58,6 +58,7 @@ protected:
mysqlpp::Connection con;
string CmpDataBase;
void alert(const string &s);
bool writeTime = false;
vector<oBase *> missingObjects;

View File

@ -319,6 +319,7 @@
<ClCompile Include="oEventResult.cpp" />
<ClCompile Include="oEventSpeaker.cpp" />
<ClCompile Include="oEventSQL.cpp" />
<ClCompile Include="oevent_transfer.cpp" />
<ClCompile Include="oFreeImport.cpp" />
<ClCompile Include="oFreePunch.cpp" />
<ClCompile Include="oImportExport.cpp" />

View File

@ -30,22 +30,22 @@
//V35: abcdef
//V36: abcdef
int getMeosBuild() {
string revision("$Rev: 1014 $");
string revision("$Rev: 1031 $");
return 174 + atoi(revision.substr(5, string::npos).c_str());
}
//V37: a
//V37: ab
wstring getMeosDate() {
wstring date(L"$Date: 2020-05-01 16:22:46 +0200 (fr, 01 maj 2020) $");
wstring date(L"$Date: 2020-08-01 10:38:11 +0200 (lö, 01 aug 2020) $");
return date.substr(7,10);
}
wstring getBuildType() {
return L"RC2"; // No parantheses (...)
return L"RC1"; // No parantheses (...)
}
wstring getMajorVersion() {
return L"3.7";
return L"3.7SD";
}
wstring getMeosFullVersion() {
@ -146,6 +146,7 @@ void getSupporters(vector<wstring> &supp, vector<wstring> &developSupp)
supp.emplace_back(L"Thomas Engberg, VK Uvarna");
supp.emplace_back(L"LG Axmalm, Sävedalens AIK");
supp.emplace_back(L"Falköpings AIK OK");
developSupp.push_back(L"Karlskrona SOK");
reverse(supp.begin(), supp.end());
}

View File

@ -215,8 +215,33 @@ wstring oBase::getTimeStamp() const {
else return Modified.getStampString();
}
string oBase::getTimeStampN() const {
if (oe && oe->isClient() && !sqlUpdated.empty()) {
return sqlUpdated;
}
else return Modified.getStampStringN();
}
const string &oBase::getStamp() const {
if (oe && oe->isClient() && !sqlUpdated.empty()) {
return Modified.getStamp(sqlUpdated);
}
else
return Modified.getStamp();
}
void oBase::changeId(int newId) {
Id = newId;
oe->updateFreeId(this);
}
void oBase::addToEvent(oEvent *e, const oBase *src) {
oe = e;
addedToEvent = true;
oe->updateFreeId(this);
if (src)
Modified = src->Modified;
}
oDataInterface oBase::getDI(void) {

View File

@ -114,6 +114,9 @@ protected:
void setLocalObject() { localObject = true; }
// Merge into this entity
virtual void merge(const oBase &input) = 0;
public:
void update(SqlUpdated &info) const;
@ -154,13 +157,15 @@ public:
bool synchronize(bool writeOnly=false);
wstring getTimeStamp() const;
string getTimeStampN() const;
const string &getStamp() const;
bool existInDB() const { return !sqlUpdated.empty(); }
void setImplicitlyCreated() { implicitlyAdded = true; }
bool isImplicitlyCreated() const { return implicitlyAdded; }
bool isAddedToEvent() const { return addedToEvent; }
void addToEvent() { addedToEvent = true; }
void addToEvent(oEvent *e, const oBase *src);
oDataInterface getDI();

View File

@ -70,7 +70,7 @@ bool oCard::Write(xmlparser &xml)
xml.write("Punches", getPunchString());
xml.write("ReadId", readId);
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.endTag();
return true;
@ -101,6 +101,17 @@ void oCard::Set(const xmlobject &xo)
}
}
pair<int, int> oCard::getCardHash() const {
int a = cardNo;
int b = readId;
for (auto &p : punches) {
a = a * 31 + p.getTimeInt() * 997 + p.getTypeCode();
b = b * 41 + p.getTimeInt() * 97 + p.getTypeCode();
}
return make_pair(a, b);
}
void oCard::setCardNo(int c)
{
if (cardNo!=c)
@ -520,8 +531,9 @@ pCard oEvent::addCard(const oCard &oc)
return 0;
Cards.push_back(oc);
Cards.back().addToEvent();
Cards.back().tOwner = nullptr;
Cards.back().addToEvent(this, &oc);
qFreeCardId = max(oc.Id, qFreeCardId);
return &Cards.back();
}

View File

@ -65,8 +65,6 @@ protected:
/** Get internal data buffers for DI */
oDataContainer &getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const;
static bool comparePunchTime(oPunch *p1, oPunch *p2);
void changedObject();
mutable string punchString;
@ -132,6 +130,9 @@ public:
void importPunches(const string &s);
const string &getPunchString() const;
void merge(const oBase &input) final;
pair<int, int> getCardHash() const;
void Set(const xmlobject &xo);
bool Write(xmlparser &xml);

View File

@ -107,7 +107,7 @@ bool oClass::Write(xmlparser &xml)
xml.startTag("Class");
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", Name);
if (Course)
@ -719,13 +719,13 @@ pClass oEvent::addClass(const wstring &pname, int CourseId, int classId)
c.Course=getCourse(CourseId);
Classes.push_back(c);
Classes.back().addToEvent();
Classes.back().addToEvent(this, &c);
Classes.back().synchronize();
updateTabs();
return &Classes.back();
}
pClass oEvent::addClass(oClass &c)
pClass oEvent::addClass(const oClass &c)
{
if (c.Id==0)
return 0;
@ -736,9 +736,9 @@ pClass oEvent::addClass(oClass &c)
}
Classes.push_back(c);
Classes.back().addToEvent();
Classes.back().addToEvent(this, &c);
if (!Classes.back().existInDB() && !c.isImplicitlyCreated()) {
if (HasDBConnection && !Classes.back().existInDB() && !c.isImplicitlyCreated()) {
Classes.back().changed = true;
Classes.back().synchronize();
}

View File

@ -707,6 +707,8 @@ public:
void setResultModule(const string &tag);
const string &getResultModuleTag() const;
void merge(const oBase &input) final;
oClass(oEvent *poe);
oClass(oEvent *poe, int id);
virtual ~oClass();

View File

@ -72,7 +72,7 @@ bool oClub::write(xmlparser &xml)
xml.startTag("Club");
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", name);
for (size_t k=0;k<altNames.size(); k++)
xml.write("AltName", altNames[k]);
@ -251,7 +251,7 @@ pClub oEvent::addClub(const oClub &oc)
return clubIdIndex[oc.Id];
Clubs.push_back(oc);
Clubs.back().addToEvent();
Clubs.back().addToEvent(this, &oc);
if (!oc.existInDB())
Clubs.back().synchronize();

View File

@ -177,6 +177,8 @@ public:
void setName(const wstring &n);
void merge(const oBase &input) final;
void set(const xmlobject &xo);
bool write(xmlparser &xml);

View File

@ -101,7 +101,7 @@ bool oControl::write(xmlparser &xml)
xml.startTag("Control");
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", Name);
xml.write("Numbers", codeNumbers());
xml.write("Status", Status);

View File

@ -219,6 +219,8 @@ public:
int getFirstNumber() const;
void getNumbers(vector<int> &numbers) const;
void merge(const oBase &input) final;
void set(const xmlobject *xo);
void set(int pId, int pNumber, wstring pName);
bool write(xmlparser &xml);

View File

@ -84,7 +84,7 @@ bool oCourse::Write(xmlparser &xml)
xml.startTag("Course");
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", Name);
xml.write("Length", Length);
xml.write("Controls", getControls());
@ -114,7 +114,7 @@ void oCourse::Set(const xmlobject *xo)
Name=it->getw();
}
else if (it->is("Controls")){
importControls(it->getRaw(), false);
importControls(it->getRaw(), false, false);
}
else if (it->is("Legs")) {
importLegLengths(it->getRaw(), false);
@ -266,7 +266,7 @@ void oCourse::splitControls(const string &ctrls, vector<int> &nr) {
}
}
bool oCourse::importControls(const string &ctrls, bool updateLegLengths) {
bool oCourse::importControls(const string &ctrls, bool setChanged, bool updateLegLengths) {
int oldNC = nControls;
vector<int> oldC;
for (int k = 0; k<nControls; k++)
@ -325,6 +325,7 @@ bool oCourse::importControls(const string &ctrls, bool updateLegLengths) {
changed |= oldC[k] != Controls[k]->getId();
if (changed) {
if (setChanged)
updateChanged();
oe->punchIndex.clear();

View File

@ -209,7 +209,7 @@ public:
bool fillCourse(gdioutput &gdi, const string &name);
/** Returns true if changed. */
bool importControls(const string &cstring, bool updateLegLengths);
bool importControls(const string &cstring, bool setChanged, bool updateLegLengths);
void importLegLengths(const string &legs, bool setChanged);
/** Returns the length of the i:th leg (or 0 if unknown)*/
@ -239,6 +239,8 @@ public:
wstring getStart() const;
void setStart(const wstring &start, bool sync);
void merge(const oBase &input) final;
bool Write(xmlparser &xml);
oCourse(oEvent *poe, int id);

View File

@ -64,7 +64,7 @@
#include "Table.h"
//Version of database
int oEvent::dbVersion = 83;
int oEvent::dbVersion = 84;
class RelativeTimeFormatter : public oDataDefiner {
string name;
@ -343,6 +343,10 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi)
oEventData->addVariableString("PayModes", "Betalsätt");
oEventData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring");
oEventData->addVariableDate("InvoiceDate", "Fakturadatum");
oEventData->addVariableString("StartGroups", "Startgrupper");
oEventData->addVariableString("MergeTag", 12, "Tag");
oEventData->addVariableString("MergeInfo", "MergeInfo");
oEventData->addVariableString("ImportStamp", 14, "Stamp");
oEventData->initData(this, dataSize);
@ -407,6 +411,8 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi)
oRunnerData->addVariableInt("Reference", oDataContainer::oIS32, "Referens", make_shared<oRunner::RunnerReference>());
oRunnerData->addVariableInt("NoRestart", oDataContainer::oIS8U, "Ej omstart", make_shared<DataBoolean>("NoRestart"));
oRunnerData->addVariableString("InputResult", "Tidigare resultat", make_shared<DataHider>());
oRunnerData->addVariableInt("StartGroup", oDataContainer::oIS32, "Startgrupp");
oRunnerData->addVariableInt("Family", oDataContainer::oIS32, "Familj");
oControlData=new oDataContainer(oControl::dataSize);
oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering");
@ -673,7 +679,7 @@ pControl oEvent::addControl(const oControl &oc)
qFreeControlId = max (qFreeControlId, Id);
Controls.push_back(oc);
oe->Controls.back().addToEvent();
oe->Controls.back().addToEvent(this, &oc);
return &Controls.back();
}
@ -818,7 +824,7 @@ bool oEvent::writeCards(xmlparser &xml)
return true;
}
void oEvent::duplicate() {
void oEvent::duplicate(const wstring &annotationIn) {
wchar_t file[260];
wchar_t filename[64];
wchar_t nameid[64];
@ -853,18 +859,26 @@ void oEvent::duplicate() {
swprintf_s(filename, L"%d/%d %d:%02d",
st.wDay, st.wMonth, st.wHour, st.wMinute);
if (annotationIn.empty()) {
wstring anno = lang.tl(L"Kopia (X)#" + wstring(filename));
anno = oldAnno.empty() ? anno : oldAnno + L" " + anno;
setAnnotation(anno);
}
else {
setAnnotation(annotationIn);
}
wstring oldTag = getMergeTag();
try {
getMergeTag(true);
save();
}
catch(...) {
getDI().setString("MergeTag", oldTag);
// Restore in case of error
wcscpy_s(CurrentFile, oldFile);
currentNameId = oldId;
setAnnotation(oldAnno);
synchronize(true);
throw;
}
@ -872,6 +886,8 @@ void oEvent::duplicate() {
wcscpy_s(CurrentFile, oldFile);
currentNameId = oldId;
setAnnotation(oldAnno);
getDI().setString("MergeTag", oldTag);
synchronize(true);
}
bool oEvent::save()
@ -979,7 +995,7 @@ bool oEvent::save(const wstring &fileIn) {
xml.write("NameId", currentNameId);
xml.write("Annotation", Annotation);
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
oEventData->write(this, xml);
@ -1082,13 +1098,17 @@ bool oEvent::open(int id)
if (it->Server.empty()) {
if (id == it->Id) {
CompetitionInfo ci=*it; //Take copy
return open(ci.FullPath.c_str());
return open(ci.FullPath.c_str(), false, false);
}
}
else if (!it->Server.empty()) {
if (id == (10000000+it->Id)) {
CompetitionInfo ci=*it; //Take copy
return readSynchronize(ci);
if (readSynchronize(ci)) {
getMergeTag();
return true;
}
return false;
}
}
}
@ -1116,8 +1136,7 @@ static void toc(const string &str) {
}
bool oEvent::open(const wstring &file, bool Import)
{
bool oEvent::open(const wstring &file, bool Import, bool forMerge) {
if (!Import)
openFileLock->lockFile(file);
@ -1159,6 +1178,13 @@ bool oEvent::open(const wstring &file, bool Import)
bool res = open(xml);
if (res && !Import)
openFileLock->lockFile(file);
getMergeTag(Import && !forMerge);
if (Import && !forMerge) {
getDI().setString("ImportStamp", gdibase.widen(getLastModified()));
}
return res;
}
@ -1265,7 +1291,7 @@ bool oEvent::open(const xmlparser &xml) {
c.Set(&*it);
if (c.Id>0 && knownClass.count(c.Id) == 0) {
Classes.push_back(c);
Classes.back().addToEvent();
Classes.back().addToEvent(this, &c);
knownClass.insert(c.Id);
}
}
@ -1330,8 +1356,8 @@ bool oEvent::open(const xmlparser &xml) {
oTeam t(this, 0);
t.set(*it);
if (t.Id>0){
Teams.push_back(t);
teamById[t.Id] = &Teams.back();
//Teams.push_back(t);
addTeam(t, false);
Teams.back().apply(ChangeType::Quiet, nullptr);
}
}
@ -1635,10 +1661,10 @@ pCourse oEvent::addCourse(const oCourse &oc)
qFreeCourseId=max(qFreeCourseId, oc.getId());
pCourse pc = &Courses.back();
pc->addToEvent();
pc->addToEvent(this, &oc);
if (!pc->existInDB() && !pc->isImplicitlyCreated()) {
pc->updateChanged();
if (HasDBConnection && !pc->existInDB() && !pc->isImplicitlyCreated()) {
pc->changed = true;
pc->synchronize();
}
courseIdIndex[oc.Id] = pc;
@ -1786,7 +1812,7 @@ pRunner oEvent::addRunner(const oRunner &r, bool updateStartNo) {
Runners.push_back(r);
pRunner pr=&Runners.back();
pr->addToEvent();
pr->addToEvent(this, &r);
for (size_t i = 0; i < pr->multiRunner.size(); i++) {
if (pr->multiRunner[i]) {
@ -2112,7 +2138,7 @@ pCard oEvent::allocateCard(pRunner owner)
c.tOwner = owner;
Cards.push_back(c);
pCard newCard = &Cards.back();
newCard->addToEvent();
newCard->addToEvent(this, &c);
return newCard;
}
@ -3686,6 +3712,7 @@ void oEvent::newCompetition(const wstring &name)
openFileLock->unlockFile();
clear();
SYSTEMTIME st;
GetLocalTime(&st);
@ -3695,6 +3722,9 @@ void oEvent::newCompetition(const wstring &name)
Name = name;
oEventData->initData(this, sizeof(oData));
if (!name.empty() && name != L"-")
getMergeTag();
getDI().setString("Organizer", getPropertyString("Organizer", L""));
getDI().setString("Street", getPropertyString("Street", L""));
getDI().setString("Address", getPropertyString("Address", L""));
@ -5855,719 +5885,6 @@ void oEvent::setCurrency(int factor, const wstring &symbol, const wstring &separ
}
}
wstring oEvent::cloneCompetition(bool cloneRunners, bool cloneTimes,
bool cloneCourses, bool cloneResult, bool addToDate) {
if (cloneResult) {
cloneTimes = true;
cloneCourses = true;
}
if (cloneTimes)
cloneRunners = true;
oEvent ce(gdibase);
ce.newCompetition(Name);
ce.ZeroTime = ZeroTime;
ce.Date = Date;
if (addToDate) {
SYSTEMTIME st;
convertDateYMS(Date, st, false);
__int64 absD = SystemTimeToInt64Second(st);
absD += 3600*24;
ce.Date = convertSystemDate(Int64SecondToSystemTime(absD));
}
int len = Name.length();
if (len > 2 && isdigit(Name[len-1]) && !isdigit(Name[len-2])) {
++ce.Name[len-1]; // E1 -> E2
}
else
ce.Name += L" E2";
memcpy(ce.oData, oData, sizeof(oData));
for (oClubList::iterator it = Clubs.begin(); it != Clubs.end(); ++it) {
if (it->isRemoved())
continue;
pClub pc = ce.addClub(it->name, it->Id);
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
if (cloneCourses) {
for (oControlList::iterator it = Controls.begin(); it != Controls.end(); ++it) {
if (it->isRemoved())
continue;
pControl pc = ce.addControl(it->Id, 100, it->Name);
pc->setNumbers(it->codeNumbers());
pc->Status = it->Status;
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
for (oCourseList::iterator it = Courses.begin(); it != Courses.end(); ++it) {
if (it->isRemoved())
continue;
pCourse pc = ce.addCourse(it->Name, it->Length, it->Id);
pc->importControls(it->getControls(), false);
pc->legLengths = it->legLengths;
memcpy(pc->oData, it->oData, sizeof(pc->oData));
}
}
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
if (it->isRemoved())
continue;
pClass pc = ce.addClass(it->Name, 0, it->Id);
memcpy(pc->oData, it->oData, sizeof(pc->oData));
pc->setNumStages(it->getNumStages());
pc->legInfo = it->legInfo;
if (cloneCourses) {
pc->Course = ce.getCourse(it->getCourseId());
pc->MultiCourse = it->MultiCourse; // Points to wrong competition, but valid for now...
}
}
if (cloneRunners) {
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
if (it->isRemoved())
continue;
oRunner r(&ce, it->Id);
r.sName = it->sName;
r.getRealName(r.sName, r.tRealName);
r.StartNo = it->StartNo;
r.cardNumber = it->cardNumber;
r.Club = ce.getClub(it->getClubId());
r.Class = ce.getClass(it->getClassId(false));
if (cloneCourses)
r.Course = ce.getCourse(it->getCourseId());
pRunner pr = ce.addRunner(r, false);
pr->decodeMultiR(it->codeMultiR());
memcpy(pr->oData, it->oData, sizeof(pr->oData));
if (cloneTimes) {
pr->startTime = it->startTime;
}
if (cloneResult) {
if (it->Card) {
pr->Card = ce.addCard(*it->Card);
pr->Card->tOwner = pr;
}
pr->FinishTime = it->FinishTime;
pr->status = it->status;
}
}
for (oTeamList::iterator it = Teams.begin(); it != Teams.end(); ++it) {
if (it->skip())
continue;
oTeam t(&ce, it->Id);
t.sName = it->sName;
t.StartNo = it->StartNo;
t.Club = ce.getClub(it->getClubId());
t.Class = ce.getClass(it->getClassId(false));
if (cloneTimes)
t.startTime = it->startTime;
pTeam pt = ce.addTeam(t, false);
memcpy(pt->oData, it->oData, sizeof(pt->oData));
pt->Runners.resize(it->Runners.size());
for (size_t k = 0; k<it->Runners.size(); k++) {
int id = it->Runners[k] ? it->Runners[k]->Id : 0;
if (id)
pt->Runners[k] = ce.getRunner(id, 0);
}
t.apply(ChangeType::Update, nullptr);
}
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
it->createMultiRunner(false, false);
}
}
vector<pRunner> changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign;
set<int> dummy;
transferResult(ce, dummy, TransferAnyway, false, changedClass, changedClassNoResult, assignedVacant, newEntries, notTransfered, noAssign);
vector<pTeam> newEntriesT, notTransferedT, noAssignT;
transferResult(ce, TransferAnyway, newEntriesT, notTransferedT, noAssignT);
int eventNumberCurrent = getStageNumber();
if (eventNumberCurrent <= 0) {
eventNumberCurrent = 1;
setStageNumber(eventNumberCurrent);
}
ce.getDI().setString("PreEvent", currentNameId);
ce.setStageNumber(eventNumberCurrent + 1);
getDI().setString("PostEvent", ce.currentNameId);
int nf = getMeOSFeatures().getNumFeatures();
for (int k = 0; k < nf; k++) {
MeOSFeatures::Feature f = getMeOSFeatures().getFeature(k);
if (getMeOSFeatures().hasFeature(f))
ce.getMeOSFeatures().useFeature(f, true, ce);
}
// Transfer lists and list configurations.
if (listContainer) {
loadGeneralResults(false, false);
swap(ce.generalResults, generalResults);
try {
ce.listContainer = new MetaListContainer(&ce, *listContainer);
ce.save();
}
catch (...) {
swap(ce.generalResults, generalResults);
throw;
}
swap(ce.generalResults, generalResults);
}
return ce.CurrentFile;
}
void oEvent::transferListsAndSave(const oEvent &src) {
src.loadGeneralResults(false, false);
swap(src.generalResults, generalResults);
try {
src.getListContainer().synchronizeTo(getListContainer());
save();
}
catch (...) {
swap(src.generalResults, generalResults);
throw;
}
swap(src.generalResults, generalResults);
}
bool checkTargetClass(pRunner target, pRunner source,
const oClassList &Classes,
const vector<pRunner> &targetVacant,
vector<pRunner> &changedClass,
oEvent::ChangedClassMethod changeClassMethod) {
if (changeClassMethod == oEvent::TransferAnyway)
return true;
if (!compareClassName(target->getClass(false), source->getClass(false))) {
// Store all vacant positions in the right class
int targetClass = -1;
if (target->getStatus() == StatusOK) {
// There is already a result. Do not change class!
return false;
}
if (changeClassMethod == oEvent::TransferNoResult)
return false; // Do not allow change class, do not transfer result
for (oClassList::const_iterator cit = Classes.begin(); cit != Classes.end(); ++cit) {
if (cit->isRemoved())
continue;
if (compareClassName(cit->getName(), source->getClass(false))) {
targetClass = cit->getId();
if (targetClass == source->getClassId(false) || cit->getName() == source->getClass(false))
break; // Assume exact match
}
}
if (targetClass != -1) {
set<int> vacantIx;
for (size_t j = 0; j < targetVacant.size(); j++) {
if (!targetVacant[j])
continue;
if (targetVacant[j]->getClassId(false) == targetClass)
vacantIx.insert(j);
}
int posToUse = -1;
if (vacantIx.size() == 1)
posToUse = *vacantIx.begin();
else if (vacantIx.size() > 1) {
wstring srcBib = source->getBib();
if (srcBib.length() > 0) {
for (set<int>::iterator tit = vacantIx.begin(); tit != vacantIx.end(); ++tit) {
if (targetVacant[*tit]->getBib() == srcBib) {
posToUse = *tit;
break;
}
}
}
if (posToUse == -1)
posToUse = *vacantIx.begin();
}
if (posToUse != -1) {
// Change class or change class vacant
changedClass.push_back(target);
int oldStart = target->getStartTime();
wstring oldBib = target->getBib();
int oldSN = target->getStartNo();
int oldClass = target->getClassId(false);
pRunner tgt = targetVacant[posToUse];
target->cloneStartTime(tgt);
target->setBib(tgt->getBib(), 0, false);
target->setStartNo(tgt->getStartNo(), oBase::ChangeType::Update);
target->setClassId(tgt->getClassId(false), false);
tgt->setStartTime(oldStart, true, oBase::ChangeType::Update);
tgt->setBib(oldBib, 0, false);
tgt->setStartNo(oldSN, oBase::ChangeType::Update);
tgt->setClassId(oldClass, false);
return true; // Changed to correct class
}
else if (changeClassMethod == oEvent::ChangeClass) {
// Simpliy change class
target->setClassId(targetClass, false);
return true;
}
}
return false; // Wrong class, ChangeClass (but failed)
}
return true; // Same class, OK
}
void oEvent::transferResult(oEvent &ce,
const set<int> &allowNewEntries,
ChangedClassMethod changeClassMethod,
bool transferAllNoCompete,
vector<pRunner> &changedClass,
vector<pRunner> &changedClassNoResult,
vector<pRunner> &assignedVacant,
vector<pRunner> &newEntries,
vector<pRunner> &notTransfered,
vector<pRunner> &noAssignmentTarget) {
inthashmap processed(ce.Runners.size());
inthashmap used(Runners.size());
changedClass.clear();
changedClassNoResult.clear();
assignedVacant.clear();
newEntries.clear();
notTransfered.clear();
noAssignmentTarget.clear();
vector<pRunner> targetRunners;
vector<pRunner> targetVacant;
targetRunners.reserve(ce.Runners.size());
for (oRunnerList::iterator it = ce.Runners.begin(); it != ce.Runners.end(); ++it) {
if (!it->skip()) {
if (!it->isVacant())
targetRunners.push_back(&*it);
else
targetVacant.push_back(&*it);
}
}
calculateResults({}, ResultType::TotalResult);
// Lookup by id
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
pRunner r = getRunner(it->Id, 0);
if (!r)
continue;
__int64 id1 = r->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
wstring cnA = canonizeName(it->sName.c_str());
wstring cnB = canonizeName(r->sName.c_str());
wstring ccnA = canonizeName(it->getClub().c_str());
wstring ccnB = canonizeName(r->getClub().c_str());
if ((id1>0 && id1==id2) ||
(r->cardNumber>0 && r->cardNumber == it->cardNumber) ||
(it->sName == r->sName) || (cnA == cnB && ccnA == ccnB)) {
processed.insert(it->Id, 1);
used.insert(r->Id, 1);
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
it->setInputData(*r);
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
if (processed.size() < int(targetRunners.size())) {
// Lookup by card
int v;
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
if (processed.lookup(it->Id, v))
continue;
if (it->getCardNo() > 0) {
pRunner r = getRunnerByCardNo(it->getCardNo(), 0, CardLookupProperty::Any);
if (!r || used.lookup(r->Id, v))
continue;
__int64 id1 = r->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
if ((id1>0 && id1==id2) || (it->sName == r->sName && it->getClub() == r->getClub())) {
processed.insert(it->Id, 1);
used.insert(r->Id, 1);
if (checkTargetClass(it, r, ce.Classes, targetVacant, changedClass, changeClassMethod))
it->setInputData(*r);
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
}
}
int v = -1;
// Store remaining runners
vector<pRunner> remainingRunners;
for (oRunnerList::iterator it2 = Runners.begin(); it2 != Runners.end(); ++it2) {
if (it2->skip() || used.lookup(it2->Id, v))
continue;
if (it2->isVacant())
continue; // Ignore vacancies on source side
remainingRunners.push_back(&*it2);
}
if (processed.size() < int(targetRunners.size()) && !remainingRunners.empty()) {
// Lookup by name / ext id
vector<int> cnd;
for (size_t k = 0; k < targetRunners.size(); k++) {
pRunner it = targetRunners[k];
if (processed.lookup(it->Id, v))
continue;
__int64 id1 = it->getExtIdentifier();
cnd.clear();
for (size_t j = 0; j < remainingRunners.size(); j++) {
pRunner src = remainingRunners[j];
if (!src)
continue;
if (id1 > 0) {
__int64 id2 = src->getExtIdentifier();
if (id2 == id1) {
cnd.clear();
cnd.push_back(j);
break; //This is the one, if they have the same Id there will be a unique match below
}
}
if (it->sName == src->sName && it->getClub() == src->getClub())
cnd.push_back(j);
}
if (cnd.size() == 1) {
pRunner &src = remainingRunners[cnd[0]];
processed.insert(it->Id, 1);
used.insert(src->Id, 1);
if (checkTargetClass(it, src, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
it->setInputData(*src);
}
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
src = 0;
}
else if (cnd.size() > 0) { // More than one candidate
int winnerIx = -1;
int point = -1;
for (size_t j = 0; j < cnd.size(); j++) {
pRunner src = remainingRunners[cnd[j]];
int p = 0;
if (src->getClass(false) == it->getClass(false))
p += 1;
if (src->getBirthYear() == it->getBirthYear())
p += 2;
if (p > point) {
winnerIx = cnd[j];
point = p;
}
}
if (winnerIx != -1) {
processed.insert(it->Id, 1);
pRunner winner = remainingRunners[winnerIx];
remainingRunners[winnerIx] = 0;
used.insert(winner->Id, 1);
if (checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass, changeClassMethod)) {
it->setInputData(*winner);
}
else {
it->resetInputData();
changedClassNoResult.push_back(it);
}
}
}
}
}
// Transfer vacancies
for (size_t k = 0; k < remainingRunners.size(); k++) {
pRunner src = remainingRunners[k];
if (!src || used.lookup(src->Id, v))
continue;
bool forceSkip = src->hasFlag(oAbstractRunner::FlagTransferSpecified) &&
!src->hasFlag(oAbstractRunner::FlagTransferNew);
if (forceSkip) {
notTransfered.push_back(src);
continue;
}
pRunner targetVacant = ce.getRunner(src->getId(), 0);
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(false), src->getClass(false)) ) {
targetVacant->setName(src->getName(), false);
targetVacant->setClub(src->getClub());
targetVacant->setCardNo(src->getCardNo(), false);
targetVacant->cloneData(src);
assignedVacant.push_back(targetVacant);
}
else {
pClass dstClass = ce.getClass(src->getClassId(false));
if (dstClass && compareClassName(dstClass->getName(), src->getClass(false))) {
if ( (!src->hasFlag(oAbstractRunner::FlagTransferSpecified) && allowNewEntries.count(src->getClassId(false)))
|| src->hasFlag(oAbstractRunner::FlagTransferNew)) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false),
src->getCardNo(), src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
newEntries.push_back(dst);
}
else if (transferAllNoCompete) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(false),
0, src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
dst->setStatus(StatusNotCompetiting, true, ChangeType::Update);
notTransfered.push_back(dst);
}
else
notTransfered.push_back(src);
}
}
}
// Runners on target side not assigned a result
for (size_t k = 0; k < targetRunners.size(); k++) {
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
noAssignmentTarget.push_back(targetRunners[k]);
if (targetRunners[k]->inputStatus == StatusUnknown ||
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
targetRunners[k]->inputStatus = StatusNotCompetiting;
}
}
}
}
void oEvent::transferResult(oEvent &ce,
ChangedClassMethod changeClassMethod,
vector<pTeam> &newEntries,
vector<pTeam> &notTransfered,
vector<pTeam> &noAssignmentTarget) {
inthashmap processed(ce.Teams.size());
inthashmap used(Teams.size());
newEntries.clear();
notTransfered.clear();
noAssignmentTarget.clear();
vector<pTeam> targetTeams;
targetTeams.reserve(ce.Teams.size());
for (oTeamList::iterator it = ce.Teams.begin(); it != ce.Teams.end(); ++it) {
if (!it->skip()) {
targetTeams.push_back(&*it);
}
}
calculateTeamResults(set<int>(), ResultType::TotalResult);
// Lookup by id
for (size_t k = 0; k < targetTeams.size(); k++) {
pTeam it = targetTeams[k];
pTeam t = getTeam(it->Id);
if (!t)
continue;
__int64 id1 = t->getExtIdentifier();
__int64 id2 = it->getExtIdentifier();
if (id1>0 && id2>0 && id1 != id2)
continue;
if ((id1>0 && id1==id2) || (it->sName == t->sName && it->getClub() == t->getClub())) {
processed.insert(it->Id, 1);
used.insert(t->Id, 1);
it->setInputData(*t);
//checkTargetClass(it, r, ce.Classes, targetVacant, changedClass);
}
}
int v = -1;
// Store remaining runners
vector<pTeam> remainingTeams;
for (oTeamList::iterator it2 = Teams.begin(); it2 != Teams.end(); ++it2) {
if (it2->skip() || used.lookup(it2->Id, v))
continue;
if (it2->isVacant())
continue; // Ignore vacancies on source side
remainingTeams.push_back(&*it2);
}
if (processed.size() < int(targetTeams.size()) && !remainingTeams.empty()) {
// Lookup by name / ext id
vector<int> cnd;
for (size_t k = 0; k < targetTeams.size(); k++) {
pTeam it = targetTeams[k];
if (processed.lookup(it->Id, v))
continue;
__int64 id1 = it->getExtIdentifier();
cnd.clear();
for (size_t j = 0; j < remainingTeams.size(); j++) {
pTeam src = remainingTeams[j];
if (!src)
continue;
if (id1 > 0) {
__int64 id2 = src->getExtIdentifier();
if (id2 == id1) {
cnd.clear();
cnd.push_back(j);
break; //This is the one, if they have the same Id there will be a unique match below
}
}
if (it->sName == src->sName && it->getClub() == src->getClub())
cnd.push_back(j);
}
if (cnd.size() == 1) {
pTeam &src = remainingTeams[cnd[0]];
processed.insert(it->Id, 1);
used.insert(src->Id, 1);
it->setInputData(*src);
//checkTargetClass(it, src, ce.Classes, targetVacant, changedClass);
src = 0;
}
else if (cnd.size() > 0) { // More than one candidate
int winnerIx = -1;
int point = -1;
for (size_t j = 0; j < cnd.size(); j++) {
pTeam src = remainingTeams[cnd[j]];
int p = 0;
if (src->getClass(false) == it->getClass(false))
p += 1;
if (p > point) {
winnerIx = cnd[j];
point = p;
}
}
if (winnerIx != -1) {
processed.insert(it->Id, 1);
pTeam winner = remainingTeams[winnerIx];
remainingTeams[winnerIx] = 0;
used.insert(winner->Id, 1);
it->setInputData(*winner);
//checkTargetClass(it, winner, ce.Classes, targetVacant, changedClass);
}
}
}
}
/*
// Transfer vacancies
for (size_t k = 0; k < remainingRunners.size(); k++) {
pRunner src = remainingRunners[k];
if (!src || used.lookup(src->Id, v))
continue;
pRunner targetVacant = ce.getRunner(src->getId(), 0);
if (targetVacant && targetVacant->isVacant() && compareClassName(targetVacant->getClass(), src->getClass()) ) {
targetVacant->setName(src->getName());
targetVacant->setClub(src->getClub());
targetVacant->setCardNo(src->getCardNo(), false);
targetVacant->cloneData(src);
assignedVacant.push_back(targetVacant);
}
else {
pClass dstClass = ce.getClass(src->getClassId());
if (dstClass && compareClassName(dstClass->getName(), src->getClass())) {
if (allowNewEntries.count(src->getClassId())) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
src->getCardNo(), src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
newEntries.push_back(dst);
}
else if (transferAllNoCompete) {
if (src->getClubId() > 0)
ce.getClubCreate(src->getClubId(), src->getClub());
pRunner dst = ce.addRunner(src->getName(), src->getClubId(), src->getClassId(),
0, src->getBirthYear(), true);
dst->cloneData(src);
dst->setInputData(*src);
dst->setStatus(StatusNotCompetiting);
notTransfered.push_back(dst);
}
else
notTransfered.push_back(src);
}
}
}
// Runners on target side not assigned a result
for (size_t k = 0; k < targetRunners.size(); k++) {
if (targetRunners[k] && !processed.count(targetRunners[k]->Id)) {
noAssignmentTarget.push_back(targetRunners[k]);
if (targetRunners[k]->inputStatus == StatusUnknown ||
(targetRunners[k]->inputStatus == StatusOK && targetRunners[k]->inputTime == 0)) {
targetRunners[k]->inputStatus = StatusNotCompetiting;
}
}
}*/
}
MetaListContainer &oEvent::getListContainer() const {
if (!listContainer)
throw std::exception("Nullpointer exception");
@ -6945,3 +6262,61 @@ void oEvent::setFlag(TransferFlags flag, bool onoff) {
cf = onoff ? (cf | flag) : (cf & (~flag));
getDI().setInt("TransferFlags", cf);
}
string oEvent::encodeStartGroups() const {
string ss;
string tmp;
for (auto &sg : startGroups) {
tmp = itos(sg.first) + "," +
itos(sg.second.first) + "," + itos(sg.second.second);
if (ss.empty())
ss = tmp;
else
ss += ";" + tmp;
}
return ss;
}
void oEvent::decodeStartGroups(const string &enc) const {
vector<string> g, sg;
split(enc, ";", g);
startGroups.clear();
for (string &grp : g) {
split(grp, ",", sg);
if (sg.size() == 3) {
int id = atoi(sg[0].c_str());
int start = atoi(sg[1].c_str());
int end = atoi(sg[2].c_str());
startGroups.emplace(id, make_pair(start, end));
}
}
}
void oEvent::setStartGroup(int id, int firstStart, int lastStart) {
if (firstStart < 0)
startGroups.erase(id);
else
startGroups[id] = make_pair(firstStart, lastStart);
}
void oEvent::updateStartGroups() {
getDI().setString("StartGroups", gdibase.widen(encodeStartGroups()));
}
void oEvent::readStartGroups() const {
auto &sg = getDCI().getString("StartGroups");
decodeStartGroups(gdibase.narrow(sg));
}
const map<int, pair<int, int>> &oEvent::getStartGroups(bool reload) const {
if (reload)
readStartGroups();
return startGroups;
}
pair<int, int> oEvent::getStartGroup(int id) const {
if (startGroups.count(id))
return startGroups.find(id)->second;
else
return make_pair(-1, -1);
}

View File

@ -442,10 +442,24 @@ protected:
mutable vector<GeneralResultCtr> generalResults;
// Start group id -> first, last start
mutable map<int, pair<int, int>> startGroups;
string encodeStartGroups() const;
void decodeStartGroups(const string &enc) const;
// Temporarily disable recaluclate leader times
bool disableRecalculate;
public:
void setStartGroup(int id, int firstStart, int lastStart);
void updateStartGroups(); // Update to source
void readStartGroups() const; // Read from source.
pair<int, int> getStartGroup(int id) const;
const map<int, pair<int, int>> &getStartGroups(bool reload) const;
enum TransferFlags {
FlagManualName = 1,
FlagManualDateTime = 2,
@ -949,7 +963,7 @@ public:
bool exportOECSV(const wchar_t *file, int LanguageTypeIndex, bool includeSplits);
bool save();
void duplicate();
void duplicate(const wstring &annotation);
void newCompetition(const wstring &Name);
void clearListedCmp();
bool enumerateCompetitions(const wchar_t *path, const wchar_t *extension);
@ -1053,11 +1067,15 @@ public:
pCard allocateCard(pRunner owner);
/** Optimize the start order based on drawInfo. Result in cInfo */
void optimizeStartOrder(gdioutput &gdi, DrawInfo &drawInfo, vector<ClassInfo> &cInfo);
void optimizeStartOrder(vector<pair<int, wstring>> &outLines, DrawInfo &drawInfo, vector<ClassInfo> &cInfo);
void loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const;
void drawRemaining(DrawMethod method, bool placeAfter);
void drawListStartGroups(const vector<ClassDrawSpecification> &spec,
DrawMethod method, int pairSize, DrawType drawType,
bool limitGroupSize = true,
DrawInfo *di = nullptr);
void drawList(const vector<ClassDrawSpecification> &spec,
DrawMethod method, int pairSize, DrawType drawType);
void drawListClumped(int classID, int firstStart, int interval, int vacances);
@ -1176,7 +1194,7 @@ public:
void fillFees(gdioutput &gdi, const string &name, bool onlyDirect, bool withAuto) const;
wstring getAutoClassName() const;
pClass addClass(const wstring &pname, int CourseId = 0, int classId = 0);
pClass addClass(oClass &c);
pClass addClass(const oClass &c);
/** Get a class if it exists, or create it.
exactNames is a set of class names that must be matched exactly.
It is extended with the name of the class added. The purpose is to allow very
@ -1242,7 +1260,7 @@ public:
const vector< pair<wstring, size_t> > &fillControlTypes(vector< pair<wstring, size_t> > &out);
bool open(int id);
bool open(const wstring &file, bool import=false);
bool open(const wstring &file, bool import, bool forMerge);
bool open(const xmlparser &xml);
bool save(const wstring &file);
@ -1283,6 +1301,8 @@ protected:
/** type: 0 control, 1 start, 2 finish*/
bool addXMLControl(const xmlobject &xcontrol, int type);
void merge(const oBase &src) final;
public:
const shared_ptr<GeneralResult> &getGeneralResult(const string &tag, wstring &sourceFileOut) const;
@ -1293,6 +1313,9 @@ public:
void getPredefinedClassTypes(map<wstring, ClassMetaType> &types) const;
void merge(oEvent &src, int &numAdd, int &numRemove, int &numUpdate);
string getLastModified() const;
wstring cloneCompetition(bool cloneRunners, bool cloneTimes,
bool cloneCourses, bool cloneResult, bool addToDate);
@ -1323,6 +1346,10 @@ public:
void transferListsAndSave(const oEvent &src);
wstring getMergeTag(bool forceReset = false);
wstring getMergeInfo(const wstring &tag) const;
void addMergeInfo(const wstring &tag, const wstring &version);
enum MultiStageType {
MultiStageNone = 0,
MultiStageSeparateEntry = 1,

View File

@ -27,6 +27,8 @@
#include <set>
#include <cassert>
#include <algorithm>
#include <chrono>
#include <random>
#include "oEvent.h"
#include "gdioutput.h"
@ -422,7 +424,6 @@ namespace {
}
}
class DrawOptimAlgo {
private:
oEvent * oe;
@ -503,7 +504,17 @@ private:
if (!drawClass)
continue;
int nr = c_it->getNumRunners(true, true, true);
int nr = 0;
if (ci.startGroupId == 0)
nr = c_it->getNumRunners(true, true, true);
else {
vector<pRunner> cr;
oe->getRunners(c_it->getId(), 0, cr, false);
for (pRunner r : cr)
if (r->getStartGroup(true) == ci.startGroupId)
nr++;
}
if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) {
// Auto initialize
int nVacancies = int(nr * di.vacancyFactor + 0.5);
@ -623,9 +634,11 @@ public:
di.baseInterval = 1;
di.minClassInterval = 0;
}
int startGroup = 0;
// Calculate an estimated maximal class intervall
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].startGroupId != 0)
startGroup = cInfo[k].startGroupId; // Need to be constant
int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval);
if (quotient*di.baseInterval > di.maxClassInterval)
@ -645,13 +658,18 @@ public:
// Fill up with non-drawn classes
for (auto &it : Runners) {
if (it->isRemoved())
continue;
int st = it->getStartTime();
int relSt = st - di.firstStart;
int relPos = relSt / di.baseInterval;
if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) {
if (otherClasses.count(it->getClassId(false)) == 0)
int cid = it->getClassId(true);
if (otherClasses.count(cid) == 0) {
if (startGroup == 0 || startGroup == it->getStartGroup(true))
continue;
}
pClass cls = it->getClassRef(true);
if (cls) {
if (!di.startName.empty() && cls->getStart() != di.startName)
@ -661,7 +679,24 @@ public:
continue;
}
ClassInfo &ci = otherClasses[it->getClassId(false)];
int unique, courseId;
auto res = otherClasses.find(cid);
if (res != otherClasses.end()) {
unique = res->second.unique;
courseId = res->second.courseId;
}
else {
res = di.classes.find(cid);
if (res != di.classes.end()) {
unique = res->second.unique;
courseId = res->second.courseId;
}
else {
int unique = 12345678;
int courseId = it->getCourse(false) ? it->getCourse(false)->getId() : 0;
}
}
int k = 0;
while (true) {
if (k == StartField.size()) {
@ -669,8 +704,8 @@ public:
StartField.back().resize(3000);
}
if (StartField[k][relPos].first == 0) {
StartField[k][relPos].first = ci.unique;
StartField[k][relPos].second = ci.courseId;
StartField[k][relPos].first = unique;
StartField[k][relPos].second = courseId;
break;
}
k++;
@ -703,8 +738,14 @@ public:
int minPos = 1000000;
int minEndPos = 1000000;
int minInterval = cInfo[k].interval;
int startV = di.minClassInterval / di.baseInterval;
int endV = cInfo[k].interval;
for (int i = di.minClassInterval / di.baseInterval; i <= cInfo[k].interval; i++) {
if (cInfo[k].fixedInterval != 0) {
endV = startV = cInfo[k].fixedInterval;
}
for (int i = startV; i <= endV; i++) {
int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3);
startpos = 0;
@ -744,7 +785,7 @@ public:
}
};
void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo> &cInfo)
void oEvent::optimizeStartOrder(vector<pair<int, wstring>> &outLines, DrawInfo &di, vector<ClassInfo> &cInfo)
{
if (Classes.size()==0)
return;
@ -823,11 +864,11 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
vector< vector<pair<int, int> > > startField(di.nFields);
drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator);
gdi.addString("", 0, "Identifierar X unika inledningar på banorna.#" + itos(di.numDistinctInit));
gdi.addString("", 0, "Största gruppen med samma inledning har X platser.#" + itos(di.numRunnerSameInitMax));
gdi.addString("", 0, "Antal löpare på vanligaste banan X.#" + itos(di.numRunnerSameCourseMax));
gdi.addString("", 0, "Kortast teoretiska startdjup utan krockar är X minuter.#" + itos(di.minimalStartDepth/60));
gdi.dropLine();
outLines.emplace_back(0, L"Identifierar X unika inledningar på banorna.#" + itow(di.numDistinctInit));
outLines.emplace_back(0, L"Största gruppen med samma inledning har X platser.#" + itow(di.numRunnerSameInitMax));
outLines.emplace_back(0, L"Antal löpare på vanligaste banan X.#" + itow(di.numRunnerSameCourseMax));
outLines.emplace_back(0, L"Kortast teoretiska startdjup utan krockar är X minuter.#" + itow(di.minimalStartDepth/60));
outLines.emplace_back(0, L"");
//Find last starter
int last = opt.last;
@ -837,17 +878,17 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
laststart=max(laststart, ci.firstStart+(ci.nRunners-1)*ci.interval);
}
gdi.addString("", 0, "Faktiskt startdjup: X minuter.#" + itos(((last+1) * di.baseInterval)/60));
outLines.emplace_back(0, L"Faktiskt startdjup: X minuter.#" + itow(((last+1) * di.baseInterval)/60));
gdi.addString("", 1, L"Sista start (nu tilldelad): X.#" +
outLines.emplace_back(1, L"Sista start (nu tilldelad): X.#" +
oe->getAbsTime(laststart*di.baseInterval+di.firstStart));
gdi.dropLine();
outLines.emplace_back(0, L"");
int nr;
int T=0;
int sum=0;
gdi.addString("", 1, "Antal startande per intervall (inklusive redan lottade):");
outLines.emplace_back(1, L"Antal startande per intervall (inklusive redan lottade):");
string str="";
int empty=4;
@ -867,8 +908,8 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
str+=bf;
}
gdi.addStringUT(10, str);
gdi.dropLine();
outLines.emplace_back(10, L"#" + gdibase.widen(str));
outLines.emplace_back(0, L"");
}
void oEvent::loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const {
@ -967,6 +1008,653 @@ void oEvent::drawRemaining(DrawMethod method, bool placeAfter)
}
}
struct GroupInfo {
int firstStart = 0;
int unassigned = 0;
int ix;
vector<int> rPerGroup;
vector<int> vacantPerGroup;
};
namespace {
bool sameFamily(pRunner a, pRunner b) {
wstring af = a->getFamilyName();
wstring bf = b->getFamilyName();
if (af.length() == bf.length())
return af == bf;
if (af.length() > bf.length())
swap(af, bf);
vector<wstring> bff;
split(bf, L" -", bff);
for (wstring &bfs : bff) {
if (bfs == af)
return true;
}
return false;
//return af.find(bf) != wstring::npos || bf.find(af) != wstring::npos;
}
template<typename RND>
void groupUnassignedRunners(vector<pRunner> &rIn,
vector<pair<vector<pRunner>, bool>> &rGroups,
int maxPerGroup, RND rnd) {
map<int, vector<pRunner>> families;
for (pRunner &r : rIn) {
int fam = r->getDCI().getInt("Family");
if (fam != 0) {
families[fam].push_back(r);
r = nullptr;
}
}
map<int, vector<pRunner>> clubs;
for (pRunner &r : rIn) {
if (r)
clubs[r->getClubId()].push_back(r);
}
// Merge families to clubs, if appropriate
for (auto &fam : families) {
int cid = fam.second[0]->getClubId();
if (cid != 0 && clubs.count(cid) && int(clubs[cid].size() + fam.second.size()) < maxPerGroup) {
clubs[cid].insert(clubs[cid].end(), fam.second.begin(), fam.second.end());
fam.second.clear();
}
}
vector<vector<pRunner>> rawGroups;
for (auto &g : families) {
if (g.second.size() > 0) {
rawGroups.emplace_back();
rawGroups.back().swap(g.second);
}
}
for (auto &g : clubs) {
if (g.second.size() > 0) {
rawGroups.emplace_back();
rawGroups.back().swap(g.second);
}
}
shuffle(rawGroups.begin(), rawGroups.end(), rnd);
stable_sort(rawGroups.begin(), rawGroups.end(),
[](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size()/4) < (b.size()/4); });
for (auto &g : rawGroups) {
if (int(g.size()) <= maxPerGroup)
rGroups.emplace_back(g, false);
else {
int nSplit = (g.size() + maxPerGroup - 1) / maxPerGroup;
sort(g.begin(), g.end(), [](const pRunner &a, const pRunner &b) {
wstring n1 = a->getFamilyName();
wstring n2 = b->getFamilyName();
return n1 < n2;
});
vector<vector<pRunner>> famGroups(1);
for (pRunner r : g) {
if (famGroups.back().empty())
famGroups.back().push_back(r);
else {
if (!sameFamily(famGroups.back().back(), r))
famGroups.emplace_back();
famGroups.back().push_back(r);
}
}
shuffle(famGroups.begin(), famGroups.end(), rnd);
stable_sort(famGroups.begin(), famGroups.end(),
[](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size() / 4) < (b.size() / 4); });
size_t nPerGroup = g.size() / nSplit + 1;
bool brk = false;
while (!famGroups.empty()) {
rGroups.emplace_back(vector<pRunner>(), brk);
auto &dst = rGroups.back().first;
brk = true;
while (!famGroups.empty() && dst.size() + famGroups.back().size() <= nPerGroup) {
dst.insert(dst.end(), famGroups.back().begin(), famGroups.back().end());
famGroups.pop_back();
}
}
}
}
}
void printGroups(gdioutput &gdibase, const list<oRunner> &Runners) {
map<int, vector<const oRunner *>> rbg;
for (const oRunner &r : Runners) {
rbg[r.getStartGroup(true)].push_back(&r);
}
gdibase.dropLine();
gdibase.addString("", 0, "List groups");
gdibase.dropLine();
map<int, int> ccCount;
for (auto rr : rbg) {
auto &vr = rr.second;
sort(vr.begin(), vr.end(), [](const oRunner *a, const oRunner *b) {
if (a->getClassId(true) != b->getClassId(true))
return a->getClassId(true) < b->getClassId(true);
else
return a->getClubId() < b->getClubId(); });
gdibase.dropLine();
gdibase.addString("", 1, "Group: " + itos(rr.first));
int cls = -1;
for (const oRunner *r : vr) {
if (cls != r->getClassId(true)) {
gdibase.dropLine();
gdibase.addString("", 1, r->getClass(true));
cls = r->getClassId(true);
}
++ccCount[cls];
gdibase.addString("", 0, itow(ccCount[cls]) + L": " + r->getCompleteIdentification());
}
}
}
}
void oEvent::drawListStartGroups(const vector<ClassDrawSpecification> &spec,
DrawMethod method, int pairSize, DrawType drawType,
bool limitGroupSize,
DrawInfo *diIn) {
int nParallel = -1;
if (diIn)
nParallel = diIn->nFields;
pRunner alice = getRunner(155227, 0);
constexpr bool logOutput = false;
auto &sgMap = getStartGroups(true);
if (sgMap.empty())
throw meosException("No start group defined");
map<int, GroupInfo> gInfo;
vector<pair<int, int>> orderedStartGroups;
for (auto &sg : sgMap) {
orderedStartGroups.emplace_back(sg.second.first, sg.first);
}
// Order by start time
sort(orderedStartGroups.begin(), orderedStartGroups.end());
int fs = orderedStartGroups[0].first; // First start
vector<pair<int, int>> rPerGroupTotal;
map<int, int> gId2Ix;
for (auto &sg : orderedStartGroups) {
gId2Ix[sg.second] = rPerGroupTotal.size();
rPerGroupTotal.emplace_back(0, sg.second);
}
for (size_t k = 0; k < spec.size(); k++) {
auto &s = spec[k];
auto &gi = gInfo[s.classID];
gi.ix = k;
gi.firstStart = fs;
gi.rPerGroup.resize(sgMap.size());
}
vector<pRunner> unassigned;
int total = 0;
for (auto &r : Runners) {
if (r.isRemoved() || r.isVacant())
continue;
r.tmpStartGroup = 0;
int clsId = r.getClassId(true);
auto res = gInfo.find(clsId);
if (res != gInfo.end()) {
total++;
int id = r.getStartGroup(false);
auto idRes = gId2Ix.find(id);
if (idRes != gId2Ix.end()) {
++res->second.rPerGroup[idRes->second];
++rPerGroupTotal[idRes->second].first;
}
else {
++res->second.unassigned;
unassigned.push_back(&r);
}
}
}
vector<pair<vector<pRunner>, bool>> uaGroups;
unsigned seed = (unsigned)chrono::system_clock::now().time_since_epoch().count();
auto rnd = std::default_random_engine(seed);
int maxPerGroup = Runners.size();
if (limitGroupSize)
maxPerGroup = max(int(Runners.size() / sgMap.size()) / 4, 4);
groupUnassignedRunners(unassigned, uaGroups, maxPerGroup, rnd );
int nPerGroupAvg = (total * 9) / (sgMap.size() * 10);
shuffle(rPerGroupTotal.begin(), rPerGroupTotal.end(), rnd);
// Assign to groups
while (!uaGroups.empty()) {
stable_sort(rPerGroupTotal.begin(), rPerGroupTotal.end(),
[](const pair<int,int> &a, const pair<int, int> &b) {return (a.first / 4) < (b.first / 4); });
// Setup map to next start group
map<int, int> nextGroup;
auto getNextGroup = [&](int ix) {
if (nextGroup.empty()) {
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
int srcId = rPerGroupTotal[k].second;
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
if (orderedStartGroups[j].second == srcId) {
int nextGrpId;
if (j + 1 < orderedStartGroups.size())
nextGrpId = orderedStartGroups[j + 1].second;
else
nextGrpId = orderedStartGroups[0].second;
for (size_t kk = 0; kk < rPerGroupTotal.size(); kk++) {
if (rPerGroupTotal[kk].second == nextGrpId) {
nextGroup[k] = kk;
break;
}
}
break;
}
}
}
}
return nextGroup[ix];
};
int nextGroupToUse = -1;
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
if (nextGroupToUse == -1)
nextGroupToUse = k;
int &nGroup = rPerGroupTotal[nextGroupToUse].first;
int groupId = rPerGroupTotal[nextGroupToUse].second;
int currentGroup = nextGroupToUse;
nextGroupToUse = -1;
if (logOutput) {
gdibase.dropLine();
gdibase.addString("", 1, "Group: " + itos(groupId) + " (" + itos(nGroup) + ")");
}
while (nGroup <= nPerGroupAvg && !uaGroups.empty()) {
for (pRunner ua : uaGroups.back().first) {
ua->tmpStartGroup = groupId;
auto &gi = gInfo[ua->getClassId(true)];
int j = gId2Ix[groupId];
++gi.rPerGroup[j];
nGroup++;
}
bool skip = uaGroups.back().second;
uaGroups.pop_back();
if (skip) {
nextGroupToUse = getNextGroup(currentGroup);
break; // Assign to next group (same club)
}
}
}
nPerGroupAvg++; // Ensure convergance
}
// vacantPerGroup
for (size_t k = 0; k < spec.size(); k++) {
auto &gi = gInfo[spec[k].classID];
gi.vacantPerGroup.resize(sgMap.size());
for (int j = 0; j < spec[k].vacances; j++)
++gi.vacantPerGroup[GetRandomNumber(sgMap.size())];
}
if (logOutput)
printGroups(gdibase, Runners);
map<int, int> rPerGroup;
// Ensure not too many competitors in same class per groups
vector<map<int, map<int, int>>> countClassGroupClub(spec.size());
vector<pRunner> rl;
map<int, double> classFractions;
map<pair<int,int>, double> clubFractions;
int rTot = 0;
for (size_t k = 0; k < spec.size(); k++) {
auto &countGroupClub = countClassGroupClub[k];
int cls = spec[k].classID;
getRunners(cls, 0, rl, false);
classFractions[cls] = rl.size();
rTot += rl.size();
for (pRunner r : rl) {
int gid = r->getStartGroup(true);
++rPerGroup[gid];
int club = r->getClubId();
if (club != 0) {
++clubFractions[make_pair(club,cls)];
++countGroupClub[gid][club]; // Count per club
}
++countGroupClub[gid][0]; // Count all
}
}
double q = 1.0 / rTot;
for (auto &e : classFractions)
e.second *= q;
for (auto &e : clubFractions)
e.second *= (q / classFractions[e.first.second]);
vector<tuple<int, int, int, int>> moveFromGroupClassClub;
// For each start group, count class members in each class and per club
vector<pair<int, int>> numberClub;
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
auto &g = orderedStartGroups[j];
int groupId = g.second;
for (size_t k = 0; k < spec.size(); k++) {
auto &countGroupClub = countClassGroupClub[k];
int cls = spec[k].classID;
const int nTotal = countGroupClub[groupId][0];
int clubSwitch = 0; // 0 any club, or specified club id.
int numNeedSwitch = 0;
if (nTotal > 3) {
numberClub.clear();
for (auto &clubCount : countGroupClub[groupId]) {
if (clubCount.first != 0)
numberClub.emplace_back(clubCount.second, clubCount.first);
}
if (numberClub.empty())
continue;
sort(numberClub.begin(), numberClub.end());
int clbId = numberClub.back().second;
int limit = max<int>(nTotal / 2, int(nTotal*clubFractions[make_pair(clbId, cls)]));
int limitTot = int(rPerGroup[groupId] * max(0.3, classFractions[cls]));
if (numberClub.size() == 1 || numberClub.back().first > limit) {
numNeedSwitch = numberClub.back().first - limit;
clubSwitch = clbId;
}
else if (nTotal > limitTot) {
// Move from any club
numNeedSwitch = nTotal - limitTot;
clubSwitch = 0;
}
}
if (numNeedSwitch > 0) {
moveFromGroupClassClub.emplace_back(groupId, cls, clubSwitch, numNeedSwitch);
}
}
}
if (moveFromGroupClassClub.size() > 0 && limitGroupSize) {
vector<vector<pRunner>> runnersPerGroup(orderedStartGroups.size());
map<int, int> groupId2Ix;
for (size_t j = 0; j < orderedStartGroups.size(); j++)
groupId2Ix[orderedStartGroups[j].second] = j;
for (size_t k = 0; k < spec.size(); k++) {
getRunners(spec[k].classID, 0, rl, false);
for (pRunner r : rl) {
runnersPerGroup[groupId2Ix[r->tmpStartGroup]].push_back(r);
}
}
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
shuffle(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), rnd);
stable_sort(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), [](pRunner a, pRunner b) {
int specA = a->getStartGroup(false);
int specB = b->getStartGroup(false);
if (specA != specB)
return specA < specB;
return a->getEntryDate(false) > b->getEntryDate(false);
});
}
map<int, map<pair<int, int>, int>> classClubCountByGroup;
for (auto ms : moveFromGroupClassClub) {
int groupId = get<0>(ms);
int clsId = get<1>(ms);
int clubId = get<2>(ms);
int cnt = get<3>(ms);
classClubCountByGroup[groupId][make_pair(clsId, clubId)] = cnt;
}
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
int thisGroupId = orderedStartGroups[j].second;
auto &classClubCount = classClubCountByGroup[thisGroupId];
for (pRunner &r : runnersPerGroup[j]) {
if (!r)
continue;
int cls = r->getClassId(true);
int club = r->getClubId();
auto res = classClubCount.find(make_pair(cls, club));
int type = 0;
if (res != classClubCount.end()) {
if (res->second > 0) {
--res->second;
type = 1;
}
}
else {
res = classClubCount.find(make_pair(cls, 0));
if (res != classClubCount.end()) {
if (res->second > 0) {
--res->second;
type = 2;
}
}
}
if (type == 0)
continue; //do not move
constexpr int done = 100;
for (int iter = 0; iter < 5; iter++) {
int nextG = -1;
if ((iter & 1) == 0)
nextG = j + (iter / 2 + 1);
else
nextG = j - (iter / 2 + 1);
if (size_t(nextG) >= runnersPerGroup.size())
continue;
int nextGroupId = orderedStartGroups[nextG].second;
auto &nextClassClubCount = classClubCountByGroup[nextGroupId];
if (type == 1 && nextClassClubCount.find(make_pair(cls, club)) != nextClassClubCount.end())
continue;
for (pRunner &rr : runnersPerGroup[nextG]) {
if (rr) {
if (type == 1 && rr->getClassId(true) == cls && rr->getClubId() != club) {
rr->tmpStartGroup = orderedStartGroups[j].second;
r->tmpStartGroup = orderedStartGroups[nextG].second;
rr = nullptr;
r = nullptr;
iter = done;
break;
}
else if (type == 2 && rr->getClassId(true) != cls) {
rr->tmpStartGroup = orderedStartGroups[j].second;
r->tmpStartGroup = orderedStartGroups[nextG].second;
rr = nullptr;
r = nullptr;
iter = done;
break;
}
}
}
}
}
}
}
if (logOutput) {
gdibase.addString("", 0, "Reordered groups");
printGroups(gdibase, Runners);
}
if (spec.size() == 1) {
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
auto &sg = orderedStartGroups[j];
int groupId = sg.second;
int firstStart = getStartGroup(groupId).first;
vector<ClassDrawSpecification> specLoc = spec;
for (size_t k = 0; k < specLoc.size(); k++) {
auto &gi = gInfo[specLoc[k].classID];
specLoc[k].startGroup = groupId;
specLoc[k].firstStart = max(gi.firstStart, firstStart);
specLoc[k].vacances = gi.vacantPerGroup[j];
gi.firstStart = specLoc[k].firstStart + specLoc[k].interval * (specLoc[k].vacances + gi.rPerGroup[j]);
}
drawList(specLoc, method, pairSize, drawType);
}
}
else {
int leg = spec[0].leg;
VacantPosition vp = VacantPosition::Mixed;
DrawInfo di;
di.baseInterval = 60;
di.allowNeighbourSameCourse = true;
di.extraFactor = 0;
di.minClassInterval = di.baseInterval * 2;
di.maxClassInterval = di.baseInterval * 8;
di.minVacancy = 1;
di.maxVacancy = 100;
di.vacancyFactor = 0;
if (diIn) {
di.baseInterval = diIn->baseInterval;
di.minClassInterval = diIn->minClassInterval;
di.maxCommonControl = diIn->maxCommonControl;
di.allowNeighbourSameCourse = diIn->allowNeighbourSameCourse;
}
// Update runner per group/class counters
for (auto &gi : gInfo) {
fill(gi.second.rPerGroup.begin(), gi.second.rPerGroup.end(), 0);
}
rPerGroup.clear();
for (size_t k = 0; k < spec.size(); k++) {
getRunners(spec[k].classID, 0, rl, false);
auto &gi = gInfo[spec[k].classID];
for (pRunner r : rl) {
r->setStartTime(0, true, oBase::ChangeType::Update, false);
int gid = r->getStartGroup(true);
++rPerGroup[gid];
++gi.rPerGroup[gId2Ix[gid]];
}
}
map<int, int> rPerGroup;
for (auto &gt : rPerGroupTotal)
rPerGroup[gt.second] = gt.first;
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
auto &sg = orderedStartGroups[j];
int groupId = sg.second;
int firstStart = getStartGroup(groupId).first;
int nspos = (oe->getStartGroup(groupId).second - firstStart) / di.baseInterval;
int optimalParallel = rPerGroup[groupId] / nspos;
if (nParallel <= 0)
di.nFields = max(3, min(optimalParallel + 2, 100));
else
di.nFields = nParallel;
di.firstStart = firstStart;
di.changedVacancyInfo = false;
vector<ClassInfo> cInfo;
vector<pair<int, wstring>> outLines;
di.vacancyFactor = 0;
auto &group = sgMap.find(groupId);
int length = max(300, group->second.second - group->second.first);
int slots = length / di.baseInterval;
di.classes.clear();
for (size_t k = 0; k < spec.size(); k++) {
auto &gi = gInfo[spec[k].classID];
int rClassGroup = gi.rPerGroup[j];
if (rClassGroup == 0)
continue;
di.classes[spec[k].classID] = ClassInfo(getClass(spec[k].classID));
ClassInfo &ci = di.classes[spec[k].classID];
ci.startGroupId = groupId;
if (gi.firstStart > firstStart) {
ci.firstStart = (gi.firstStart - firstStart) / di.baseInterval;
ci.hasFixedTime = true;
}
ci.nVacant = gi.vacantPerGroup[j];
ci.nVacantSpecified = true;
ci.nExtraSpecified = true;
if (rClassGroup == 1)
ci.fixedInterval = 4;
else {
int q = slots / (rClassGroup-1);
if (q >= 12)
ci.fixedInterval = 8;
else if (q >= 6)
ci.fixedInterval = 4;
else
ci.fixedInterval = 2;
}
if (ci.fixedInterval > 2)
ci.interval = ci.fixedInterval;
else
ci.interval = spec[k].interval / di.baseInterval;
}
if (logOutput) {
gdibase.dropLine();
gdibase.addString("", 1, "Behandlar grupp: " + itos(groupId));
}
optimizeStartOrder(outLines, di, cInfo);
if (logOutput) {
for (auto &ol : outLines) {
gdibase.addString("", ol.first, ol.second);
}
}
int laststart = 0;
for (size_t k = 0; k<cInfo.size(); k++) {
const ClassInfo &ci = cInfo[k];
laststart = max(laststart, ci.firstStart + ci.nRunners*ci.interval);
}
//gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
// getAbsTime((laststart)*di.baseInterval + di.firstStart));
for (size_t k = 0; k < cInfo.size(); k++) {
ClassInfo &ci = cInfo[k];
vector<ClassDrawSpecification> specx;
specx.emplace_back(ci.classId, leg,
di.firstStart + di.baseInterval * ci.firstStart,
di.baseInterval * ci.interval, ci.nVacant, vp);
auto &gi = gInfo[cInfo[k].classId];
gi.firstStart = specx[0].firstStart + specx[0].interval * (specx[0].vacances + gi.rPerGroup[j]);
specx.back().startGroup = groupId;
drawList(specx, method, pairSize, drawType);
}
}
}
}
void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
DrawMethod method, int pairSize, DrawType drawType) {
@ -1020,6 +1708,11 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
continue;
if (it->tInTeam)
continue; // Cannot remove team runners
int k = clsId2Ix.find(it->getClassId(true))->second;
if (spec[k].startGroup > 0 &&
it->getStartGroup(true) > 0 &&
it->getStartGroup(true) != spec[k].startGroup)
continue;
if (it->getClubId() == VacantClubId) {
toRemove.push_back(it->getId());
}
@ -1034,7 +1727,7 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
if (!clsIdClearVac.count(spec[k].classID))
continue;
for (int i = 0; i < spec[k].vacances; i++) {
oe->addRunnerVacant(spec[k].classID);
oe->addRunnerVacant(spec[k].classID)->setStartGroup(spec[k].startGroup);
}
}
}
@ -1045,6 +1738,9 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
if (it->getStatus() == StatusNotCompetiting)
continue;
int ix = clsId2Ix[cid];
if (spec[ix].startGroup != 0 && it->getStartGroup(true) != spec[ix].startGroup)
continue;
if (it->legToRun() == spec[ix].leg || spec[ix].leg == -1) {
runners.push_back(&*it);
spec[ix].ntimes++;
@ -1174,7 +1870,13 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
minStartNo = min(minStartNo, runners[k]->getStartNo());
newStartNo.emplace_back(stimes[k], k);
}
/*
gdibase.dropLine();
gdibase.addString("", 1, L"Draw: " + oe->getClass(spec[0].classID)->getName());
for (unsigned k = 0; k < stimes.size(); k++) {
gdibase.addString("", 0, runners[k]->getCompleteIdentification() + L" " + runners[k]->getStartTimeS());
}
*/
sort(newStartNo.begin(), newStartNo.end());
//CurrentSortOrder = SortByStartTime;
//sort(runners.begin(), runners.end());
@ -1388,7 +2090,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
continue;
vector<ClassDrawSpecification> spec;
spec.emplace_back(it->getId(), 0, iFirstStart, 0, 0, vp);
oe->drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll);
drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll);
}
return;
}
@ -1469,6 +2171,8 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
if (maxRunners==0)
continue;
if (getStartGroups(true).size() == 0) {
int maxParallell = 15;
if (runnersStart < 100)
@ -1517,8 +2221,10 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
gdi.refreshFast();
gdi.dropLine();
vector<ClassInfo> cInfo;
optimizeStartOrder(gdi, di, cInfo);
vector<pair<int, wstring>> outLines;
optimizeStartOrder(outLines, di, cInfo);
for (auto &ol : outLines)
gdi.addString("", ol.first, ol.second);
int laststart = 0;
for (size_t k = 0; k < cInfo.size(); k++) {
@ -1552,6 +2258,25 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
drawn++;
}
}
else {
vector<ClassDrawSpecification> spec;
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
if (it->getStart() != start)
continue;
if (notDrawn.count(it->getId()) == 0)
continue; // Only not drawn classes
if (it->hasFreeStart())
continue;
//int classID, int leg, int firstStart, int interval, int vacances, oEvent::VacantPosition vp)
spec.emplace_back(it->getId(), 0, 0, 120, 1, VacantPosition::Mixed);
drawn++;
}
if (spec.size() == 0)
continue;
drawListStartGroups(spec, method, pairSize, DrawType::DrawAll);
}
}
// Classes that need completion
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {

View File

@ -26,6 +26,7 @@
struct ClassDrawSpecification {
int classID;
int startGroup = 0;
int leg;
mutable int firstStart;
mutable int interval;
@ -42,10 +43,12 @@ struct ClassDrawSpecification {
/** Struct with info to draw a class */
struct ClassInfo {
int classId;
int startGroupId;
pClass pc;
int firstStart;
int interval;
int fixedInterval;
int unique;
int courseId;

View File

@ -422,13 +422,13 @@ bool oEvent::calculateTeamResults(vector<const oTeam*> &teams, int leg, ResultTy
if (invalidClass) {
p = 0;
}
else if (it->_cachedStatus == StatusOK) {
else if (it->tmpCachedStatus == StatusOK) {
cPlace++;
if (it->_sortTime > cTime)
if (it->tmpSortTime > cTime)
vPlace = cPlace;
cTime = it->_sortTime;
cTime = it->tmpSortTime;
p = vPlace;
}
@ -443,8 +443,8 @@ bool oEvent::calculateTeamResults(vector<const oTeam*> &teams, int leg, ResultTy
else {
it->getTeamPlace(sleg).p.update(*this, p, tmpDefaultResult);
res.version = tmpDefaultResult ? -1 : dataRevision;
res.status = it->_cachedStatus;
res.time = it->_sortTime;
res.status = it->tmpCachedStatus;
res.time = it->tmpDefinedTime;
it->setComputedResult(sleg, res);
}
}

View File

@ -66,7 +66,7 @@ bool oFreePunch::Write(xmlparser &xml)
xml.write("Time", Time);
xml.write("Type", Type);
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.endTag();
return true;
@ -446,7 +446,7 @@ pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFi
punches.push_back(ofp);
pFreePunch fp=&punches.back();
fp->addToEvent();
fp->addToEvent(this, &ofp);
oFreePunch::rehashPunches(*this, card, fp);
insertIntoPunchHash(card, type, time);
@ -516,7 +516,7 @@ pFreePunch oEvent::addFreePunch(oFreePunch &fp) {
insertIntoPunchHash(fp.CardNo, fp.Type, fp.Time);
punches.push_back(fp);
pFreePunch fpz=&punches.back();
fpz->addToEvent();
fpz->addToEvent(this, &fp);
oFreePunch::rehashPunches(*this, fp.CardNo, fpz);
if (!fpz->existInDB() && HasDBConnection) {

View File

@ -86,6 +86,8 @@ public:
static void rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch);
static bool disableHashing;
void merge(const oBase &input) final;
oFreePunch(oEvent *poe, int card, int time, int type);
oFreePunch(oEvent *poe, int id);
virtual ~oFreePunch(void);

View File

@ -1587,9 +1587,9 @@ bool oEvent::addXMLCourse(const xmlobject &xcrs, bool addClasses, set<wstring> &
pc->setName(cname);
pc->setLength(len);
pc->importControls("", false);
pc->importControls("", true, false);
for (size_t i = 0; i<ctrlCode.size(); i++) {
if (ctrlCode[i]>30 && ctrlCode[i]<1000)
if (ctrlCode[i]>=30 && ctrlCode[i]<1024)
pc->addControl(ctrlCode[i]);
}
if (pc->getNumControls() + 1 == legLen.size())

View File

@ -98,6 +98,9 @@ public:
string codeString() const;
void appendCodeString(string &dst) const;
void merge(const oBase &input) override;
oPunch(oEvent *poe);
virtual ~oPunch();

View File

@ -368,7 +368,7 @@ bool oRunner::Write(xmlparser &xml)
xml.startTag("Runner");
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", sName);
xml.write("Start", startTime);
xml.write("Finish", FinishTime);

View File

@ -201,6 +201,8 @@ public:
bool preventRestart() const;
void preventRestart(bool state);
void merge(const oBase &input) override;
/** Call this method after doing something to just this
runner/team that changed the time/status etc, that effects
the result. May make a global evaluation of the class.
@ -658,9 +660,20 @@ protected:
bool isHiredCard(int card) const;
int tmpStartGroup = 0;
public:
static const shared_ptr<Table> &getTable(oEvent *oe);
int getStartGroup(bool useTmpStartGroup) const {
if (useTmpStartGroup && tmpStartGroup)
return tmpStartGroup;
return getDCI().getInt("StartGroup");
}
void setStartGroup(int sg) {
getDI().setInt("StartGroup", sg);
}
// Get the leg defineing parallel results for this runner (in a team)
int getParResultLeg() const;
@ -902,7 +915,7 @@ public:
void setNumShortening(int numShorten);
pCard getCard() const {return Card;}
int getCardId(){if (Card) return Card->Id; else return 0;}
int getCardId() const {if (Card) return Card->Id; else return 0;}
bool operator<(const oRunner &c) const;
bool static CompareCardNumber(const oRunner &a, const oRunner &b) { return a.cardNumber < b.cardNumber; }
@ -945,6 +958,8 @@ public:
/** Formats extra line for runner []-syntax, or if r is null, checks validity and throws on error.*/
static wstring formatExtraLine(pRunner r, const wstring &input);
void merge(const oBase &input) final;
virtual ~oRunner();
friend class MeosSQL;

View File

@ -69,7 +69,7 @@ bool oTeam::write(xmlparser &xml)
xml.startTag("Team");
xml.write("Id", Id);
xml.write("StartNo", StartNo);
xml.write("Updated", Modified.getStamp());
xml.write("Updated", getStamp());
xml.write("Name", sName);
xml.write("Start", startTime);
xml.write("Finish", FinishTime);
@ -814,10 +814,10 @@ bool oTeam::compareResult(const oTeam &a, const oTeam &b)
}
else return false;
}
else if (a._sortStatus!=b._sortStatus)
return a._sortStatus<b._sortStatus;
else if (a._sortTime!=b._sortTime)
return a._sortTime<b._sortTime;
else if (a.tmpSortStatus != b.tmpSortStatus)
return a.tmpSortStatus < b.tmpSortStatus;
else if (a.tmpSortTime != b.tmpSortTime)
return a.tmpSortTime < b.tmpSortTime;
const wstring &as = a.getBib();
const wstring &bs = b.getBib();
@ -844,10 +844,10 @@ bool oTeam::compareResultNoSno(const oTeam &a, const oTeam &b)
}
else return false;
}
else if (a._sortStatus != b._sortStatus)
return a._sortStatus<b._sortStatus;
else if (a._sortTime != b._sortTime)
return a._sortTime<b._sortTime;
else if (a.tmpSortStatus != b.tmpSortStatus)
return a.tmpSortStatus<b.tmpSortStatus;
else if (a.tmpSortTime != b.tmpSortTime)
return a.tmpSortTime<b.tmpSortTime;
return CompareString(LOCALE_USER_DEFAULT, 0,
a.sName.c_str(), a.sName.length(),

View File

@ -88,9 +88,11 @@ protected:
mutable vector<ComputedLegResult> tComputedResults;
mutable int _sortTime;
mutable int _sortStatus;
mutable RunnerStatus _cachedStatus;
void setTmpTime(int t) const { tmpSortTime = tmpDefinedTime = t; }
mutable int tmpSortTime;
mutable int tmpDefinedTime;
mutable int tmpSortStatus;
mutable RunnerStatus tmpCachedStatus;
mutable vector< vector< vector<int> > > resultCalculationCache;
@ -277,6 +279,8 @@ public:
void set(const xmlobject &xo);
bool write(xmlparser &xml);
void merge(const oBase &input) final;
oTeam(oEvent *poe, int id);
oTeam(oEvent *poe);
virtual ~oTeam(void);

View File

@ -161,7 +161,7 @@ pTeam oEvent::addTeam(const wstring &pname, int ClubId, int ClassId)
bibStartNoToRunnerTeam.clear();
Teams.push_back(t);
pTeam pt = &Teams.back();
pt->addToEvent();
pt->addToEvent(this, &t);
teamById[t.Id] = pt;
oe->updateTabs();
@ -184,7 +184,7 @@ pTeam oEvent::addTeam(const oTeam &t, bool autoAssignStartNo) {
Teams.push_back(t);
pTeam pt = &Teams.back();
pt->addToEvent();
pt->addToEvent(this, &t);
for (size_t i = 0; i < pt->Runners.size(); i++) {
if (pt->Runners[i]) {
@ -850,23 +850,24 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map<int, int>
leg = 0;
if (unsigned(leg) < Runners.size())
hasRunner = true;
_cachedStatus = StatusUnknown;
_sortStatus = 0;
_sortTime = getLegStartTime(leg);
if (_sortTime <= 0)
_sortStatus = 1;
tmpCachedStatus = StatusUnknown;
tmpSortStatus = 0;
setTmpTime(getLegStartTime(leg));
if (tmpSortTime <= 0)
tmpSortStatus = 1;
return;
}
else if (so == ClassPoints) {
bool totalResult = so == ClassTotalResult;
_sortTime = getRunningTime(true) - 7 * 24 * 3600 * getRogainingPoints(true, totalResult);
_cachedStatus = getLegStatus(-1, true, totalResult);
setTmpTime(getRunningTime(true));
tmpSortTime -= 7 * 24 * 3600 * getRogainingPoints(true, totalResult);
tmpCachedStatus = getLegStatus(-1, true, totalResult);
}
else if (so == ClassKnockoutTotalResult) {
hasRunner = true;
_cachedStatus = StatusUnknown;
_sortStatus = 0;
_sortTime = 0;
tmpCachedStatus = StatusUnknown;
tmpSortStatus = 0;
setTmpTime(0);
// Count number of races with results
int numResult = 0;
@ -880,15 +881,15 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map<int, int>
numResult++;
lastClassHeat = r->getDCI().getInt("Heat");
_cachedStatus = r->tStatus;
_sortTime = r->getRunningTime(false);
tmpCachedStatus = r->tStatus;
setTmpTime(r->getRunningTime(false));
}
}
if (lastClassHeat > 50 || lastClassHeat < 0)
lastClassHeat = 0;
unsigned rawStatus = _cachedStatus;
_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0] - (numResult * 100 + lastClassHeat) * 1000;
unsigned rawStatus = tmpCachedStatus;
tmpSortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0] - (numResult * 100 + lastClassHeat) * 1000;
return;
}
@ -926,36 +927,38 @@ void oTeam::fillInSortData(SortOrder so, int leg, bool linearLeg, map<int, int>
pRunner r = getRunner(lg);
if (r) {
if (so == ClassDefaultResult) {
_sortTime = r->getRunningTime(false);
_cachedStatus = r->getStatus();
setTmpTime(r->getRunningTime(false));
tmpCachedStatus = r->getStatus();
}
else {
_sortTime = r->getRunningTime(true);
_cachedStatus = r->getStatusComputed();
setTmpTime(r->getRunningTime(true));
tmpCachedStatus = r->getStatusComputed();
}
}
else {
_sortTime = 0;
_cachedStatus = StatusUnknown;
setTmpTime(0);
tmpCachedStatus = StatusUnknown;
}
}
else {
if (so == ClassDefaultResult) {
_sortTime = getLegRunningTime(lg, false, totalResult) + getNumShortening(lg) * 3600 * 24 * 10;
_cachedStatus = getLegStatus(lg, false, totalResult);
setTmpTime(getLegRunningTime(lg, false, totalResult));
tmpSortTime += getNumShortening(lg) * 3600 * 24 * 10;
tmpCachedStatus = getLegStatus(lg, false, totalResult);
}
else {
_sortTime = getLegRunningTime(lg, true, totalResult) + getNumShortening(lg) * 3600 * 24 * 10;
_cachedStatus = getLegStatus(lg, true, totalResult);
setTmpTime(getLegRunningTime(lg, true, totalResult));
tmpSortTime += getNumShortening(lg) * 3600 * 24 * 10;
tmpCachedStatus = getLegStatus(lg, true, totalResult);
}
// Ensure number of restarts has effect on final result
if (lg == lastIndex)
_sortTime += tNumRestarts * 24 * 3600;
tmpSortTime += tNumRestarts * 24 * 3600;
}
}
unsigned rawStatus = _cachedStatus;
_sortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0];
unsigned rawStatus = tmpCachedStatus;
tmpSortStatus = RunnerStatusOrderMap[rawStatus < 100u ? rawStatus : 0];
}
bool oEvent::sortTeams(SortOrder so, int leg, bool linearLeg) {

1473
code/oevent_transfer.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2501,3 +2501,29 @@ Lottat = Lottat
Sist = Sist
Fakturadatum = Fakturadatum
Youth Cup X = Ungdomscup X
Ny startgrupp = Ny startgrupp
Slut = Slut
Startgrupper = Startgrupper
help:startgroup = Startgrupper används för att styra lottningen. Deltagare i en viss grupp börjar starta vid startgruppens starttid
Tips: ställ in rätt tid innan du lägger till fler grupper = Tips: ställ in rätt tid innan du lägger till fler grupper
Familj = Familj
Startgrupp = Startgrupp
Slå ihop tävlingar = Slå ihop tävlingar
help:merge = Det är möjligt att slå ihop tävlingar och resultat, givet att de är grundade på samma uppsättning banor och kontroller. Olika grupper deltagare kan genomföra tävlingen vid olika tillfällen och sedan kan de olika tävlingarna slås ihop till en tävling med gemensam resultatlista. En annan möjlighet är att ha olika TC för olika klasser. Om det inte är möjliget att att sätt upp ett gemensamt nätverk, kan man med jämna mellanrum utbyta tävlingsfiler för att införliva ändringar.\n\n1. Förbered hela tävlingen.\n2. Spara en kopia och importera den på de utlokaliserade datorerna (eller lokala nätverken).\n3. För att överföra ändringar, exportera tävlingen från den utlokaliserade datorn (eller datorerna) och slå ihop den med denna funktion. Exportera sedan en kopia från huvuddatorn och gör motsvarande import på de utlokaliserade datorerna.\n4 Proceduren kan upprepas flera gånger för att kontinuerligt överföra resultaten. \n\n Observera: Om du gör ändringar i (t.ex.) samma deltagare på flera ställen, blir vissa av ändringarna överskrivna utan varning. Se till att varje utlokaliserat ställe endast ändrar i sin del av tävlingen.\n\nTips: Gör en överföring så snart de utlokaliserade tävlingarna är startade innan någon ändring utförts, för att testa att allt blivit rätt uppsatt.
Denna datakälla är aldrig tidigare infogad = Denna datakälla är aldrig tidigare infogad
Fel: Denna tävlingsversion är redan infogad = Fel: Denna tävlingsversion är redan infogad
Infoga version: X = Infoga version: X
Samma bastävling = Samma bastävling
Sammanslagning fungerar om samma uppsättning banor/kontroller används = Sammanslagning fungerar om samma uppsättning banor/kontroller används
Varning: Olika bastävlingar = Varning: Olika bastävlingar
Borttagna: X = Borttagna: X
Fel: En tävling kan inte slås ihop med sig själv = Fel: En tävling kan inte slås ihop med sig själv
Sammanfattning, uppdateradet poster = Sammanfattning, uppdateradet poster
Sammanslagning klar = Sammanslagning klar
Skapade lokal säkerhetskopia (X) innan sammanslagning = Skapade lokal säkerhetskopia (X) innan sammanslagning
Tillagda: X = Tillagda: X
Uppdaterade: X = Uppdaterade: X
Tjänstebeställningar (IOF XML) = Tjänstebeställningar (IOF XML)
Tjänster (IOF XML) = Tjänster (IOF XML)
Flytta deltagare från överfulla grupper = Flytta deltagare från överfulla grupper
Lotta med startgrupper = Lotta med startgrupper