MeOS version 3.7SD.1205 RC1
This commit is contained in:
parent
b79eec9686
commit
5e242c81c0
@ -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);
|
||||||
|
|||||||
@ -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;}
|
||||||
|
|||||||
@ -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,9 +1305,19 @@ 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;
|
||||||
|
|
||||||
|
if (!hasGroups) {
|
||||||
loadReadyToDistribute(gdi, bx, by);
|
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") {
|
||||||
|
|
||||||
gdioutput *gdi_new = getExtraWindow("help", true);
|
gdioutput *gdi_new = getExtraWindow("help", true);
|
||||||
@ -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,7 +1591,9 @@ int TabClass::classCB(gdioutput &gdi, int type, void *data)
|
|||||||
if (method == oEvent::DrawMethod::Random || method == oEvent::DrawMethod::SOFT || method == oEvent::DrawMethod::MeOS) {
|
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->drawListStartGroups(spec, method, pairSize, dtype);
|
||||||
|
else
|
||||||
oe->drawList(spec, method, pairSize, dtype);
|
oe->drawList(spec, method, pairSize, dtype);
|
||||||
}
|
}
|
||||||
else if (method == oEvent::DrawMethod::Clumped)
|
else if (method == oEvent::DrawMethod::Clumped)
|
||||||
@ -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,14 +4840,25 @@ 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();
|
||||||
|
|
||||||
|
if (!hasGroups)
|
||||||
gdi.addInput("FirstStart", firstStart, 10, 0, L"Första start:");
|
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();
|
||||||
@ -4775,6 +4880,8 @@ 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);
|
||||||
|
|
||||||
|
if (!hasGroups)
|
||||||
gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false);
|
gdi.addCheckbox("CoursesTogether", "Lotta klasser med samma bana gemensamt", 0, false);
|
||||||
|
|
||||||
gdi.dropLine(0.5);
|
gdi.dropLine(0.5);
|
||||||
@ -4783,6 +4890,8 @@ 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:");
|
||||||
|
|
||||||
|
if (!hasGroups)
|
||||||
gdi.addInput("MaxInterval", minInterval, 10, 0, L"Största intervall i klass:");
|
gdi.addInput("MaxInterval", minInterval, 10, 0, L"Största intervall i klass:");
|
||||||
|
|
||||||
gdi.popX();
|
gdi.popX();
|
||||||
@ -4797,8 +4906,25 @@ void TabClass::loadBasicDrawSetup(gdioutput &gdi, int &bx, int &by, const wstrin
|
|||||||
|
|
||||||
gdi.addInput("VacancesMin", zeroVac ? L"0" : L"1", 6, 0, L"Min. vakanser (per klass):");
|
gdi.addInput("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):");
|
||||||
|
|
||||||
|
if (!hasGroups)
|
||||||
gdi.addInput("Extra", L"0%", 6, 0, L"Förväntad andel efteranmälda:");
|
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();
|
||||||
gdi.popX();
|
gdi.popX();
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
|||||||
@ -163,7 +163,12 @@ 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();
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
gdi.clearPage(false);
|
|
||||||
|
|
||||||
wstring fn = gdi.browseForOpen({ make_pair(L"csv", L"*.csv") }, L"csv");
|
class MergeHandler : public GuiHandler {
|
||||||
if (!fn.empty()) {
|
TabCompetition *tc;
|
||||||
csvparser csv;
|
public:
|
||||||
list<vector<wstring>> data;
|
|
||||||
csv.parse(fn, data);
|
MergeHandler(TabCompetition *tc) : tc(tc) {}
|
||||||
set<int> rentCards;
|
|
||||||
for (auto &c : data) {
|
void handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) final {
|
||||||
if (c.size() > 0) {
|
if (type == GuiEventType::GUI_BUTTON) {
|
||||||
int cn = _wtoi(c[0].c_str());
|
ButtonInfo bi = dynamic_cast<ButtonInfo &>(info);
|
||||||
rentCards.insert(cn);
|
|
||||||
|
if (bi.id == "Cancel") {
|
||||||
|
if (tc->mergeEvent)
|
||||||
|
tc->mergeEvent->clear();
|
||||||
|
tc->loadPage(gdi);
|
||||||
|
}
|
||||||
|
else if (bi.id == "Merge") {
|
||||||
|
if (!tc->mergeEvent)
|
||||||
|
return;
|
||||||
|
int numAdd, numRemove, numUpdate;
|
||||||
|
|
||||||
|
wstring anno = lang.tl(L"Kopia (X)#MRG " + wstring(getLocalTime()));
|
||||||
|
tc->oe->duplicate(anno);
|
||||||
|
|
||||||
|
tc->oe->merge(*tc->mergeEvent, numAdd, numRemove, numUpdate);
|
||||||
|
|
||||||
|
|
||||||
|
gdi.clearPage(true);
|
||||||
|
gdi.addString("", fontMediumPlus, "Sammanslagning klar.");
|
||||||
|
gdi.dropLine();
|
||||||
|
|
||||||
|
gdi.addString("", 1, "Sammanfattning, uppdateradet poster");
|
||||||
|
gdi.addString("", 0, "Tillagda: X#" + itos(numAdd));
|
||||||
|
gdi.addString("", 0, "Uppdaterade: X#" + itos(numUpdate));
|
||||||
|
gdi.addString("", 0, "Borttagna: X#" + itos(numRemove));
|
||||||
|
|
||||||
|
gdi.dropLine();
|
||||||
|
gdi.addString("", 0, L"Skapade lokal säkerhetskopia (X) innan sammanslagning#" + anno);
|
||||||
|
|
||||||
|
tc->mergeEvent->clear();
|
||||||
|
|
||||||
|
gdi.dropLine(3);
|
||||||
|
gdi.addButton("Cancel", "Återgå").setHandler(this);
|
||||||
|
gdi.refresh();
|
||||||
|
}
|
||||||
|
else if (bi.id == "Browse") {
|
||||||
|
wstring fn = gdi.browseForOpen({ make_pair(L"xml", L"*.xml") }, L"xml");
|
||||||
|
if (fn.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
tc->mergeFile = fn;
|
||||||
|
gdi.setText("File", fn);
|
||||||
|
gdi.enableInput("Read");
|
||||||
|
}
|
||||||
|
else if (bi.id == "Read") {
|
||||||
|
tc->mergeEvent = make_shared<oEvent>(gdi);
|
||||||
|
|
||||||
|
if (!tc->mergeEvent->open(tc->mergeFile, true, true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
gdi.restore("merge", false);
|
||||||
|
gdi.fillDown();
|
||||||
|
gdi.addStringUT(1, tc->mergeEvent->getName());
|
||||||
|
|
||||||
|
gdi.dropLine();
|
||||||
|
bool error = false;
|
||||||
|
if (tc->mergeEvent->getNameId(0) == tc->oe->getNameId(0)) {
|
||||||
|
if (tc->mergeEvent->getMergeTag() == tc->oe->getMergeTag()) {
|
||||||
|
gdi.addString("", 1, "Fel: En tävling kan inte slås ihop med sig själv.").setColor(colorRed);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gdi.addString("", 1, "Samma bastävling").setColor(colorDarkGreen);
|
||||||
|
wstring info = tc->oe->getMergeInfo(tc->mergeEvent->getMergeTag());
|
||||||
|
string mod = tc->mergeEvent->getLastModified();
|
||||||
|
|
||||||
|
if (info.empty()) {
|
||||||
|
gdi.addString("", 0, "Denna datakälla är aldrig tidigare infogad");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string merged = gdi.narrow(info);
|
||||||
|
|
||||||
|
if (mod <= merged) {
|
||||||
|
gdi.addString("", 1, "Fel: Denna tävlingsversion är redan infogad.").setColor(colorRed);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TimeStamp ts;
|
||||||
|
ts.setStamp(merged);
|
||||||
|
gdi.addString("", 0, "Tidigare infogad version: X#" + ts.getStampStringN());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<pRunner> runners;
|
if (!error) {
|
||||||
oe->getRunners(0, 0, runners);
|
TimeStamp ts;
|
||||||
int bcf = oe->getBaseCardFee();
|
ts.setStamp(mod);
|
||||||
for (pRunner r : runners) {
|
gdi.addString("", 0, "Infoga version: X#" + ts.getStampStringN());
|
||||||
if (rentCards.count(r->getCardNo()) && r->getDCI().getInt("CardFee") == 0) {
|
|
||||||
gdi.addStringUT(0, r->getCompleteIdentification());
|
|
||||||
r->getDI().setInt("CardFee", bcf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.dropLine();
|
||||||
gdi.addButton("Cancel", "OK", CompetitionCB);
|
|
||||||
|
gdi.pushX();
|
||||||
|
gdi.fillRight();
|
||||||
|
if (!error) {
|
||||||
|
gdi.addString("", 1, "Klasser: ");
|
||||||
|
|
||||||
|
vector<pClass> cls;
|
||||||
|
tc->mergeEvent->getClasses(cls, false);
|
||||||
|
int xw, yw;
|
||||||
|
gdi.getTargetDimension(xw, yw);
|
||||||
|
int limit = (xw * 2) / 3;
|
||||||
|
|
||||||
|
for (pClass c : cls) {
|
||||||
|
gdi.addStringUT(0, c->getName());
|
||||||
|
if (gdi.getCX() >= limit) {
|
||||||
|
gdi.popX();
|
||||||
|
gdi.dropLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gdi.fillDown();
|
||||||
|
gdi.popX();
|
||||||
|
gdi.dropLine();
|
||||||
|
|
||||||
|
gdi.addString("", 1, "Antal deltagare: X#" + itos(tc->mergeEvent->getNumRunners()));
|
||||||
|
}
|
||||||
|
|
||||||
|
gdi.dropLine();
|
||||||
|
gdi.fillRight();
|
||||||
|
if (!error)
|
||||||
|
gdi.addButton("Merge", "Slå ihop").setHandler(tc->mergeHandler.get());
|
||||||
|
gdi.addButton("Cancel", "Avbryt").setHandler(tc->mergeHandler.get());
|
||||||
|
|
||||||
|
gdi.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mergeHandler = make_shared<MergeHandler>(this);
|
||||||
|
|
||||||
|
gdi.clearPage(false);
|
||||||
|
|
||||||
|
|
||||||
|
gdi.addString("", boldLarge, "Slå ihop tävlingar");
|
||||||
|
gdi.setRestorePoint("merge");
|
||||||
|
gdi.dropLine();
|
||||||
|
gdi.addString("", 10, "help:merge");
|
||||||
|
gdi.dropLine();
|
||||||
|
|
||||||
|
gdi.pushX();
|
||||||
|
gdi.fillRight();
|
||||||
|
gdi.addInput("File", mergeFile, 32, nullptr, L"Tävling:");
|
||||||
|
gdi.dropLine(0.8);
|
||||||
|
gdi.addButton("Browse", "Bläddra...").setHandler(mergeHandler.get());
|
||||||
|
|
||||||
|
gdi.dropLine(4);
|
||||||
|
gdi.popX();
|
||||||
|
gdi.fillRight();
|
||||||
|
gdi.addButton("Read", "Fortsätt").setHandler(mergeHandler.get());
|
||||||
|
gdi.addButton("Cancel", "Avbryt").setHandler(mergeHandler.get());
|
||||||
|
gdi.setInputStatus("Read", !mergeFile.empty());
|
||||||
|
gdi.refresh();
|
||||||
|
|
||||||
gdi.refresh();
|
gdi.refresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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"));
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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;}
|
||||||
|
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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");
|
||||||
|
if (!importStartGroups)
|
||||||
r->getDI().setInt("Heat", id);
|
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());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -755,11 +755,12 @@ 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");
|
con.query().exec("DELETE FROM oCard");
|
||||||
{
|
{
|
||||||
list<oCard>::iterator it=oe->Cards.begin();
|
list<oCard>::iterator it = oe->Cards.begin();
|
||||||
while(it!=oe->Cards.end()){
|
while (it != oe->Cards.end()) {
|
||||||
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
||||||
return opStatusFail;
|
return opStatusFail;
|
||||||
++it;
|
++it;
|
||||||
@ -768,8 +769,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
|
|
||||||
con.query().exec("DELETE FROM oClub");
|
con.query().exec("DELETE FROM oClub");
|
||||||
{
|
{
|
||||||
list<oClub>::iterator it=oe->Clubs.begin();
|
list<oClub>::iterator it = oe->Clubs.begin();
|
||||||
while(it!=oe->Clubs.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;
|
||||||
@ -777,8 +778,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
}
|
}
|
||||||
con.query().exec("DELETE FROM oControl");
|
con.query().exec("DELETE FROM oControl");
|
||||||
{
|
{
|
||||||
list<oControl>::iterator it=oe->Controls.begin();
|
list<oControl>::iterator it = oe->Controls.begin();
|
||||||
while(it!=oe->Controls.end()){
|
while (it != oe->Controls.end()) {
|
||||||
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
||||||
return opStatusFail;
|
return opStatusFail;
|
||||||
++it;
|
++it;
|
||||||
@ -786,8 +787,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
}
|
}
|
||||||
con.query().exec("DELETE FROM oCourse");
|
con.query().exec("DELETE FROM oCourse");
|
||||||
{
|
{
|
||||||
list<oCourse>::iterator it=oe->Courses.begin();
|
list<oCourse>::iterator it = oe->Courses.begin();
|
||||||
while(it!=oe->Courses.end()){
|
while (it != oe->Courses.end()) {
|
||||||
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
||||||
return opStatusFail;
|
return opStatusFail;
|
||||||
++it;
|
++it;
|
||||||
@ -795,8 +796,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
}
|
}
|
||||||
con.query().exec("DELETE FROM oClass");
|
con.query().exec("DELETE FROM oClass");
|
||||||
{
|
{
|
||||||
list<oClass>::iterator it=oe->Classes.begin();
|
list<oClass>::iterator it = oe->Classes.begin();
|
||||||
while(it!=oe->Classes.end()){
|
while (it != oe->Classes.end()) {
|
||||||
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
||||||
return opStatusFail;
|
return opStatusFail;
|
||||||
++it;
|
++it;
|
||||||
@ -804,8 +805,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
}
|
}
|
||||||
con.query().exec("DELETE FROM oRunner");
|
con.query().exec("DELETE FROM oRunner");
|
||||||
{
|
{
|
||||||
list<oRunner>::iterator it=oe->Runners.begin();
|
list<oRunner>::iterator it = oe->Runners.begin();
|
||||||
while(it!=oe->Runners.end()){
|
while (it != oe->Runners.end()) {
|
||||||
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
if (!it->isRemoved() && syncUpdate(&*it, true) == opStatusFail)
|
||||||
return opStatusFail;
|
return opStatusFail;
|
||||||
++it;
|
++it;
|
||||||
@ -814,8 +815,8 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
|
|
||||||
con.query().exec("DELETE FROM oTeam");
|
con.query().exec("DELETE FROM oTeam");
|
||||||
{
|
{
|
||||||
list<oTeam>::iterator it=oe->Teams.begin();
|
list<oTeam>::iterator it = oe->Teams.begin();
|
||||||
while(it!=oe->Teams.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;
|
||||||
@ -824,13 +825,19 @@ OpFailStatus MeosSQL::SyncUpdate(oEvent *oe)
|
|||||||
|
|
||||||
con.query().exec("DELETE FROM oPunch");
|
con.query().exec("DELETE FROM oPunch");
|
||||||
{
|
{
|
||||||
list<oFreePunch>::iterator it=oe->punches.begin();
|
list<oFreePunch>::iterator it = oe->punches.begin();
|
||||||
while(it!=oe->punches.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 (...) {
|
||||||
|
writeTime = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
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()) {
|
||||||
|
if (ob->counter != 0)
|
||||||
return " AND Counter!=" + itos(ob->counter);
|
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,6 +2815,9 @@ 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();
|
||||||
|
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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,6 +325,7 @@ bool oCourse::importControls(const string &ctrls, bool updateLegLengths) {
|
|||||||
changed |= oldC[k] != Controls[k]->getId();
|
changed |= oldC[k] != Controls[k]->getId();
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
if (setChanged)
|
||||||
updateChanged();
|
updateChanged();
|
||||||
|
|
||||||
oe->punchIndex.clear();
|
oe->punchIndex.clear();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
835
code/oEvent.cpp
835
code/oEvent.cpp
@ -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);
|
||||||
|
|
||||||
|
if (annotationIn.empty()) {
|
||||||
wstring anno = lang.tl(L"Kopia (X)#" + wstring(filename));
|
wstring anno = lang.tl(L"Kopia (X)#" + wstring(filename));
|
||||||
anno = oldAnno.empty() ? anno : oldAnno + L" " + anno;
|
anno = oldAnno.empty() ? anno : oldAnno + L" " + anno;
|
||||||
setAnnotation(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> ¬Transfered,
|
|
||||||
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> ¬Transfered,
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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,6 +1313,9 @@ 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);
|
||||||
|
|
||||||
@ -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,
|
||||||
|
|||||||
@ -27,6 +27,8 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
#include "oEvent.h"
|
#include "oEvent.h"
|
||||||
#include "gdioutput.h"
|
#include "gdioutput.h"
|
||||||
@ -422,7 +424,6 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DrawOptimAlgo {
|
class DrawOptimAlgo {
|
||||||
private:
|
private:
|
||||||
oEvent * oe;
|
oEvent * oe;
|
||||||
@ -503,7 +504,17 @@ private:
|
|||||||
if (!drawClass)
|
if (!drawClass)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int nr = c_it->getNumRunners(true, true, true);
|
int nr = 0;
|
||||||
|
if (ci.startGroupId == 0)
|
||||||
|
nr = c_it->getNumRunners(true, true, true);
|
||||||
|
else {
|
||||||
|
vector<pRunner> cr;
|
||||||
|
oe->getRunners(c_it->getId(), 0, cr, false);
|
||||||
|
for (pRunner r : cr)
|
||||||
|
if (r->getStartGroup(true) == ci.startGroupId)
|
||||||
|
nr++;
|
||||||
|
}
|
||||||
|
|
||||||
if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) {
|
if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) {
|
||||||
// Auto initialize
|
// Auto initialize
|
||||||
int nVacancies = int(nr * di.vacancyFactor + 0.5);
|
int nVacancies = int(nr * di.vacancyFactor + 0.5);
|
||||||
@ -623,9 +634,11 @@ public:
|
|||||||
di.baseInterval = 1;
|
di.baseInterval = 1;
|
||||||
di.minClassInterval = 0;
|
di.minClassInterval = 0;
|
||||||
}
|
}
|
||||||
|
int startGroup = 0;
|
||||||
// Calculate an estimated maximal class intervall
|
// Calculate an estimated maximal class intervall
|
||||||
for (size_t k = 0; k < cInfo.size(); k++) {
|
for (size_t k = 0; k < cInfo.size(); k++) {
|
||||||
|
if (cInfo[k].startGroupId != 0)
|
||||||
|
startGroup = cInfo[k].startGroupId; // Need to be constant
|
||||||
int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval);
|
int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval);
|
||||||
|
|
||||||
if (quotient*di.baseInterval > di.maxClassInterval)
|
if (quotient*di.baseInterval > di.maxClassInterval)
|
||||||
@ -645,13 +658,18 @@ public:
|
|||||||
|
|
||||||
// Fill up with non-drawn classes
|
// Fill up with non-drawn classes
|
||||||
for (auto &it : Runners) {
|
for (auto &it : Runners) {
|
||||||
|
if (it->isRemoved())
|
||||||
|
continue;
|
||||||
int st = it->getStartTime();
|
int st = it->getStartTime();
|
||||||
int relSt = st - di.firstStart;
|
int relSt = st - di.firstStart;
|
||||||
int relPos = relSt / di.baseInterval;
|
int relPos = relSt / di.baseInterval;
|
||||||
|
|
||||||
if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) {
|
if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) {
|
||||||
if (otherClasses.count(it->getClassId(false)) == 0)
|
int cid = it->getClassId(true);
|
||||||
|
if (otherClasses.count(cid) == 0) {
|
||||||
|
if (startGroup == 0 || startGroup == it->getStartGroup(true))
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
pClass cls = it->getClassRef(true);
|
pClass cls = it->getClassRef(true);
|
||||||
if (cls) {
|
if (cls) {
|
||||||
if (!di.startName.empty() && cls->getStart() != di.startName)
|
if (!di.startName.empty() && cls->getStart() != di.startName)
|
||||||
@ -661,7 +679,24 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassInfo &ci = otherClasses[it->getClassId(false)];
|
int unique, courseId;
|
||||||
|
auto res = otherClasses.find(cid);
|
||||||
|
if (res != otherClasses.end()) {
|
||||||
|
unique = res->second.unique;
|
||||||
|
courseId = res->second.courseId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = di.classes.find(cid);
|
||||||
|
if (res != di.classes.end()) {
|
||||||
|
unique = res->second.unique;
|
||||||
|
courseId = res->second.courseId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int unique = 12345678;
|
||||||
|
int courseId = it->getCourse(false) ? it->getCourse(false)->getId() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (k == StartField.size()) {
|
if (k == StartField.size()) {
|
||||||
@ -669,8 +704,8 @@ public:
|
|||||||
StartField.back().resize(3000);
|
StartField.back().resize(3000);
|
||||||
}
|
}
|
||||||
if (StartField[k][relPos].first == 0) {
|
if (StartField[k][relPos].first == 0) {
|
||||||
StartField[k][relPos].first = ci.unique;
|
StartField[k][relPos].first = unique;
|
||||||
StartField[k][relPos].second = ci.courseId;
|
StartField[k][relPos].second = courseId;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
k++;
|
k++;
|
||||||
@ -703,8 +738,14 @@ public:
|
|||||||
int minPos = 1000000;
|
int minPos = 1000000;
|
||||||
int minEndPos = 1000000;
|
int minEndPos = 1000000;
|
||||||
int minInterval = cInfo[k].interval;
|
int minInterval = cInfo[k].interval;
|
||||||
|
int startV = di.minClassInterval / di.baseInterval;
|
||||||
|
int endV = cInfo[k].interval;
|
||||||
|
|
||||||
for (int i = di.minClassInterval / di.baseInterval; i <= cInfo[k].interval; i++) {
|
if (cInfo[k].fixedInterval != 0) {
|
||||||
|
endV = startV = cInfo[k].fixedInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startV; i <= endV; i++) {
|
||||||
|
|
||||||
int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3);
|
int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3);
|
||||||
startpos = 0;
|
startpos = 0;
|
||||||
@ -744,7 +785,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo> &cInfo)
|
void oEvent::optimizeStartOrder(vector<pair<int, wstring>> &outLines, DrawInfo &di, vector<ClassInfo> &cInfo)
|
||||||
{
|
{
|
||||||
if (Classes.size()==0)
|
if (Classes.size()==0)
|
||||||
return;
|
return;
|
||||||
@ -823,11 +864,11 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
|
|||||||
vector< vector<pair<int, int> > > startField(di.nFields);
|
vector< vector<pair<int, int> > > startField(di.nFields);
|
||||||
drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator);
|
drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator);
|
||||||
|
|
||||||
gdi.addString("", 0, "Identifierar X unika inledningar på banorna.#" + itos(di.numDistinctInit));
|
outLines.emplace_back(0, L"Identifierar X unika inledningar på banorna.#" + itow(di.numDistinctInit));
|
||||||
gdi.addString("", 0, "Största gruppen med samma inledning har X platser.#" + itos(di.numRunnerSameInitMax));
|
outLines.emplace_back(0, L"Största gruppen med samma inledning har X platser.#" + itow(di.numRunnerSameInitMax));
|
||||||
gdi.addString("", 0, "Antal löpare på vanligaste banan X.#" + itos(di.numRunnerSameCourseMax));
|
outLines.emplace_back(0, L"Antal löpare på vanligaste banan X.#" + itow(di.numRunnerSameCourseMax));
|
||||||
gdi.addString("", 0, "Kortast teoretiska startdjup utan krockar är X minuter.#" + itos(di.minimalStartDepth/60));
|
outLines.emplace_back(0, L"Kortast teoretiska startdjup utan krockar är X minuter.#" + itow(di.minimalStartDepth/60));
|
||||||
gdi.dropLine();
|
outLines.emplace_back(0, L"");
|
||||||
//Find last starter
|
//Find last starter
|
||||||
int last = opt.last;
|
int last = opt.last;
|
||||||
|
|
||||||
@ -837,17 +878,17 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
|
|||||||
laststart=max(laststart, ci.firstStart+(ci.nRunners-1)*ci.interval);
|
laststart=max(laststart, ci.firstStart+(ci.nRunners-1)*ci.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
gdi.addString("", 0, "Faktiskt startdjup: X minuter.#" + itos(((last+1) * di.baseInterval)/60));
|
outLines.emplace_back(0, L"Faktiskt startdjup: X minuter.#" + itow(((last+1) * di.baseInterval)/60));
|
||||||
|
|
||||||
gdi.addString("", 1, L"Sista start (nu tilldelad): X.#" +
|
outLines.emplace_back(1, L"Sista start (nu tilldelad): X.#" +
|
||||||
oe->getAbsTime(laststart*di.baseInterval+di.firstStart));
|
oe->getAbsTime(laststart*di.baseInterval+di.firstStart));
|
||||||
|
|
||||||
gdi.dropLine();
|
outLines.emplace_back(0, L"");
|
||||||
|
|
||||||
int nr;
|
int nr;
|
||||||
int T=0;
|
int T=0;
|
||||||
int sum=0;
|
int sum=0;
|
||||||
gdi.addString("", 1, "Antal startande per intervall (inklusive redan lottade):");
|
outLines.emplace_back(1, L"Antal startande per intervall (inklusive redan lottade):");
|
||||||
string str="";
|
string str="";
|
||||||
int empty=4;
|
int empty=4;
|
||||||
|
|
||||||
@ -867,8 +908,8 @@ void oEvent::optimizeStartOrder(gdioutput &gdi, DrawInfo &di, vector<ClassInfo>
|
|||||||
str+=bf;
|
str+=bf;
|
||||||
}
|
}
|
||||||
|
|
||||||
gdi.addStringUT(10, str);
|
outLines.emplace_back(10, L"#" + gdibase.widen(str));
|
||||||
gdi.dropLine();
|
outLines.emplace_back(0, L"");
|
||||||
}
|
}
|
||||||
|
|
||||||
void oEvent::loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const {
|
void oEvent::loadDrawSettings(const set<int> &classes, DrawInfo &drawInfo, vector<ClassInfo> &cInfo) const {
|
||||||
@ -967,6 +1008,653 @@ void oEvent::drawRemaining(DrawMethod method, bool placeAfter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GroupInfo {
|
||||||
|
int firstStart = 0;
|
||||||
|
int unassigned = 0;
|
||||||
|
int ix;
|
||||||
|
vector<int> rPerGroup;
|
||||||
|
vector<int> vacantPerGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool sameFamily(pRunner a, pRunner b) {
|
||||||
|
wstring af = a->getFamilyName();
|
||||||
|
wstring bf = b->getFamilyName();
|
||||||
|
if (af.length() == bf.length())
|
||||||
|
return af == bf;
|
||||||
|
if (af.length() > bf.length())
|
||||||
|
swap(af, bf);
|
||||||
|
|
||||||
|
vector<wstring> bff;
|
||||||
|
split(bf, L" -", bff);
|
||||||
|
for (wstring &bfs : bff) {
|
||||||
|
if (bfs == af)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
//return af.find(bf) != wstring::npos || bf.find(af) != wstring::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename RND>
|
||||||
|
void groupUnassignedRunners(vector<pRunner> &rIn,
|
||||||
|
vector<pair<vector<pRunner>, bool>> &rGroups,
|
||||||
|
int maxPerGroup, RND rnd) {
|
||||||
|
|
||||||
|
map<int, vector<pRunner>> families;
|
||||||
|
for (pRunner &r : rIn) {
|
||||||
|
int fam = r->getDCI().getInt("Family");
|
||||||
|
if (fam != 0) {
|
||||||
|
families[fam].push_back(r);
|
||||||
|
r = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map<int, vector<pRunner>> clubs;
|
||||||
|
for (pRunner &r : rIn) {
|
||||||
|
if (r)
|
||||||
|
clubs[r->getClubId()].push_back(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge families to clubs, if appropriate
|
||||||
|
for (auto &fam : families) {
|
||||||
|
int cid = fam.second[0]->getClubId();
|
||||||
|
if (cid != 0 && clubs.count(cid) && int(clubs[cid].size() + fam.second.size()) < maxPerGroup) {
|
||||||
|
clubs[cid].insert(clubs[cid].end(), fam.second.begin(), fam.second.end());
|
||||||
|
fam.second.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<vector<pRunner>> rawGroups;
|
||||||
|
for (auto &g : families) {
|
||||||
|
if (g.second.size() > 0) {
|
||||||
|
rawGroups.emplace_back();
|
||||||
|
rawGroups.back().swap(g.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &g : clubs) {
|
||||||
|
if (g.second.size() > 0) {
|
||||||
|
rawGroups.emplace_back();
|
||||||
|
rawGroups.back().swap(g.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(rawGroups.begin(), rawGroups.end(), rnd);
|
||||||
|
|
||||||
|
stable_sort(rawGroups.begin(), rawGroups.end(),
|
||||||
|
[](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size()/4) < (b.size()/4); });
|
||||||
|
|
||||||
|
for (auto &g : rawGroups) {
|
||||||
|
if (int(g.size()) <= maxPerGroup)
|
||||||
|
rGroups.emplace_back(g, false);
|
||||||
|
else {
|
||||||
|
int nSplit = (g.size() + maxPerGroup - 1) / maxPerGroup;
|
||||||
|
sort(g.begin(), g.end(), [](const pRunner &a, const pRunner &b) {
|
||||||
|
wstring n1 = a->getFamilyName();
|
||||||
|
wstring n2 = b->getFamilyName();
|
||||||
|
return n1 < n2;
|
||||||
|
});
|
||||||
|
|
||||||
|
vector<vector<pRunner>> famGroups(1);
|
||||||
|
for (pRunner r : g) {
|
||||||
|
if (famGroups.back().empty())
|
||||||
|
famGroups.back().push_back(r);
|
||||||
|
else {
|
||||||
|
if (!sameFamily(famGroups.back().back(), r))
|
||||||
|
famGroups.emplace_back();
|
||||||
|
famGroups.back().push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(famGroups.begin(), famGroups.end(), rnd);
|
||||||
|
|
||||||
|
stable_sort(famGroups.begin(), famGroups.end(),
|
||||||
|
[](const vector<pRunner> &a, const vector<pRunner> &b) {return (a.size() / 4) < (b.size() / 4); });
|
||||||
|
|
||||||
|
size_t nPerGroup = g.size() / nSplit + 1;
|
||||||
|
bool brk = false;
|
||||||
|
while (!famGroups.empty()) {
|
||||||
|
rGroups.emplace_back(vector<pRunner>(), brk);
|
||||||
|
auto &dst = rGroups.back().first;
|
||||||
|
brk = true;
|
||||||
|
while (!famGroups.empty() && dst.size() + famGroups.back().size() <= nPerGroup) {
|
||||||
|
dst.insert(dst.end(), famGroups.back().begin(), famGroups.back().end());
|
||||||
|
famGroups.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printGroups(gdioutput &gdibase, const list<oRunner> &Runners) {
|
||||||
|
map<int, vector<const oRunner *>> rbg;
|
||||||
|
for (const oRunner &r : Runners) {
|
||||||
|
rbg[r.getStartGroup(true)].push_back(&r);
|
||||||
|
}
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 0, "List groups");
|
||||||
|
gdibase.dropLine();
|
||||||
|
|
||||||
|
map<int, int> ccCount;
|
||||||
|
for (auto rr : rbg) {
|
||||||
|
auto &vr = rr.second;
|
||||||
|
sort(vr.begin(), vr.end(), [](const oRunner *a, const oRunner *b) {
|
||||||
|
if (a->getClassId(true) != b->getClassId(true))
|
||||||
|
return a->getClassId(true) < b->getClassId(true);
|
||||||
|
else
|
||||||
|
return a->getClubId() < b->getClubId(); });
|
||||||
|
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 1, "Group: " + itos(rr.first));
|
||||||
|
int cls = -1;
|
||||||
|
|
||||||
|
for (const oRunner *r : vr) {
|
||||||
|
if (cls != r->getClassId(true)) {
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 1, r->getClass(true));
|
||||||
|
cls = r->getClassId(true);
|
||||||
|
}
|
||||||
|
++ccCount[cls];
|
||||||
|
gdibase.addString("", 0, itow(ccCount[cls]) + L": " + r->getCompleteIdentification());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void oEvent::drawListStartGroups(const vector<ClassDrawSpecification> &spec,
|
||||||
|
DrawMethod method, int pairSize, DrawType drawType,
|
||||||
|
bool limitGroupSize,
|
||||||
|
DrawInfo *diIn) {
|
||||||
|
int nParallel = -1;
|
||||||
|
if (diIn)
|
||||||
|
nParallel = diIn->nFields;
|
||||||
|
|
||||||
|
pRunner alice = getRunner(155227, 0);
|
||||||
|
|
||||||
|
constexpr bool logOutput = false;
|
||||||
|
auto &sgMap = getStartGroups(true);
|
||||||
|
if (sgMap.empty())
|
||||||
|
throw meosException("No start group defined");
|
||||||
|
|
||||||
|
map<int, GroupInfo> gInfo;
|
||||||
|
vector<pair<int, int>> orderedStartGroups;
|
||||||
|
for (auto &sg : sgMap) {
|
||||||
|
orderedStartGroups.emplace_back(sg.second.first, sg.first);
|
||||||
|
}
|
||||||
|
// Order by start time
|
||||||
|
sort(orderedStartGroups.begin(), orderedStartGroups.end());
|
||||||
|
|
||||||
|
int fs = orderedStartGroups[0].first; // First start
|
||||||
|
vector<pair<int, int>> rPerGroupTotal;
|
||||||
|
map<int, int> gId2Ix;
|
||||||
|
for (auto &sg : orderedStartGroups) {
|
||||||
|
gId2Ix[sg.second] = rPerGroupTotal.size();
|
||||||
|
rPerGroupTotal.emplace_back(0, sg.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
auto &s = spec[k];
|
||||||
|
auto &gi = gInfo[s.classID];
|
||||||
|
gi.ix = k;
|
||||||
|
gi.firstStart = fs;
|
||||||
|
gi.rPerGroup.resize(sgMap.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<pRunner> unassigned;
|
||||||
|
int total = 0;
|
||||||
|
for (auto &r : Runners) {
|
||||||
|
if (r.isRemoved() || r.isVacant())
|
||||||
|
continue;
|
||||||
|
r.tmpStartGroup = 0;
|
||||||
|
int clsId = r.getClassId(true);
|
||||||
|
auto res = gInfo.find(clsId);
|
||||||
|
if (res != gInfo.end()) {
|
||||||
|
total++;
|
||||||
|
int id = r.getStartGroup(false);
|
||||||
|
auto idRes = gId2Ix.find(id);
|
||||||
|
if (idRes != gId2Ix.end()) {
|
||||||
|
++res->second.rPerGroup[idRes->second];
|
||||||
|
++rPerGroupTotal[idRes->second].first;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++res->second.unassigned;
|
||||||
|
unassigned.push_back(&r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<pair<vector<pRunner>, bool>> uaGroups;
|
||||||
|
unsigned seed = (unsigned)chrono::system_clock::now().time_since_epoch().count();
|
||||||
|
auto rnd = std::default_random_engine(seed);
|
||||||
|
|
||||||
|
int maxPerGroup = Runners.size();
|
||||||
|
if (limitGroupSize)
|
||||||
|
maxPerGroup = max(int(Runners.size() / sgMap.size()) / 4, 4);
|
||||||
|
|
||||||
|
groupUnassignedRunners(unassigned, uaGroups, maxPerGroup, rnd );
|
||||||
|
|
||||||
|
int nPerGroupAvg = (total * 9) / (sgMap.size() * 10);
|
||||||
|
shuffle(rPerGroupTotal.begin(), rPerGroupTotal.end(), rnd);
|
||||||
|
|
||||||
|
// Assign to groups
|
||||||
|
while (!uaGroups.empty()) {
|
||||||
|
stable_sort(rPerGroupTotal.begin(), rPerGroupTotal.end(),
|
||||||
|
[](const pair<int,int> &a, const pair<int, int> &b) {return (a.first / 4) < (b.first / 4); });
|
||||||
|
|
||||||
|
// Setup map to next start group
|
||||||
|
map<int, int> nextGroup;
|
||||||
|
auto getNextGroup = [&](int ix) {
|
||||||
|
if (nextGroup.empty()) {
|
||||||
|
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
|
||||||
|
int srcId = rPerGroupTotal[k].second;
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
if (orderedStartGroups[j].second == srcId) {
|
||||||
|
int nextGrpId;
|
||||||
|
if (j + 1 < orderedStartGroups.size())
|
||||||
|
nextGrpId = orderedStartGroups[j + 1].second;
|
||||||
|
else
|
||||||
|
nextGrpId = orderedStartGroups[0].second;
|
||||||
|
|
||||||
|
for (size_t kk = 0; kk < rPerGroupTotal.size(); kk++) {
|
||||||
|
if (rPerGroupTotal[kk].second == nextGrpId) {
|
||||||
|
nextGroup[k] = kk;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextGroup[ix];
|
||||||
|
};
|
||||||
|
|
||||||
|
int nextGroupToUse = -1;
|
||||||
|
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
|
||||||
|
if (nextGroupToUse == -1)
|
||||||
|
nextGroupToUse = k;
|
||||||
|
|
||||||
|
int &nGroup = rPerGroupTotal[nextGroupToUse].first;
|
||||||
|
int groupId = rPerGroupTotal[nextGroupToUse].second;
|
||||||
|
int currentGroup = nextGroupToUse;
|
||||||
|
nextGroupToUse = -1;
|
||||||
|
|
||||||
|
if (logOutput) {
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 1, "Group: " + itos(groupId) + " (" + itos(nGroup) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (nGroup <= nPerGroupAvg && !uaGroups.empty()) {
|
||||||
|
for (pRunner ua : uaGroups.back().first) {
|
||||||
|
ua->tmpStartGroup = groupId;
|
||||||
|
auto &gi = gInfo[ua->getClassId(true)];
|
||||||
|
int j = gId2Ix[groupId];
|
||||||
|
++gi.rPerGroup[j];
|
||||||
|
nGroup++;
|
||||||
|
}
|
||||||
|
bool skip = uaGroups.back().second;
|
||||||
|
uaGroups.pop_back();
|
||||||
|
if (skip) {
|
||||||
|
nextGroupToUse = getNextGroup(currentGroup);
|
||||||
|
break; // Assign to next group (same club)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nPerGroupAvg++; // Ensure convergance
|
||||||
|
}
|
||||||
|
|
||||||
|
// vacantPerGroup
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
auto &gi = gInfo[spec[k].classID];
|
||||||
|
gi.vacantPerGroup.resize(sgMap.size());
|
||||||
|
for (int j = 0; j < spec[k].vacances; j++)
|
||||||
|
++gi.vacantPerGroup[GetRandomNumber(sgMap.size())];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logOutput)
|
||||||
|
printGroups(gdibase, Runners);
|
||||||
|
|
||||||
|
map<int, int> rPerGroup;
|
||||||
|
// Ensure not too many competitors in same class per groups
|
||||||
|
vector<map<int, map<int, int>>> countClassGroupClub(spec.size());
|
||||||
|
vector<pRunner> rl;
|
||||||
|
map<int, double> classFractions;
|
||||||
|
map<pair<int,int>, double> clubFractions;
|
||||||
|
int rTot = 0;
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
auto &countGroupClub = countClassGroupClub[k];
|
||||||
|
int cls = spec[k].classID;
|
||||||
|
getRunners(cls, 0, rl, false);
|
||||||
|
classFractions[cls] = rl.size();
|
||||||
|
rTot += rl.size();
|
||||||
|
for (pRunner r : rl) {
|
||||||
|
int gid = r->getStartGroup(true);
|
||||||
|
++rPerGroup[gid];
|
||||||
|
int club = r->getClubId();
|
||||||
|
if (club != 0) {
|
||||||
|
++clubFractions[make_pair(club,cls)];
|
||||||
|
++countGroupClub[gid][club]; // Count per club
|
||||||
|
}
|
||||||
|
++countGroupClub[gid][0]; // Count all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double q = 1.0 / rTot;
|
||||||
|
for (auto &e : classFractions)
|
||||||
|
e.second *= q;
|
||||||
|
|
||||||
|
for (auto &e : clubFractions)
|
||||||
|
e.second *= (q / classFractions[e.first.second]);
|
||||||
|
|
||||||
|
vector<tuple<int, int, int, int>> moveFromGroupClassClub;
|
||||||
|
// For each start group, count class members in each class and per club
|
||||||
|
vector<pair<int, int>> numberClub;
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
auto &g = orderedStartGroups[j];
|
||||||
|
int groupId = g.second;
|
||||||
|
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
auto &countGroupClub = countClassGroupClub[k];
|
||||||
|
int cls = spec[k].classID;
|
||||||
|
const int nTotal = countGroupClub[groupId][0];
|
||||||
|
int clubSwitch = 0; // 0 any club, or specified club id.
|
||||||
|
int numNeedSwitch = 0;
|
||||||
|
if (nTotal > 3) {
|
||||||
|
numberClub.clear();
|
||||||
|
for (auto &clubCount : countGroupClub[groupId]) {
|
||||||
|
if (clubCount.first != 0)
|
||||||
|
numberClub.emplace_back(clubCount.second, clubCount.first);
|
||||||
|
}
|
||||||
|
if (numberClub.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sort(numberClub.begin(), numberClub.end());
|
||||||
|
int clbId = numberClub.back().second;
|
||||||
|
int limit = max<int>(nTotal / 2, int(nTotal*clubFractions[make_pair(clbId, cls)]));
|
||||||
|
int limitTot = int(rPerGroup[groupId] * max(0.3, classFractions[cls]));
|
||||||
|
if (numberClub.size() == 1 || numberClub.back().first > limit) {
|
||||||
|
numNeedSwitch = numberClub.back().first - limit;
|
||||||
|
clubSwitch = clbId;
|
||||||
|
}
|
||||||
|
else if (nTotal > limitTot) {
|
||||||
|
// Move from any club
|
||||||
|
numNeedSwitch = nTotal - limitTot;
|
||||||
|
clubSwitch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numNeedSwitch > 0) {
|
||||||
|
moveFromGroupClassClub.emplace_back(groupId, cls, clubSwitch, numNeedSwitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveFromGroupClassClub.size() > 0 && limitGroupSize) {
|
||||||
|
vector<vector<pRunner>> runnersPerGroup(orderedStartGroups.size());
|
||||||
|
map<int, int> groupId2Ix;
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++)
|
||||||
|
groupId2Ix[orderedStartGroups[j].second] = j;
|
||||||
|
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
getRunners(spec[k].classID, 0, rl, false);
|
||||||
|
for (pRunner r : rl) {
|
||||||
|
runnersPerGroup[groupId2Ix[r->tmpStartGroup]].push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
shuffle(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), rnd);
|
||||||
|
stable_sort(runnersPerGroup[j].begin(), runnersPerGroup[j].end(), [](pRunner a, pRunner b) {
|
||||||
|
int specA = a->getStartGroup(false);
|
||||||
|
int specB = b->getStartGroup(false);
|
||||||
|
if (specA != specB)
|
||||||
|
return specA < specB;
|
||||||
|
return a->getEntryDate(false) > b->getEntryDate(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
map<int, map<pair<int, int>, int>> classClubCountByGroup;
|
||||||
|
for (auto ms : moveFromGroupClassClub) {
|
||||||
|
int groupId = get<0>(ms);
|
||||||
|
int clsId = get<1>(ms);
|
||||||
|
int clubId = get<2>(ms);
|
||||||
|
int cnt = get<3>(ms);
|
||||||
|
classClubCountByGroup[groupId][make_pair(clsId, clubId)] = cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
int thisGroupId = orderedStartGroups[j].second;
|
||||||
|
auto &classClubCount = classClubCountByGroup[thisGroupId];
|
||||||
|
for (pRunner &r : runnersPerGroup[j]) {
|
||||||
|
if (!r)
|
||||||
|
continue;
|
||||||
|
int cls = r->getClassId(true);
|
||||||
|
int club = r->getClubId();
|
||||||
|
auto res = classClubCount.find(make_pair(cls, club));
|
||||||
|
int type = 0;
|
||||||
|
if (res != classClubCount.end()) {
|
||||||
|
if (res->second > 0) {
|
||||||
|
--res->second;
|
||||||
|
type = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = classClubCount.find(make_pair(cls, 0));
|
||||||
|
if (res != classClubCount.end()) {
|
||||||
|
if (res->second > 0) {
|
||||||
|
--res->second;
|
||||||
|
type = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 0)
|
||||||
|
continue; //do not move
|
||||||
|
|
||||||
|
constexpr int done = 100;
|
||||||
|
for (int iter = 0; iter < 5; iter++) {
|
||||||
|
int nextG = -1;
|
||||||
|
if ((iter & 1) == 0)
|
||||||
|
nextG = j + (iter / 2 + 1);
|
||||||
|
else
|
||||||
|
nextG = j - (iter / 2 + 1);
|
||||||
|
|
||||||
|
if (size_t(nextG) >= runnersPerGroup.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int nextGroupId = orderedStartGroups[nextG].second;
|
||||||
|
auto &nextClassClubCount = classClubCountByGroup[nextGroupId];
|
||||||
|
|
||||||
|
if (type == 1 && nextClassClubCount.find(make_pair(cls, club)) != nextClassClubCount.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (pRunner &rr : runnersPerGroup[nextG]) {
|
||||||
|
if (rr) {
|
||||||
|
if (type == 1 && rr->getClassId(true) == cls && rr->getClubId() != club) {
|
||||||
|
rr->tmpStartGroup = orderedStartGroups[j].second;
|
||||||
|
r->tmpStartGroup = orderedStartGroups[nextG].second;
|
||||||
|
rr = nullptr;
|
||||||
|
r = nullptr;
|
||||||
|
iter = done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (type == 2 && rr->getClassId(true) != cls) {
|
||||||
|
rr->tmpStartGroup = orderedStartGroups[j].second;
|
||||||
|
r->tmpStartGroup = orderedStartGroups[nextG].second;
|
||||||
|
rr = nullptr;
|
||||||
|
r = nullptr;
|
||||||
|
iter = done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logOutput) {
|
||||||
|
gdibase.addString("", 0, "Reordered groups");
|
||||||
|
printGroups(gdibase, Runners);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.size() == 1) {
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
auto &sg = orderedStartGroups[j];
|
||||||
|
int groupId = sg.second;
|
||||||
|
int firstStart = getStartGroup(groupId).first;
|
||||||
|
|
||||||
|
vector<ClassDrawSpecification> specLoc = spec;
|
||||||
|
for (size_t k = 0; k < specLoc.size(); k++) {
|
||||||
|
auto &gi = gInfo[specLoc[k].classID];
|
||||||
|
specLoc[k].startGroup = groupId;
|
||||||
|
specLoc[k].firstStart = max(gi.firstStart, firstStart);
|
||||||
|
specLoc[k].vacances = gi.vacantPerGroup[j];
|
||||||
|
gi.firstStart = specLoc[k].firstStart + specLoc[k].interval * (specLoc[k].vacances + gi.rPerGroup[j]);
|
||||||
|
}
|
||||||
|
drawList(specLoc, method, pairSize, drawType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int leg = spec[0].leg;
|
||||||
|
VacantPosition vp = VacantPosition::Mixed;
|
||||||
|
DrawInfo di;
|
||||||
|
di.baseInterval = 60;
|
||||||
|
di.allowNeighbourSameCourse = true;
|
||||||
|
|
||||||
|
di.extraFactor = 0;
|
||||||
|
di.minClassInterval = di.baseInterval * 2;
|
||||||
|
di.maxClassInterval = di.baseInterval * 8;
|
||||||
|
|
||||||
|
di.minVacancy = 1;
|
||||||
|
di.maxVacancy = 100;
|
||||||
|
di.vacancyFactor = 0;
|
||||||
|
if (diIn) {
|
||||||
|
di.baseInterval = diIn->baseInterval;
|
||||||
|
di.minClassInterval = diIn->minClassInterval;
|
||||||
|
di.maxCommonControl = diIn->maxCommonControl;
|
||||||
|
di.allowNeighbourSameCourse = diIn->allowNeighbourSameCourse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update runner per group/class counters
|
||||||
|
for (auto &gi : gInfo) {
|
||||||
|
fill(gi.second.rPerGroup.begin(), gi.second.rPerGroup.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
rPerGroup.clear();
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
getRunners(spec[k].classID, 0, rl, false);
|
||||||
|
auto &gi = gInfo[spec[k].classID];
|
||||||
|
for (pRunner r : rl) {
|
||||||
|
r->setStartTime(0, true, oBase::ChangeType::Update, false);
|
||||||
|
int gid = r->getStartGroup(true);
|
||||||
|
++rPerGroup[gid];
|
||||||
|
++gi.rPerGroup[gId2Ix[gid]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map<int, int> rPerGroup;
|
||||||
|
for (auto > : rPerGroupTotal)
|
||||||
|
rPerGroup[gt.second] = gt.first;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
|
||||||
|
auto &sg = orderedStartGroups[j];
|
||||||
|
int groupId = sg.second;
|
||||||
|
|
||||||
|
int firstStart = getStartGroup(groupId).first;
|
||||||
|
|
||||||
|
int nspos = (oe->getStartGroup(groupId).second - firstStart) / di.baseInterval;
|
||||||
|
int optimalParallel = rPerGroup[groupId] / nspos;
|
||||||
|
|
||||||
|
if (nParallel <= 0)
|
||||||
|
di.nFields = max(3, min(optimalParallel + 2, 100));
|
||||||
|
else
|
||||||
|
di.nFields = nParallel;
|
||||||
|
|
||||||
|
di.firstStart = firstStart;
|
||||||
|
di.changedVacancyInfo = false;
|
||||||
|
|
||||||
|
vector<ClassInfo> cInfo;
|
||||||
|
vector<pair<int, wstring>> outLines;
|
||||||
|
di.vacancyFactor = 0;
|
||||||
|
auto &group = sgMap.find(groupId);
|
||||||
|
int length = max(300, group->second.second - group->second.first);
|
||||||
|
int slots = length / di.baseInterval;
|
||||||
|
di.classes.clear();
|
||||||
|
for (size_t k = 0; k < spec.size(); k++) {
|
||||||
|
auto &gi = gInfo[spec[k].classID];
|
||||||
|
int rClassGroup = gi.rPerGroup[j];
|
||||||
|
if (rClassGroup == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
di.classes[spec[k].classID] = ClassInfo(getClass(spec[k].classID));
|
||||||
|
ClassInfo &ci = di.classes[spec[k].classID];
|
||||||
|
ci.startGroupId = groupId;
|
||||||
|
if (gi.firstStart > firstStart) {
|
||||||
|
ci.firstStart = (gi.firstStart - firstStart) / di.baseInterval;
|
||||||
|
ci.hasFixedTime = true;
|
||||||
|
}
|
||||||
|
ci.nVacant = gi.vacantPerGroup[j];
|
||||||
|
ci.nVacantSpecified = true;
|
||||||
|
ci.nExtraSpecified = true;
|
||||||
|
|
||||||
|
if (rClassGroup == 1)
|
||||||
|
ci.fixedInterval = 4;
|
||||||
|
else {
|
||||||
|
int q = slots / (rClassGroup-1);
|
||||||
|
if (q >= 12)
|
||||||
|
ci.fixedInterval = 8;
|
||||||
|
else if (q >= 6)
|
||||||
|
ci.fixedInterval = 4;
|
||||||
|
else
|
||||||
|
ci.fixedInterval = 2;
|
||||||
|
}
|
||||||
|
if (ci.fixedInterval > 2)
|
||||||
|
ci.interval = ci.fixedInterval;
|
||||||
|
else
|
||||||
|
ci.interval = spec[k].interval / di.baseInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logOutput) {
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 1, "Behandlar grupp: " + itos(groupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
optimizeStartOrder(outLines, di, cInfo);
|
||||||
|
|
||||||
|
if (logOutput) {
|
||||||
|
for (auto &ol : outLines) {
|
||||||
|
gdibase.addString("", ol.first, ol.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int laststart = 0;
|
||||||
|
for (size_t k = 0; k<cInfo.size(); k++) {
|
||||||
|
const ClassInfo &ci = cInfo[k];
|
||||||
|
laststart = max(laststart, ci.firstStart + ci.nRunners*ci.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
//gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
|
||||||
|
// getAbsTime((laststart)*di.baseInterval + di.firstStart));
|
||||||
|
|
||||||
|
|
||||||
|
for (size_t k = 0; k < cInfo.size(); k++) {
|
||||||
|
ClassInfo &ci = cInfo[k];
|
||||||
|
vector<ClassDrawSpecification> specx;
|
||||||
|
specx.emplace_back(ci.classId, leg,
|
||||||
|
di.firstStart + di.baseInterval * ci.firstStart,
|
||||||
|
di.baseInterval * ci.interval, ci.nVacant, vp);
|
||||||
|
|
||||||
|
auto &gi = gInfo[cInfo[k].classId];
|
||||||
|
gi.firstStart = specx[0].firstStart + specx[0].interval * (specx[0].vacances + gi.rPerGroup[j]);
|
||||||
|
|
||||||
|
specx.back().startGroup = groupId;
|
||||||
|
drawList(specx, method, pairSize, drawType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
||||||
DrawMethod method, int pairSize, DrawType drawType) {
|
DrawMethod method, int pairSize, DrawType drawType) {
|
||||||
|
|
||||||
@ -1014,13 +1702,18 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
|||||||
//Only remove vacances on leg 0.
|
//Only remove vacances on leg 0.
|
||||||
vector<int> toRemove;
|
vector<int> toRemove;
|
||||||
//Remove old vacances
|
//Remove old vacances
|
||||||
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
for (it = Runners.begin(); it != Runners.end(); ++it) {
|
||||||
if (clsIdClearVac.count(it->getClassId(true))) {
|
if (clsIdClearVac.count(it->getClassId(true))) {
|
||||||
if (it->isRemoved())
|
if (it->isRemoved())
|
||||||
continue;
|
continue;
|
||||||
if (it->tInTeam)
|
if (it->tInTeam)
|
||||||
continue; // Cannot remove team runners
|
continue; // Cannot remove team runners
|
||||||
if (it->getClubId()==VacantClubId) {
|
int k = clsId2Ix.find(it->getClassId(true))->second;
|
||||||
|
if (spec[k].startGroup > 0 &&
|
||||||
|
it->getStartGroup(true) > 0 &&
|
||||||
|
it->getStartGroup(true) != spec[k].startGroup)
|
||||||
|
continue;
|
||||||
|
if (it->getClubId() == VacantClubId) {
|
||||||
toRemove.push_back(it->getId());
|
toRemove.push_back(it->getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1034,7 +1727,7 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
|||||||
if (!clsIdClearVac.count(spec[k].classID))
|
if (!clsIdClearVac.count(spec[k].classID))
|
||||||
continue;
|
continue;
|
||||||
for (int i = 0; i < spec[k].vacances; i++) {
|
for (int i = 0; i < spec[k].vacances; i++) {
|
||||||
oe->addRunnerVacant(spec[k].classID);
|
oe->addRunnerVacant(spec[k].classID)->setStartGroup(spec[k].startGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1045,6 +1738,9 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
|||||||
if (it->getStatus() == StatusNotCompetiting)
|
if (it->getStatus() == StatusNotCompetiting)
|
||||||
continue;
|
continue;
|
||||||
int ix = clsId2Ix[cid];
|
int ix = clsId2Ix[cid];
|
||||||
|
if (spec[ix].startGroup != 0 && it->getStartGroup(true) != spec[ix].startGroup)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (it->legToRun() == spec[ix].leg || spec[ix].leg == -1) {
|
if (it->legToRun() == spec[ix].leg || spec[ix].leg == -1) {
|
||||||
runners.push_back(&*it);
|
runners.push_back(&*it);
|
||||||
spec[ix].ntimes++;
|
spec[ix].ntimes++;
|
||||||
@ -1174,7 +1870,13 @@ void oEvent::drawList(const vector<ClassDrawSpecification> &spec,
|
|||||||
minStartNo = min(minStartNo, runners[k]->getStartNo());
|
minStartNo = min(minStartNo, runners[k]->getStartNo());
|
||||||
newStartNo.emplace_back(stimes[k], k);
|
newStartNo.emplace_back(stimes[k], k);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
gdibase.dropLine();
|
||||||
|
gdibase.addString("", 1, L"Draw: " + oe->getClass(spec[0].classID)->getName());
|
||||||
|
for (unsigned k = 0; k < stimes.size(); k++) {
|
||||||
|
gdibase.addString("", 0, runners[k]->getCompleteIdentification() + L" " + runners[k]->getStartTimeS());
|
||||||
|
}
|
||||||
|
*/
|
||||||
sort(newStartNo.begin(), newStartNo.end());
|
sort(newStartNo.begin(), newStartNo.end());
|
||||||
//CurrentSortOrder = SortByStartTime;
|
//CurrentSortOrder = SortByStartTime;
|
||||||
//sort(runners.begin(), runners.end());
|
//sort(runners.begin(), runners.end());
|
||||||
@ -1388,7 +2090,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
continue;
|
continue;
|
||||||
vector<ClassDrawSpecification> spec;
|
vector<ClassDrawSpecification> spec;
|
||||||
spec.emplace_back(it->getId(), 0, iFirstStart, 0, 0, vp);
|
spec.emplace_back(it->getId(), 0, iFirstStart, 0, 0, vp);
|
||||||
oe->drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll);
|
drawList(spec, DrawMethod::Random, 1, DrawType::DrawAll);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1469,6 +2171,8 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
if (maxRunners==0)
|
if (maxRunners==0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (getStartGroups(true).size() == 0) {
|
||||||
|
|
||||||
int maxParallell = 15;
|
int maxParallell = 15;
|
||||||
|
|
||||||
if (runnersStart < 100)
|
if (runnersStart < 100)
|
||||||
@ -1482,9 +2186,9 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
else
|
else
|
||||||
maxParallell = 15;
|
maxParallell = 15;
|
||||||
|
|
||||||
int optimalParallel = runnersStart / (maxRunners*2); // Min is every second interval
|
int optimalParallel = runnersStart / (maxRunners * 2); // Min is every second interval
|
||||||
|
|
||||||
di.nFields = max(3, min (optimalParallel + 2, 15));
|
di.nFields = max(3, min(optimalParallel + 2, 15));
|
||||||
di.baseInterval = baseInterval;
|
di.baseInterval = baseInterval;
|
||||||
di.extraFactor = extraFactor;
|
di.extraFactor = extraFactor;
|
||||||
di.firstStart = iFirstStart;
|
di.firstStart = iFirstStart;
|
||||||
@ -1498,10 +2202,10 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
|
|
||||||
di.startName = start;
|
di.startName = start;
|
||||||
|
|
||||||
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
||||||
if (it->getStart() != start)
|
if (it->getStart() != start)
|
||||||
continue;
|
continue;
|
||||||
if (notDrawn.count(it->getId())==0)
|
if (notDrawn.count(it->getId()) == 0)
|
||||||
continue; // Only not drawn classes
|
continue; // Only not drawn classes
|
||||||
if (it->hasFreeStart())
|
if (it->hasFreeStart())
|
||||||
continue;
|
continue;
|
||||||
@ -1509,7 +2213,7 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
di.classes[it->getId()] = ClassInfo(&*it);
|
di.classes[it->getId()] = ClassInfo(&*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (di.classes.size()==0)
|
if (di.classes.size() == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
gdi.dropLine();
|
gdi.dropLine();
|
||||||
@ -1517,21 +2221,23 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
gdi.refreshFast();
|
gdi.refreshFast();
|
||||||
gdi.dropLine();
|
gdi.dropLine();
|
||||||
vector<ClassInfo> cInfo;
|
vector<ClassInfo> cInfo;
|
||||||
optimizeStartOrder(gdi, di, cInfo);
|
vector<pair<int, wstring>> outLines;
|
||||||
|
optimizeStartOrder(outLines, di, cInfo);
|
||||||
|
for (auto &ol : outLines)
|
||||||
|
gdi.addString("", ol.first, ol.second);
|
||||||
|
|
||||||
|
int laststart = 0;
|
||||||
int laststart=0;
|
for (size_t k = 0; k < cInfo.size(); k++) {
|
||||||
for (size_t k=0;k<cInfo.size();k++) {
|
|
||||||
const ClassInfo &ci = cInfo[k];
|
const ClassInfo &ci = cInfo[k];
|
||||||
laststart=max(laststart, ci.firstStart+ci.nRunners*ci.interval);
|
laststart = max(laststart, ci.firstStart + ci.nRunners*ci.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
|
gdi.addStringUT(1, lang.tl("Sista start (nu tilldelad)") + L": " +
|
||||||
getAbsTime((laststart)*di.baseInterval+di.firstStart));
|
getAbsTime((laststart)*di.baseInterval + di.firstStart));
|
||||||
gdi.dropLine();
|
gdi.dropLine();
|
||||||
gdi.refreshFast();
|
gdi.refreshFast();
|
||||||
|
|
||||||
for (size_t k=0;k<cInfo.size();k++) {
|
for (size_t k = 0; k < cInfo.size(); k++) {
|
||||||
const ClassInfo &ci = cInfo[k];
|
const ClassInfo &ci = cInfo[k];
|
||||||
|
|
||||||
if (getClass(ci.classId)->getClassType() == oClassRelay) {
|
if (getClass(ci.classId)->getClassType() == oClassRelay) {
|
||||||
@ -1552,6 +2258,25 @@ void oEvent::automaticDrawAll(gdioutput &gdi,
|
|||||||
drawn++;
|
drawn++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
vector<ClassDrawSpecification> spec;
|
||||||
|
for (oClassList::iterator it = Classes.begin(); it != Classes.end(); ++it) {
|
||||||
|
if (it->getStart() != start)
|
||||||
|
continue;
|
||||||
|
if (notDrawn.count(it->getId()) == 0)
|
||||||
|
continue; // Only not drawn classes
|
||||||
|
if (it->hasFreeStart())
|
||||||
|
continue;
|
||||||
|
//int classID, int leg, int firstStart, int interval, int vacances, oEvent::VacantPosition vp)
|
||||||
|
spec.emplace_back(it->getId(), 0, 0, 120, 1, VacantPosition::Mixed);
|
||||||
|
drawn++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.size() == 0)
|
||||||
|
continue;
|
||||||
|
drawListStartGroups(spec, method, pairSize, DrawType::DrawAll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Classes that need completion
|
// Classes that need completion
|
||||||
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
|
for (oClassList::iterator it = Classes.begin(); it!=Classes.end(); ++it) {
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
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;
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
10
code/oTeam.h
10
code/oTeam.h
@ -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);
|
||||||
|
|||||||
@ -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
1473
code/oevent_transfer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user