/*
* Project.cpp - TaskJuggler
*
- * Copyright (c) 2001 by Chris Schlaeger <cs@suse.de>
+ * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
*
* This program is free software; you can redistribute it and/or modify
- * it under the terms version 2 of the GNU General Public License as
+ * it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* $Id$
*/
+
#include <config.h>
#include <stdio.h>
-
+#include <stdlib.h>
+#include <qdom.h>
#include <qdict.h>
+#include "debug.h"
#include "Project.h"
#include "Utility.h"
+#include "kotrus.h"
+
+DebugController DebugCtrl;
Project::Project()
{
taskList.setAutoDelete(TRUE);
resourceList.setAutoDelete(TRUE);
+ accountList.setAutoDelete(TRUE);
+
+ 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
+ * changed in project files. */
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;
- xmlreport = 0L;
+ currencyDigits = 3;
+ kotrus = 0;
+
+ /* Initialize working hours with default values that match the Monday -
+ * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
+ * countries. */
+ // Sunday
+ workingHours[0] = new QPtrList<Interval>();
+ workingHours[0]->setAutoDelete(TRUE);
+
+ 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
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);
+
// Create hash to map task IDs to pointers.
for (Task* t = taskList.first(); t != 0; t = taskList.next())
{
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();
+ // 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 (!t->preScheduleOk())
- error = TRUE;
+ if (!hasExtraValues && t->hasExtraValues(Task::Actual))
+ hasExtraValues = TRUE;
- if (error)
- return FALSE;
+ /* Now we can copy the missing values from the plan scenario to the other
+ * scenarios. */
+ for (int sc = 1; sc < getMaxScenarios(); sc++)
+ overlayScenario(sc);
- preparePlan();
- schedule();
- finishPlan();
+ // 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;
- prepareActual();
- schedule();
- finishActual();
+ // Check all tasks for dependency loops.
+ for (Task* t = taskList.first(); t != 0; t = taskList.next())
+ if (t->loopDetector())
+ return FALSE;
- return TRUE;
+ return !error;
}
-void
-Project::preparePlan()
+bool
+Project::scheduleAllScenarios()
{
+ bool error = FALSE;
+
+ if (DEBUGPS(1))
+ qWarning("Scheduling plan scenario...");
+ prepareScenario(Task::Plan);
+ if (!schedule("Plan"))
+ {
+ if (DEBUGPS(2))
+ qWarning("Scheduling errors in plan scenario.");
+ error = TRUE;
+ }
+ finishScenario(Task::Plan);
+
+ if (hasExtraValues)
+ {
+ if (DEBUGPS(1))
+ qWarning("Scheduling actual scenario...");
+ 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->preparePlan();
- for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
- r->preparePlan();
+ t->computeBuffers();
+
+ /* Create indices for all lists according to their default sorting
+ * criteria. */
+ taskList.createIndex();
+ resourceList.createIndex();
+ accountList.createIndex();
+ shiftList.createIndex();
+
+ 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;
- for (day = start; !done && day >= start && day < end; day += timeDelta)
+ bool done;
+ do
{
- do
+ done = TRUE;
+ time_t slot = 0;
+ for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
{
- done = TRUE;
- for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
- if (!t->schedule(day, scheduleGranularity))
+ if (slot == 0)
+ {
+ 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 we have at least one ALAP task that has an end date but no
- * start date then we move backwards in time. Otherwise we more
- * forward in time. */
- timeDelta = scheduleGranularity;
- for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
- {
- if (t->needsEarlierTimeSlot(day + scheduleGranularity))
- {
- timeDelta = -scheduleGranularity;
- done = FALSE;
- break;
}
- if (!t->isScheduled())
- done = FALSE;
- }
- }
-
- if (!done)
- {
- if (day < start)
- {
- qWarning("Some tasks need to start earlier than the specified "
- "project start date.");
- error = TRUE;
+ t->schedule(slot, scheduleGranularity);
+ done = FALSE;
}
- if (day >= end)
- {
- qWarning("Some tasks need to finish later than the specified "
- "project end date.");
- error = TRUE;
- }
- }
-
- if (!checkSchedule())
+ } 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;
}
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::generateReports()
{
+ if (DEBUGPS(1))
+ qWarning("Generating reports...");
+
// Generate task reports
for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
h = htmlTaskReports.next())
h->generate();
+
// Generate resource reports
for (HTMLResourceReport* r = htmlResourceReports.first(); r != 0;
r = htmlResourceReports.next())
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();
+
if( xmlreport )
xmlreport->generate();
+#ifdef HAVE_ICAL
+#ifdef HAVE_KDE
+ if( icalReport )
+ icalReport->generate();
+#endif
+#endif
+
+}
+
+bool
+Project::needsActualDataForReports()
+{
+ bool needsActual = FALSE;
+
+ // Generate task reports
+ for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
+ h = htmlTaskReports.next())
+ if (h->getShowActual())
+ needsActual = TRUE;
+ // Generate resource reports
+ for (HTMLResourceReport* h = htmlResourceReports.first(); h != 0;
+ h = htmlResourceReports.next())
+ if (h->getShowActual())
+ needsActual = TRUE;
+
+ // Generate account reports
+ for (HTMLAccountReport* h = htmlAccountReports.first(); h != 0;
+ h = htmlAccountReports.next())
+ if (h->getShowActual())
+ needsActual = TRUE;
+
+ return needsActual;
+}
+
+void
+Project::setKotrus(Kotrus* k)
+{
+ if (kotrus)
+ delete kotrus;
+ kotrus = k;
+}
+
+bool
+Project::readKotrus()
+{
+ if (!kotrus)
+ return TRUE;
+
+ for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
+ r->dbLoadBookings(r->getKotrusId(), 0);
+
+ return TRUE;
+}
+
+bool
+Project::updateKotrus()
+{
+ return TRUE;
+}
+
+bool
+Project::loadFromXML( const QString& inpFile )
+{
+ QDomDocument doc;
+ QFile file( inpFile );
+
+ doc.setContent( &file );
+ qDebug( "Loading XML " + inpFile );
+
+ QDomElement elemProject = doc.documentElement();
+
+ if( !elemProject.isNull())
+ {
+ parseDomElem( elemProject );
+ }
+ else
+ {
+ qDebug("Empty !" );
+ }
+ pass2();
+ scheduleAllScenarios();
+ return true;
+}
+
+
+void Project::parseDomElem( QDomElement& parentElem )
+{
+ QDomElement elem = parentElem.firstChild().toElement();
+
+ for( ; !elem.isNull(); elem = elem.nextSibling().toElement() )
+ {
+ QString tagName = elem.tagName();
+
+ qDebug( "|| elemType: " + tagName );
+
+ if( tagName == "Task" )
+ {
+ QString tId = elem.attribute("Id");
+ Task *t = new Task( this, tId, QString(), 0, QString(), 0 );
+
+ t->loadFromXML( elem, this );
+ addTask( t );
+ }
+ else if( tagName == "Name" )
+ {
+ 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" )
+ setPriority( elem.text().toInt());
+ else if( tagName == "start" )
+ setStart( elem.text().toLong());
+ else if( tagName == "end" )
+ setEnd( elem.text().toLong());
+
+ // parseDomElem( elem );
+ }
}