OSDN Git Service

- Added better status reporting functions.
[tjqt4port/tj2qt4.git] / taskjuggler / Project.cpp
index b2ca5fb..306ad72 100644 (file)
 /*
  * 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
@@ -61,11 +131,17 @@ Project::getIdIndex(const QString& i) const
        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
@@ -74,154 +150,197 @@ Project::pass2()
        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;
@@ -230,10 +349,14 @@ Project::checkSchedule()
 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())
@@ -244,6 +367,142 @@ Project::generateReports()
                 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 );
+   }
 }