#include <qdom.h>
#include <qdict.h>
+#include "debug.h"
#include "Project.h"
#include "Utility.h"
#include "kotrus.h"
-int Project::debugLevel = 0;
+DebugController DebugCtrl;
Project::Project()
{
taskList.setAutoDelete(TRUE);
resourceList.setAutoDelete(TRUE);
accountList.setAutoDelete(TRUE);
- activeAsap.setSorting(CoreAttributesList::PrioDown);
- activeAlap.setSorting(CoreAttributesList::PrioDown);
+
+ vacationList.setAutoDelete(TRUE);
+
+ htmlTaskReports.setAutoDelete(TRUE);
+ htmlResourceReports.setAutoDelete(TRUE);
+ htmlAccountReports.setAutoDelete(TRUE);
+ htmlWeeklyCalendars.setAutoDelete(TRUE);
+ exportReports.setAutoDelete(TRUE);
+
+ scenarioNames.append("Plan");
+ scenarioNames.append("Actual");
+
+ xmlreport = 0;
+#ifdef HAVE_ICAL
+#ifdef HAVE_KDE
+ icalReport = 0;
+#endif
+#endif
+
priority = 500;
/* The following settings are country and culture dependent. Those
* defaults are probably true for many Western countries, but have to be
dailyWorkingHours = 8.0;
yearlyWorkingDays = 252;
scheduleGranularity = ONEHOUR;
+ weekStartsMonday = TRUE;
+ timeFormat = "%Y-%m-%d %H:%M";
+ shortTimeFormat = "%H:%M";
+
start = 0;
end = 0;
now = time(0);
- copyright = "";
+
minEffort = 0.0;
maxEffort = 1.0;
rate = 0.0;
- currency = "";
currencyDigits = 3;
kotrus = 0;
- xmlreport = 0;
-#ifdef HAVE_ICAL
-#ifdef HAVE_KDE
- icalReport = 0;
-#endif
-#endif
/* Initialize working hours with default values that match the Monday -
* Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
workingHours[0] = new QPtrList<Interval>();
workingHours[0]->setAutoDelete(TRUE);
- // Monday
- workingHours[1] = new QPtrList<Interval>();
- workingHours[1]->setAutoDelete(TRUE);
- workingHours[1]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
- workingHours[1]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
- // Tuesday
- workingHours[2] = new QPtrList<Interval>();
- workingHours[2]->setAutoDelete(TRUE);
- workingHours[2]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
- workingHours[2]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
- // Wednesday
- workingHours[3] = new QPtrList<Interval>();
- workingHours[3]->setAutoDelete(TRUE);
- workingHours[3]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
- workingHours[3]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
- // Thursday
- workingHours[4] = new QPtrList<Interval>();
- workingHours[4]->setAutoDelete(TRUE);
- workingHours[4]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
- workingHours[4]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
- // Friday
- workingHours[5] = new QPtrList<Interval>();
- workingHours[5]->setAutoDelete(TRUE);
- workingHours[5]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
- workingHours[5]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
+ for (int i = 1; i < 6; ++i)
+ {
+ workingHours[i] = new QPtrList<Interval>();
+ workingHours[i]->setAutoDelete(TRUE);
+ workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
+ workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
+ }
// Saturday
workingHours[6] = new QPtrList<Interval>();
workingHours[6]->setAutoDelete(TRUE);
}
+Project::~Project()
+{
+ delete xmlreport;
+#ifdef HAVE_ICAL
+#ifdef HAVE_KDE
+ delete icalReport;
+#endif
+#endif
+}
+
+const QString&
+Project::getScenarioName(int sc)
+{
+ return scenarioNames[sc];
+}
+
bool
Project::addId(const QString& id)
{
return idxStr;
}
-bool
-Project::addTask(Task* t)
+int
+Project::calcWorkingDays(const Interval& iv)
{
- taskList.append(t);
- return TRUE;
+ int workingDays = 0;
+
+ for (time_t s = midnight(iv.getStart()); s <= iv.getEnd();
+ s = sameTimeNextDay(s))
+ if (isWorkingDay(s))
+ workingDays++;
+
+ return workingDays;
}
bool
QDict<Task> idHash;
bool error = FALSE;
- taskList.createIndex();
- resourceList.createIndex();
- accountList.createIndex();
+ // Generate sequence numbers for all lists.
+ taskList.createIndex(TRUE);
+ resourceList.createIndex(TRUE);
+ accountList.createIndex(TRUE);
+ shiftList.createIndex(TRUE);
// Initialize random generator.
srand((int) start);
{
idHash.insert(t->getId(), t);
}
-
// Create cross links from dependency lists.
for (Task* t = taskList.first(); t != 0; t = taskList.next())
{
if (!t->xRef(idHash))
error = TRUE;
}
+ // Set dates according to implicit dependencies
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
+ t->implicitXRef();
- bool hasActualValues = FALSE;
+ // Find out what scenarios need to be scheduled.
+ // TODO: No multiple scenario support yet.
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
+ if (!hasExtraValues && t->hasExtraValues(Task::Actual))
+ hasExtraValues = TRUE;
+
+ /* Now we can copy the missing values from the plan scenario to the other
+ * scenarios. */
+ for (int sc = 1; sc < getMaxScenarios(); sc++)
+ overlayScenario(sc);
+
+ // Now check that all tasks have sufficient data to be scheduled.
for (Task* t = taskList.first(); t != 0; t = taskList.next())
- {
if (!t->preScheduleOk())
error = TRUE;
- if (t->hasActualValues())
- hasActualValues = TRUE;
- }
- if (error)
- return FALSE;
+ // Check all tasks for dependency loops.
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
+ if (t->loopDetector())
+ return FALSE;
+
+ return !error;
+}
- if (debugLevel > 0)
+bool
+Project::scheduleAllScenarios()
+{
+ bool error = FALSE;
+
+ if (DEBUGPS(1))
qWarning("Scheduling plan scenario...");
- preparePlan();
- schedule();
- finishPlan();
+ prepareScenario(Task::Plan);
+ if (!schedule("Plan"))
+ {
+ if (DEBUGPS(2))
+ qWarning("Scheduling errors in plan scenario.");
+ error = TRUE;
+ }
+ finishScenario(Task::Plan);
- if (hasActualValues)
+ if (hasExtraValues)
{
- if (debugLevel > 0)
+ if (DEBUGPS(1))
qWarning("Scheduling actual scenario...");
- prepareActual();
- schedule();
- finishActual();
+ prepareScenario(Task::Actual);
+ if (!schedule("Actual"))
+ {
+ if (DEBUGPS(2))
+ qWarning("Scheduling errors in actual scenario.");
+ error = TRUE;
+ }
+ finishScenario(Task::Actual);
}
for (Task* t = taskList.first(); t != 0; t = taskList.next())
t->computeBuffers();
- return TRUE;
-}
+ /* Create indices for all lists according to their default sorting
+ * criteria. */
+ taskList.createIndex();
+ resourceList.createIndex();
+ accountList.createIndex();
+ shiftList.createIndex();
-void
-Project::preparePlan()
-{
- for (Task* t = taskList.first(); t != 0; t = taskList.next())
- t->preparePlan();
- for (Task* t = taskList.first(); t != 0; t = taskList.next())
- t->propagateInitialValues();
- for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
- r->preparePlan();
+ return !error;
}
void
-Project::finishPlan()
+Project::overlayScenario(int sc)
{
for (Task* t = taskList.first(); t != 0; t = taskList.next())
- t->finishPlan();
- for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
- r->finishPlan();
+ t->overlayScenario(sc);
}
void
-Project::prepareActual()
+Project::prepareScenario(int sc)
{
for (Task* t = taskList.first(); t != 0; t = taskList.next())
- t->prepareActual();
+ t->prepareScenario(sc);
for (Task* t = taskList.first(); t != 0; t = taskList.next())
t->propagateInitialValues();
for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
- r->prepareActual();
+ r->prepareScenario(sc);
}
void
-Project::finishActual()
+Project::finishScenario(int sc)
{
- for (Task* t = taskList.first(); t != 0; t = taskList.next())
- t->finishActual();
for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
- r->finishActual();
+ r->finishScenario(sc);
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
+ t->finishScenario(sc);
}
bool
-Project::schedule()
+Project::schedule(const QString& scenario)
{
bool error = FALSE;
TaskList sortedTasks(taskList);
- sortedTasks.setSorting(CoreAttributesList::PrioDown);
+ sortedTasks.setSorting(CoreAttributesList::PrioDown, 0);
+ sortedTasks.setSorting(CoreAttributesList::SequenceUp, 1);
sortedTasks.sort();
- time_t timeDelta = scheduleGranularity;
- bool done = FALSE;
- time_t day;
-
- updateActiveTaskList(sortedTasks);
- for (day = start; !(activeAsap.isEmpty() && activeAlap.isEmpty()) &&
- day >= start && day < end; day += timeDelta)
+ bool done;
+ do
{
- timeDelta = scheduleGranularity;
- do
- {
- done = TRUE;
- for (Task* t = activeAlap.first(); t != 0; t = activeAlap.next())
- {
- if (!t->schedule(day, scheduleGranularity))
- {
- done = FALSE;
- break; // Start with top priority tasks again.
- }
- if (t->needsEarlierTimeSlot(day + scheduleGranularity))
- timeDelta = -scheduleGranularity;
- }
- } while (!done);
-
- if (timeDelta < 0)
- continue;
- uint i = 0;
- do
+ done = TRUE;
+ time_t slot = 0;
+ for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
{
- done = TRUE;
- i = 0;
- for (Task* t = activeAsap.first(); t != 0; t = activeAsap.next())
+ if (slot == 0)
{
- i++;
- if (!t->schedule(day, scheduleGranularity))
+ slot = t->nextSlot(scheduleGranularity);
+ if (slot == 0)
+ continue;
+ if (DEBUGPS(5))
+ qWarning("Task %s requests slot %s", t->getId().latin1(),
+ time2ISO(slot).latin1());
+ if (slot < start || slot > end)
{
- done = FALSE;
- break; // Start with top priority tasks again.
+ t->setRunaway();
+ if (DEBUGPS(5))
+ qDebug("Marking task %s as runaway",
+ t->getId().latin1());
+ error = TRUE;
}
}
- } while (!done);
- if (i != activeAsap.count())
- qFatal("activeAsap list corrupted");
- }
-
- if (!activeAsap.isEmpty() || !activeAlap.isEmpty())
- {
- for (Task* t = activeAsap.first(); t != 0; t = activeAsap.next())
- qWarning("Task %s does not fit into the project time frame",
- t->getId().latin1());
- for (Task* t = activeAlap.first(); t != 0; t = activeAlap.next())
- qWarning("Task %s does not fit into the project time frame",
- t->getId().latin1());
- error = TRUE;
- }
-
- if (!checkSchedule())
+ t->schedule(slot, scheduleGranularity);
+ done = FALSE;
+ }
+ } while (!done);
+
+ if (error)
+ for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
+ if (t->isRunaway())
+ if (t->getScheduling() == Task::ASAP)
+ t->fatalError(QString(
+ "End of task %1 does not fit into the project time "
+ "frame.").arg(t->getId()));
+ else
+ t->fatalError(QString(
+ "Start of task %1 does not fit into the project time "
+ "frame.").arg(t->getId()));
+
+ if (!checkSchedule(scenario))
error = TRUE;
return !error;
}
-void
-Project::updateActiveTaskList(TaskList& sortedTasks)
-{
- for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
- if (t->isActive())
- addActiveTask(t);
-}
-
bool
-Project::checkSchedule()
+Project::checkSchedule(const QString& scenario)
{
- TaskList tl = taskList;
- tl.setSorting(CoreAttributesList::StartDown);
- tl.sort();
int errors = 0;
- for (Task* t = tl.first(); t != 0; t = tl.next())
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
{
- if (!t->scheduleOk())
- errors++;
+ /* Only check top-level tasks, since they recursively check their sub
+ * tasks. */
+ if (t->getParent() == 0)
+ t->scheduleOk(errors, scenario);
if (errors >= 10)
+ {
+ qWarning(QString("Too many errors in %1 scenario. Giving up.")
+ .arg(scenario.lower()));
break;
+ }
}
return errors == 0;
}
void
-Project::setKotrus(Kotrus* k)
-{
- if (kotrus)
- delete kotrus;
- kotrus = k;
-}
-
-void
Project::generateReports()
{
- if (debugLevel > 0)
+ if (DEBUGPS(1))
qWarning("Generating reports...");
// Generate task reports
r = htmlAccountReports.next())
r->generate();
+ // Generate calendar reports
+ for (HTMLWeeklyCalendar* r = htmlWeeklyCalendars.first(); r != 0;
+ r = htmlWeeklyCalendars.next())
+ r->generate();
+
+ // Generate export files
for (ExportReport* e = exportReports.first(); e != 0;
e = exportReports.next())
e->generate();
}
void
-Project::removeActiveTask(Task* t)
-{
- t->setScheduled();
-
- if (debugLevel > 2)
- qWarning("Deactivating %s", t->getId().latin1());
-
- if (t->getScheduling() == Task::ASAP)
- activeAsap.removeRef(t);
- else
- activeAlap.removeRef(t);
-}
-
-void
-Project::addActiveTask(Task* t)
+Project::setKotrus(Kotrus* k)
{
- if (t->getScheduling() == Task::ASAP)
- {
- if (activeAsap.findRef(t) == -1)
- {
- if (debugLevel > 2)
- qWarning("Activating %s", t->getId().latin1());
- activeAsap.inSort(t);
- }
- }
- else
- {
- if (activeAlap.findRef(t) == -1)
- {
- if (debugLevel > 2)
- qWarning("Activating %s", t->getId().latin1());
- activeAlap.inSort(t);
- }
- }
+ if (kotrus)
+ delete kotrus;
+ kotrus = k;
}
bool
return TRUE;
}
-
bool
Project::loadFromXML( const QString& inpFile )
{
{
qDebug("Empty !" );
}
+ pass2();
+ scheduleAllScenarios();
return true;
}
{
setName( elem.text() );
}
+ else if( tagName == "Project" )
+ {
+ QString prjId = elem.attribute("Id");
+ addId( prjId ); // FIXME ! There can be more than one project ids!
+
+ prjId = elem.attribute("WeekStart");
+ setWeekStartsMonday( prjId == "Mon" );
+ }
else if( tagName == "Version" )
setVersion( elem.text() );
else if( tagName == "Priority" )