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

@ -1211,7 +1211,7 @@ void RunnerDB::generateRunnerTableData(Table &table, oDBRunnerEntry *addEntry)
table.reserve(rdb.size()); table.reserve(rdb.size());
oRDB.resize(rdb.size(), oDBRunnerEntry(oe)); oRDB.resize(rdb.size(), oDBRunnerEntry(oe));
for (size_t k = 0; k<rdb.size(); k++){ for (size_t k = 0; k < rdb.size(); k++) {
if (!rdb[k].isRemoved()) { if (!rdb[k].isRemoved()) {
oRDB[k].init(this, k); oRDB[k].init(this, k);
oRDB[k].addTableRow(table); oRDB[k].addTableRow(table);

View File

@ -368,6 +368,7 @@ protected:
int getDISize() const {return 0;} int getDISize() const {return 0;}
void changedObject() {} void changedObject() {}
public: public:
void merge(const oBase &input) final {}
int getIndex() const {return index;} int getIndex() const {return index;}
void init(RunnerDB *db_, int index_) {db=db_, index=index_; Id = 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.popX();
gdi.fillRight(); gdi.fillRight();
gdi.addButton("AutomaticDraw", "Automatisk lottning", ClassesCB); 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("DrawAll", "Manuell lottning", ClassesCB).setExtra(1);
gdi.addButton("Simultaneous", "Gemensam start", ClassesCB); gdi.addButton("Simultaneous", "Gemensam start", ClassesCB);
@ -1268,7 +1274,7 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
EditChanged=false; EditChanged=false;
} }
else { else {
firstStart = gdi.getText("FirstStart"); firstStart = gdi.getText("FirstStart", true);
minInterval = gdi.getText("MinInterval"); minInterval = gdi.getText("MinInterval");
vacances = gdi.getText("Vacances"); vacances = gdi.getText("Vacances");
//pairwise = gdi.isChecked("Pairwise"); //pairwise = gdi.isChecked("Pairwise");
@ -1299,8 +1305,18 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
by = gdi.getHeight(); by = gdi.getHeight();
gdi.enableEditControls(true); gdi.enableEditControls(true);
} }
bool hasGroups = oe->getStartGroups(true).size() > 0;
loadReadyToDistribute(gdi, bx, by); 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") { else if (bi.id == "HelpDraw") {
@ -1363,10 +1379,62 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
} }
gdi.enableEditControls(false); 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); 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") { else if (bi.id == "LoadSettings") {
set<int> classes; set<int> classes;
gdi.getSelection("Classes", classes); gdi.getSelection("Classes", classes);
@ -1434,7 +1502,11 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
else if (bi.id == "DrawAdjust") { else if (bi.id == "DrawAdjust") {
readClassSettings(gdi); readClassSettings(gdi);
gdi.restore("ReadyToDistribute"); 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); showClassSettings(gdi);
} }
else if (bi.id == "DrawAllAdjust") { else if (bi.id == "DrawAllAdjust") {
@ -1447,10 +1519,12 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
oe->addAutoBib(); oe->addAutoBib();
loadPage(gdi); 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)) if (!checkClassSelected(gdi))
return false; return false;
bool withGroups = bi.id == "DoDrawGroups";
DWORD cid=ClassId; DWORD cid=ClassId;
pClass pc = oe->getClass(cid); pClass pc = oe->getClass(cid);
oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first); oEvent::DrawMethod method = oEvent::DrawMethod(gdi.getSelectedItem("Method").first);
@ -1517,8 +1591,10 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::MeOS) { if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::MeOS) {
vector<ClassDrawSpecification> spec; vector<ClassDrawSpecification> spec;
spec.emplace_back(cid, leg, t, interval, vacanses, vp); spec.emplace_back(cid, leg, t, interval, vacanses, vp);
if (withGroups)
oe->drawList(spec, method, pairSize, dtype); oe->drawListStartGroups(spec, method, pairSize, dtype);
else
oe->drawList(spec, method, pairSize, dtype);
} }
else if (method == oEvent::DrawMethod::Clumped) else if (method == oEvent::DrawMethod::Clumped)
oe->drawListClumped(cid, t, interval, vacanses); oe->drawListClumped(cid, t, interval, vacanses);
@ -1704,6 +1780,12 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
selectClass(gdi, pc->getId()); selectClass(gdi, pc->getId());
} }
} }
else if (bi.id == "StartGroups") {
loadStartGroupSettings(gdi, true);
}
else if (bi.id == "DrawStartGroups") {
drawStartGroups(gdi);
}
else if (bi.id=="Bibs") { else if (bi.id=="Bibs") {
save(gdi, true); save(gdi, true);
if (!checkClassSelected(gdi)) if (!checkClassSelected(gdi))
@ -3395,6 +3477,9 @@ bool TabClass::loadPage(gdioutput &gdi)
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::MultipleRaces)) if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::MultipleRaces))
func.push_back(ButtonData("QualificationFinal", "Kval/final-schema", false)); func.push_back(ButtonData("QualificationFinal", "Kval/final-schema", false));
if (showAdvanced)
func.push_back(ButtonData("StartGroups", "Startgrupper", true));
RECT funRect; RECT funRect;
funRect.right = gdi.getCX() - 7; funRect.right = gdi.getCX() - 7;
funRect.top = gdi.getCY() - 2; funRect.top = gdi.getCY() - 2;
@ -3411,14 +3496,20 @@ bool TabClass::loadPage(gdioutput &gdi)
int xlimit = gdi.getWidth() - button_w/2; int xlimit = gdi.getWidth() - button_w/2;
for (size_t k = 0; k < func.size(); k++) { 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); ButtonInfo &bi = gdi.addButton(func[k].id, func[k].label, ClassesCB);
if (!func[k].global) if (!func[k].global)
bi.isEdit(true); bi.isEdit(true);
funRect.left = max<int>(funRect.left, gdi.getCX() + 7); 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); gdi.dropLine(2.5);
@ -3701,8 +3792,11 @@ void TabClass::drawDialog(gdioutput &gdi, oEvent::DrawMethod method, const oClas
gdi.fillRight(); gdi.fillRight();
if (method != oEvent::DrawMethod::Simultaneous) if (method != oEvent::DrawMethod::Simultaneous) {
gdi.addButton("DoDraw", "Lotta klassen", ClassesCB, "Lotta om hela klassen"); 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 else
gdi.addButton("DoDraw", "Tilldela", ClassesCB, "Tilldela starttider"); gdi.addButton("DoDraw", "Tilldela", ClassesCB, "Tilldela starttider");
@ -4501,7 +4595,7 @@ void TabClass::readDrawInfo(gdioutput &gdi, DrawInfo &drawInfoOut) {
int minVacancy = gdi.getTextNo("VacancesMin"); int minVacancy = gdi.getTextNo("VacancesMin");
setDefaultVacant(gdi.getText("Vacances")); setDefaultVacant(gdi.getText("Vacances"));
double vacancyFactor = 0.01*_wtof(gdi.getText("Vacances").c_str()); 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 || drawInfoOut.changedVacancyInfo = drawInfoOut.maxVacancy != maxVacancy ||
@ -4522,9 +4616,9 @@ void TabClass::readDrawInfo(gdioutput &gdi, DrawInfo &drawInfoOut) {
drawInfoOut.coursesTogether = gdi.isChecked("CoursesTogether"); drawInfoOut.coursesTogether = gdi.isChecked("CoursesTogether");
drawInfoOut.minClassInterval = convertAbsoluteTimeMS(gdi.getText("MinInterval")); 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.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) { void TabClass::writeDrawInfo(gdioutput &gdi, const DrawInfo &drawInfoIn) {
@ -4746,15 +4840,26 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
const set<int> &clsId) { const set<int> &clsId) {
showClassSelection(gdi, bx, by, DrawClassesCB); showClassSelection(gdi, bx, by, DrawClassesCB);
bool hasGroups = oe->getStartGroups(true).size() > 0;
gdi.setSelection("Classes", clsId); 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.addString("", 1, "Grundinställningar");
gdi.pushX(); gdi.pushX();
gdi.fillRight(); gdi.fillRight();
gdi.addInput("FirstStart", firstStart, 10, 0, L"Första start:"); if (!hasGroups)
gdi.addInput("FirstStart", firstStart, 10, 0, L"Första start:");
gdi.addInput("nFields", L"10", 10, 0, L"Max parallellt startande:"); gdi.addInput("nFields", L"10", 10, 0, L"Max parallellt startande:");
gdi.popX(); gdi.popX();
gdi.dropLine(3); gdi.dropLine(3);
@ -4775,7 +4880,9 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
gdi.dropLine(4); gdi.dropLine(4);
gdi.fillDown(); gdi.fillDown();
gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) != 0); gdi.addCheckbox("AllowNeighbours", "Tillåt samma bana inom basintervall", 0, oe->getPropertyInt("DrawInterlace", 1) != 0);
gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false);
if (!hasGroups)
gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false);
gdi.dropLine(0.5); gdi.dropLine(0.5);
gdi.addString("", 1, "Startintervall"); gdi.addString("", 1, "Startintervall");
@ -4783,7 +4890,9 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
gdi.fillRight(); gdi.fillRight();
gdi.addInput("BaseInterval", L"1:00", 10, 0, L"Basintervall (min):"); gdi.addInput("BaseInterval", L"1:00", 10, 0, L"Basintervall (min):");
gdi.addInput("MinInterval", minInterval, 10, 0, L"Minsta intervall i klass:"); gdi.addInput("MinInterval", minInterval, 10, 0, L"Minsta intervall i klass:");
gdi.addInput("MaxInterval", minInterval, 10, 0, L"Största intervall i klass:");
if (!hasGroups)
gdi.addInput("MaxInterval", minInterval, 10, 0, L"Största intervall i klass:");
gdi.popX(); gdi.popX();
gdi.dropLine(4); gdi.dropLine(4);
@ -4797,7 +4906,24 @@ 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("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):"); gdi.addInput("VacancesMax", zeroVac ? L"0" : L"10", 6, 0, L"Max. vakanser (per klass):");
gdi.addInput("Extra", L"0%", 6, 0, L"Förväntad andel efteranmälda:");
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.dropLine(4);
gdi.fillDown(); gdi.fillDown();
@ -4903,3 +5029,136 @@ void TabClass::fillResultModules(gdioutput &gdi, pClass pc) {
gdi.selectItemByData("Module", current); gdi.selectItemByData("Module", current);
hideEditResultModule(gdi, 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,8 +163,13 @@ class TabClass :
vector<string> currentResultModuleTags; vector<string> currentResultModuleTags;
void fillResultModules(gdioutput &gdi, pClass pc); void fillResultModules(gdioutput &gdi, pClass pc);
shared_ptr<GuiHandler> startGroupHandler;
public: public:
void loadStartGroupSettings(gdioutput &gdi, bool reload);
void drawStartGroups(gdioutput &gdi);
void clearCompetitionData(); void clearCompetitionData();
void closeWindow(gdioutput &gdi); void closeWindow(gdioutput &gdi);

View File

@ -163,7 +163,7 @@ bool TabCompetition::importFile(HWND hWnd, gdioutput &gdi)
return false; return false;
gdi.setWaitCursor(true); gdi.setWaitCursor(true);
if (oe->open(fileName, true)) { if (oe->open(fileName, true, false)) {
gdi.setWindowTitle(oe->getTitleName()); gdi.setWindowTitle(oe->getTitleName());
resetSaveTimer(); resetSaveTimer();
return true; return true;
@ -562,7 +562,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
gdi.refresh(); gdi.refresh();
} }
else if (bi.id=="Test") { else if (bi.id=="Test") {
checkRentCards(gdi); //mergeCompetition(gdi);
} }
else if (bi.id=="Report") { else if (bi.id=="Report") {
gdi.clearPage(true); gdi.clearPage(true);
@ -892,7 +892,7 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
oEvent nextStage(gdi); oEvent nextStage(gdi);
if (!file.empty()) if (!file.empty())
success = nextStage.open(file.c_str(), false); success = nextStage.open(file.c_str(), false, false);
if (success) if (success)
success = nextStage.getNameId(0) == oe->getDCI().getString("PostEvent"); success = nextStage.getNameId(0) == oe->getDCI().getString("PostEvent");
@ -1871,8 +1871,11 @@ int TabCompetition::competitionCB(gdioutput &gdi, int type, void *data)
save(gdi, true); save(gdi, true);
exportFileAs(hWndMain, gdi); exportFileAs(hWndMain, gdi);
} }
else if (bi.id == "Merge") {
mergeCompetition(gdi);
}
else if (bi.id=="Duplicate") { else if (bi.id=="Duplicate") {
oe->duplicate(); oe->duplicate(L"");
gdi.alert("Skapade en lokal kopia av tävlingen."); gdi.alert("Skapade en lokal kopia av tävlingen.");
} }
else if (bi.id=="Import") { else if (bi.id=="Import") {
@ -2310,7 +2313,7 @@ int TabCompetition::restoreCB(gdioutput &gdi, int type, void *data) {
if (ti.id == "") { if (ti.id == "") {
wstring fi(bi.FullPath); wstring fi(bi.FullPath);
if (!oe->open(fi, false)) { if (!oe->open(fi, false, false)) {
gdi.alert("Kunde inte öppna tävlingen."); gdi.alert("Kunde inte öppna tävlingen.");
} }
else { else {
@ -2404,6 +2407,7 @@ void TabCompetition::loadAboutPage(gdioutput &gdi) const
"\n\nRussian Translation by Paul A. Kazakov and Albert Salihov" "\n\nRussian Translation by Paul A. Kazakov and Albert Salihov"
"\n\nOriginal French Translation by Jerome Monclard" "\n\nOriginal French Translation by Jerome Monclard"
"\n\nAdaption to French conditions and extended translation by Pierre Gaufillet" "\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\nCzech Translation by Marek Kustka"
"\n\nSpanish Translation by Manuel Pedre" "\n\nSpanish Translation by Manuel Pedre"
"\n\nHelp with English documentation: Torbjörn Wikström"); "\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", gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "SaveAs", "Säkerhetskopiera",
CompetitionCB, "", false, false); CompetitionCB, "", false, false);
gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "Merge", "Slå ihop tävlingar",
CompetitionCB, "", false, false);
if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Network)) { if (oe->getMeOSFeatures().hasFeature(MeOSFeatures::Network)) {
gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "ConnectMySQL", "Databasanslutning", gdi.addButton(gdi.getCX(), gdi.getCY(), bw, "ConnectMySQL", "Databasanslutning",
CompetitionCB, "", false, false); 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.addInput("FileName", L"", 48, 0, L"Anmälningar (IOF (xml) eller OE-CSV)");
gdi.dropLine(); gdi.dropLine();
gdi.addButton("BrowseEntries", "Bläddra...", CompetitionCB).setExtra(L"FileName"); 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.popX();
gdi.dropLine(3.2); gdi.dropLine(3.2);
@ -3467,23 +3486,22 @@ void TabCompetition::entryForm(gdioutput &gdi, bool isGuide) {
} }
FlowOperation TabCompetition::saveEntries(gdioutput &gdi, bool removeRemoved, 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()) if (filename[i].empty())
continue; continue;
gdi.addString("", 0, L"Behandlar: X#" + filename[i]); gdi.addString("", 0, L"Behandlar: X#" + filename[i]);
csvparser::CSV type = csvparser::iscsv(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 // Ranking
const wchar_t *File = filename[i].c_str(); 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) { void TabCompetition::mergeCompetition(gdioutput &gdi) {
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());
}
}
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.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.clearPage(false);
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);
}
}
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);
}
}
}
gdi.addString("", boldLarge, "Slå ihop tävlingar");
gdi.setRestorePoint("merge");
gdi.dropLine(); gdi.dropLine();
gdi.addButton("Cancel", "OK", CompetitionCB); 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(); gdi.refresh();
} }

View File

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

View File

@ -263,7 +263,7 @@ void TabCourse::save(gdioutput &gdi, int canSwitchViewMode) {
pc->setName(name); 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->setLength(gdi.getTextNo("Length"));
pc->getDI().setInt("Climb", gdi.getTextNo("Climb")); pc->getDI().setInt("Climb", gdi.getTextNo("Climb"));
pc->setNumberMaps(gdi.getTextNo("NumberMaps")); pc->setNumberMaps(gdi.getTextNo("NumberMaps"));

View File

@ -100,6 +100,16 @@ const string &TimeStamp::getStamp() const
return stampCode; 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 wstring TimeStamp::getStampString() const
{ {
__int64 ft64=(__int64(Time)+minYearConstant*365*24*3600)*10000000; __int64 ft64=(__int64(Time)+minYearConstant*365*24*3600)*10000000;
@ -113,6 +123,19 @@ wstring TimeStamp::getStampString() const
return bf; 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) void TimeStamp::setStamp(const string &s)
{ {
if (s.size()<14) if (s.size()<14)

View File

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

View File

@ -588,7 +588,7 @@ bool csvparser::importOCAD_CSV(oEvent &event, const wstring &file, bool addClass
} }
else { else {
// Reset control // Reset control
pc->importControls("", false); pc->importControls("", true, false);
pc->setLength(int(Length*1000)); 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? 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 RunnerRogainingPointGross = Rogaining points before reduction
Samlade poäng = Collected points Samlade poäng = Collected points
Tidsavdrag = Deduction Tidsavdrag = Reduction
X p = X p X p = X p
Bricka X används också av = Card X is also used by Bricka X används också av = Card X is also used by
reused card = reused card reused card = reused card
@ -2494,3 +2494,29 @@ Lottat = Drawn
Sist = Last Sist = Last
Fakturadatum = Fakturadatum Fakturadatum = Fakturadatum
Youth Cup X = Youth Cup X 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; 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 char *id) const;
BaseInfo &getBaseInfo(const wchar_t *id) const { BaseInfo &getBaseInfo(const wchar_t *id) const {
return getBaseInfo(narrow(id).c_str()); return getBaseInfo(narrow(id).c_str());

View File

@ -1064,6 +1064,10 @@ void IOF30Interface::readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &
xo.getObjects("PersonServiceRequest", req); xo.getObjects("PersonServiceRequest", req);
entrySourceId = 0; entrySourceId = 0;
auto &sg = oe.getStartGroups(true);
bool importStartGroups = sg.size() > 0;
for (auto &rx : req) { for (auto &rx : req) {
xmlobject xPers = rx.getObject("Person"); xmlobject xPers = rx.getObject("Person");
pRunner r = 0; pRunner r = 0;
@ -1075,9 +1079,13 @@ void IOF30Interface::readServiceRequestList(gdioutput &gdi, xmlobject &xo, int &
if (xreq) { if (xreq) {
auto xServ = xreq.getObject("Service"); auto xServ = xreq.getObject("Service");
string type; string type;
if (xServ && xServ.getObjectString("type", type)=="StartGroup") { if (xServ && (xServ.getObjectString("type", type)=="StartGroup" || importStartGroups)) {
int id = xServ.getObjectInt("Id"); int id = xServ.getObjectInt("Id");
r->getDI().setInt("Heat", 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); DI.setString("LateEntryFactor", lf);
} }
} }
oe.synchronize(); 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) { 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) { if (pc) {
pc->setName(name); pc->setName(name);
pc->setLength(len); pc->setLength(len);
pc->importControls("", false); pc->importControls("", true, false);
for (size_t i = 0; i<ctrlCode.size(); i++) { for (size_t i = 0; i<ctrlCode.size(); i++) {
pc->addControl(ctrlCode[i]->getId()); pc->addControl(ctrlCode[i]->getId());
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,22 +30,22 @@
//V35: abcdef //V35: abcdef
//V36: abcdef //V36: abcdef
int getMeosBuild() { int getMeosBuild() {
string revision("$Rev: 1014 $"); string revision("$Rev: 1031 $");
return 174 + atoi(revision.substr(5, string::npos).c_str()); return 174 + atoi(revision.substr(5, string::npos).c_str());
} }
//V37: a //V37: ab
wstring getMeosDate() { 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); return date.substr(7,10);
} }
wstring getBuildType() { wstring getBuildType() {
return L"RC2"; // No parantheses (...) return L"RC1"; // No parantheses (...)
} }
wstring getMajorVersion() { wstring getMajorVersion() {
return L"3.7"; return L"3.7SD";
} }
wstring getMeosFullVersion() { 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"Thomas Engberg, VK Uvarna");
supp.emplace_back(L"LG Axmalm, Sävedalens AIK"); supp.emplace_back(L"LG Axmalm, Sävedalens AIK");
supp.emplace_back(L"Falköpings AIK OK"); supp.emplace_back(L"Falköpings AIK OK");
developSupp.push_back(L"Karlskrona SOK");
reverse(supp.begin(), supp.end()); reverse(supp.begin(), supp.end());
} }

View File

@ -215,8 +215,33 @@ wstring oBase::getTimeStamp() const {
else return Modified.getStampString(); 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) { void oBase::changeId(int newId) {
Id = 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) { oDataInterface oBase::getDI(void) {

View File

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

View File

@ -70,7 +70,7 @@ bool oCard::Write(xmlparser &xml)
xml.write("Punches", getPunchString()); xml.write("Punches", getPunchString());
xml.write("ReadId", readId); xml.write("ReadId", readId);
xml.write("Id", Id); xml.write("Id", Id);
xml.write("Updated", Modified.getStamp()); xml.write("Updated", getStamp());
xml.endTag(); xml.endTag();
return true; 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) void oCard::setCardNo(int c)
{ {
if (cardNo!=c) if (cardNo!=c)
@ -520,8 +531,9 @@ pCard oEvent::addCard(const oCard &oc)
return 0; return 0;
Cards.push_back(oc); Cards.push_back(oc);
Cards.back().addToEvent(); Cards.back().tOwner = nullptr;
Cards.back().addToEvent(this, &oc);
qFreeCardId = max(oc.Id, qFreeCardId);
return &Cards.back(); return &Cards.back();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -64,7 +64,7 @@
#include "Table.h" #include "Table.h"
//Version of database //Version of database
int oEvent::dbVersion = 83; int oEvent::dbVersion = 84;
class RelativeTimeFormatter : public oDataDefiner { class RelativeTimeFormatter : public oDataDefiner {
string name; string name;
@ -343,6 +343,10 @@ oEvent::oEvent(gdioutput &gdi):oBase(0), gdibase(gdi)
oEventData->addVariableString("PayModes", "Betalsätt"); oEventData->addVariableString("PayModes", "Betalsätt");
oEventData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring"); oEventData->addVariableInt("TransferFlags", oDataContainer::oIS32, "Överföring");
oEventData->addVariableDate("InvoiceDate", "Fakturadatum"); 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); 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("Reference", oDataContainer::oIS32, "Referens", make_shared<oRunner::RunnerReference>());
oRunnerData->addVariableInt("NoRestart", oDataContainer::oIS8U, "Ej omstart", make_shared<DataBoolean>("NoRestart")); oRunnerData->addVariableInt("NoRestart", oDataContainer::oIS8U, "Ej omstart", make_shared<DataBoolean>("NoRestart"));
oRunnerData->addVariableString("InputResult", "Tidigare resultat", make_shared<DataHider>()); 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=new oDataContainer(oControl::dataSize);
oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering"); oControlData->addVariableInt("TimeAdjust", oDataContainer::oIS32, "Tidsjustering");
@ -673,7 +679,7 @@ pControl oEvent::addControl(const oControl &oc)
qFreeControlId = max (qFreeControlId, Id); qFreeControlId = max (qFreeControlId, Id);
Controls.push_back(oc); Controls.push_back(oc);
oe->Controls.back().addToEvent(); oe->Controls.back().addToEvent(this, &oc);
return &Controls.back(); return &Controls.back();
} }
@ -818,7 +824,7 @@ bool oEvent::writeCards(xmlparser &xml)
return true; return true;
} }
void oEvent::duplicate() { void oEvent::duplicate(const wstring &annotationIn) {
wchar_t file[260]; wchar_t file[260];
wchar_t filename[64]; wchar_t filename[64];
wchar_t nameid[64]; wchar_t nameid[64];
@ -853,18 +859,26 @@ void oEvent::duplicate() {
swprintf_s(filename, L"%d/%d %d:%02d", swprintf_s(filename, L"%d/%d %d:%02d",
st.wDay, st.wMonth, st.wHour, st.wMinute); st.wDay, st.wMonth, st.wHour, st.wMinute);
wstring anno = lang.tl(L"Kopia (X)#" + wstring(filename)); if (annotationIn.empty()) {
anno = oldAnno.empty() ? anno : oldAnno + L" " + anno; wstring anno = lang.tl(L"Kopia (X)#" + wstring(filename));
setAnnotation(anno); anno = oldAnno.empty() ? anno : oldAnno + L" " + anno;
setAnnotation(anno);
}
else {
setAnnotation(annotationIn);
}
wstring oldTag = getMergeTag();
try { try {
getMergeTag(true);
save(); save();
} }
catch(...) { catch(...) {
getDI().setString("MergeTag", oldTag);
// Restore in case of error // Restore in case of error
wcscpy_s(CurrentFile, oldFile); wcscpy_s(CurrentFile, oldFile);
currentNameId = oldId; currentNameId = oldId;
setAnnotation(oldAnno); setAnnotation(oldAnno);
synchronize(true);
throw; throw;
} }
@ -872,6 +886,8 @@ void oEvent::duplicate() {
wcscpy_s(CurrentFile, oldFile); wcscpy_s(CurrentFile, oldFile);
currentNameId = oldId; currentNameId = oldId;
setAnnotation(oldAnno); setAnnotation(oldAnno);
getDI().setString("MergeTag", oldTag);
synchronize(true);
} }
bool oEvent::save() bool oEvent::save()
@ -979,7 +995,7 @@ bool oEvent::save(const wstring &fileIn) {
xml.write("NameId", currentNameId); xml.write("NameId", currentNameId);
xml.write("Annotation", Annotation); xml.write("Annotation", Annotation);
xml.write("Id", Id); xml.write("Id", Id);
xml.write("Updated", Modified.getStamp()); xml.write("Updated", getStamp());
oEventData->write(this, xml); oEventData->write(this, xml);
@ -1082,13 +1098,17 @@ bool oEvent::open(int id)
if (it->Server.empty()) { if (it->Server.empty()) {
if (id == it->Id) { if (id == it->Id) {
CompetitionInfo ci=*it; //Take copy CompetitionInfo ci=*it; //Take copy
return open(ci.FullPath.c_str()); return open(ci.FullPath.c_str(), false, false);
} }
} }
else if (!it->Server.empty()) { else if (!it->Server.empty()) {
if (id == (10000000+it->Id)) { if (id == (10000000+it->Id)) {
CompetitionInfo ci=*it; //Take copy 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) if (!Import)
openFileLock->lockFile(file); openFileLock->lockFile(file);
@ -1159,6 +1178,13 @@ bool oEvent::open(const wstring &file, bool Import)
bool res = open(xml); bool res = open(xml);
if (res && !Import) if (res && !Import)
openFileLock->lockFile(file); openFileLock->lockFile(file);
getMergeTag(Import && !forMerge);
if (Import && !forMerge) {
getDI().setString("ImportStamp", gdibase.widen(getLastModified()));
}
return res; return res;
} }
@ -1265,7 +1291,7 @@ bool oEvent::open(const xmlparser &xml) {
c.Set(&*it); c.Set(&*it);
if (c.Id>0 && knownClass.count(c.Id) == 0) { if (c.Id>0 && knownClass.count(c.Id) == 0) {
Classes.push_back(c); Classes.push_back(c);
Classes.back().addToEvent(); Classes.back().addToEvent(this, &c);
knownClass.insert(c.Id); knownClass.insert(c.Id);
} }
} }
@ -1330,8 +1356,8 @@ bool oEvent::open(const xmlparser &xml) {
oTeam t(this, 0); oTeam t(this, 0);
t.set(*it); t.set(*it);
if (t.Id>0){ if (t.Id>0){
Teams.push_back(t); //Teams.push_back(t);
teamById[t.Id] = &Teams.back(); addTeam(t, false);
Teams.back().apply(ChangeType::Quiet, nullptr); Teams.back().apply(ChangeType::Quiet, nullptr);
} }
} }
@ -1635,10 +1661,10 @@ pCourse oEvent::addCourse(const oCourse &oc)
qFreeCourseId=max(qFreeCourseId, oc.getId()); qFreeCourseId=max(qFreeCourseId, oc.getId());
pCourse pc = &Courses.back(); pCourse pc = &Courses.back();
pc->addToEvent(); pc->addToEvent(this, &oc);
if (!pc->existInDB() && !pc->isImplicitlyCreated()) { if (HasDBConnection && !pc->existInDB() && !pc->isImplicitlyCreated()) {
pc->updateChanged(); pc->changed = true;
pc->synchronize(); pc->synchronize();
} }
courseIdIndex[oc.Id] = pc; courseIdIndex[oc.Id] = pc;
@ -1786,7 +1812,7 @@ pRunner oEvent::addRunner(const oRunner &r, bool updateStartNo) {
Runners.push_back(r); Runners.push_back(r);
pRunner pr=&Runners.back(); pRunner pr=&Runners.back();
pr->addToEvent(); pr->addToEvent(this, &r);
for (size_t i = 0; i < pr->multiRunner.size(); i++) { for (size_t i = 0; i < pr->multiRunner.size(); i++) {
if (pr->multiRunner[i]) { if (pr->multiRunner[i]) {
@ -2112,7 +2138,7 @@ pCard oEvent::allocateCard(pRunner owner)
c.tOwner = owner; c.tOwner = owner;
Cards.push_back(c); Cards.push_back(c);
pCard newCard = &Cards.back(); pCard newCard = &Cards.back();
newCard->addToEvent(); newCard->addToEvent(this, &c);
return newCard; return newCard;
} }
@ -3686,6 +3712,7 @@ void oEvent::newCompetition(const wstring &name)
openFileLock->unlockFile(); openFileLock->unlockFile();
clear(); clear();
SYSTEMTIME st; SYSTEMTIME st;
GetLocalTime(&st); GetLocalTime(&st);
@ -3695,6 +3722,9 @@ void oEvent::newCompetition(const wstring &name)
Name = name; Name = name;
oEventData->initData(this, sizeof(oData)); oEventData->initData(this, sizeof(oData));
if (!name.empty() && name != L"-")
getMergeTag();
getDI().setString("Organizer", getPropertyString("Organizer", L"")); getDI().setString("Organizer", getPropertyString("Organizer", L""));
getDI().setString("Street", getPropertyString("Street", L"")); getDI().setString("Street", getPropertyString("Street", L""));
getDI().setString("Address", getPropertyString("Address", 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 { MetaListContainer &oEvent::getListContainer() const {
if (!listContainer) if (!listContainer)
throw std::exception("Nullpointer exception"); throw std::exception("Nullpointer exception");
@ -6945,3 +6262,61 @@ void oEvent::setFlag(TransferFlags flag, bool onoff) {
cf = onoff ? (cf | flag) : (cf & (~flag)); cf = onoff ? (cf | flag) : (cf & (~flag));
getDI().setInt("TransferFlags", cf); 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; 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 // Temporarily disable recaluclate leader times
bool disableRecalculate; bool disableRecalculate;
public: 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 { enum TransferFlags {
FlagManualName = 1, FlagManualName = 1,
FlagManualDateTime = 2, FlagManualDateTime = 2,
@ -949,7 +963,7 @@ public:
bool exportOECSV(const wchar_t *file, int LanguageTypeIndex, bool includeSplits); bool exportOECSV(const wchar_t *file, int LanguageTypeIndex, bool includeSplits);
bool save(); bool save();
void duplicate(); void duplicate(const wstring &annotation);
void newCompetition(const wstring &Name); void newCompetition(const wstring &Name);
void clearListedCmp(); void clearListedCmp();
bool enumerateCompetitions(const wchar_t *path, const wchar_t *extension); bool enumerateCompetitions(const wchar_t *path, const wchar_t *extension);
@ -1053,11 +1067,15 @@ public:
pCard allocateCard(pRunner owner); pCard allocateCard(pRunner owner);
/** Optimize the start order based on drawInfo. Result in cInfo */ /** 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 loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const;
void drawRemaining(DrawMethod method, bool placeAfter); 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, void drawList(const vector<ClassDrawSpecification> &spec,
DrawMethod method, int pairSize, DrawType drawType); DrawMethod method, int pairSize, DrawType drawType);
void drawListClumped(int classID, int firstStart, int interval, int vacances); 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; void fillFees(gdioutput &gdi, const string &name, bool onlyDirect, bool withAuto) const;
wstring getAutoClassName() const; wstring getAutoClassName() const;
pClass addClass(const wstring &pname, int CourseId = 0, int classId = 0); 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. /** Get a class if it exists, or create it.
exactNames is a set of class names that must be matched exactly. 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 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); const vector< pair<wstring, size_t> > &fillControlTypes(vector< pair<wstring, size_t> > &out);
bool open(int id); 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 open(const xmlparser &xml);
bool save(const wstring &file); bool save(const wstring &file);
@ -1283,6 +1301,8 @@ protected:
/** type: 0 control, 1 start, 2 finish*/ /** type: 0 control, 1 start, 2 finish*/
bool addXMLControl(const xmlobject &xcontrol, int type); bool addXMLControl(const xmlobject &xcontrol, int type);
void merge(const oBase &src) final;
public: public:
const shared_ptr<GeneralResult> &getGeneralResult(const string &tag, wstring &sourceFileOut) const; const shared_ptr<GeneralResult> &getGeneralResult(const string &tag, wstring &sourceFileOut) const;
@ -1293,8 +1313,11 @@ public:
void getPredefinedClassTypes(map<wstring, ClassMetaType> &types) const; 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, wstring cloneCompetition(bool cloneRunners, bool cloneTimes,
bool cloneCourses, bool cloneResult, bool addToDate); bool cloneCourses, bool cloneResult, bool addToDate);
enum ChangedClassMethod { enum ChangedClassMethod {
ChangeClassVacant, ChangeClassVacant,
@ -1323,6 +1346,10 @@ public:
void transferListsAndSave(const oEvent &src); 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 { enum MultiStageType {
MultiStageNone = 0, MultiStageNone = 0,
MultiStageSeparateEntry = 1, MultiStageSeparateEntry = 1,

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,8 @@
#include "oEvent.h" #include "oEvent.h"
struct ClassDrawSpecification { struct ClassDrawSpecification {
int classID; int classID;
int startGroup = 0;
int leg; int leg;
mutable int firstStart; mutable int firstStart;
mutable int interval; mutable int interval;
@ -42,10 +43,12 @@ struct ClassDrawSpecification {
/** Struct with info to draw a class */ /** Struct with info to draw a class */
struct ClassInfo { struct ClassInfo {
int classId; int classId;
int startGroupId;
pClass pc; pClass pc;
int firstStart; int firstStart;
int interval; int interval;
int fixedInterval;
int unique; int unique;
int courseId; int courseId;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -201,6 +201,8 @@ public:
bool preventRestart() const; bool preventRestart() const;
void preventRestart(bool state); void preventRestart(bool state);
void merge(const oBase &input) override;
/** Call this method after doing something to just this /** Call this method after doing something to just this
runner/team that changed the time/status etc, that effects runner/team that changed the time/status etc, that effects
the result. May make a global evaluation of the class. the result. May make a global evaluation of the class.
@ -658,9 +660,20 @@ protected:
bool isHiredCard(int card) const; bool isHiredCard(int card) const;
int tmpStartGroup = 0;
public: public:
static const shared_ptr<Table> &getTable(oEvent *oe); 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) // Get the leg defineing parallel results for this runner (in a team)
int getParResultLeg() const; int getParResultLeg() const;
@ -902,7 +915,7 @@ public:
void setNumShortening(int numShorten); void setNumShortening(int numShorten);
pCard getCard() const {return Card;} 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 operator<(const oRunner &c) const;
bool static CompareCardNumber(const oRunner &a, const oRunner &b) { return a.cardNumber < b.cardNumber; } 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.*/ /** 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); static wstring formatExtraLine(pRunner r, const wstring &input);
void merge(const oBase &input) final;
virtual ~oRunner(); virtual ~oRunner();
friend class MeosSQL; friend class MeosSQL;

View File

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

View File

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

View File

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