OSDN Git Service

- Added better status reporting functions.
[tjqt4port/tj2qt4.git] / taskjuggler / Project.cpp
index 4262ada..306ad72 100644 (file)
 #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
@@ -37,22 +55,19 @@ Project::Project()
        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
@@ -61,37 +76,35 @@ Project::Project()
        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)
 {
@@ -118,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
@@ -131,9 +150,11 @@ 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);
@@ -143,196 +164,192 @@ Project::pass2()
        {
                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
@@ -350,6 +367,12 @@ 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();
@@ -391,40 +414,11 @@ Project::needsActualDataForReports()
 }
 
 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
@@ -445,7 +439,6 @@ Project::updateKotrus()
        return TRUE;
 }
 
-
 bool
 Project::loadFromXML( const QString& inpFile )
 {
@@ -465,6 +458,8 @@ Project::loadFromXML( const QString& inpFile )
    {
       qDebug("Empty !" );
    }
+   pass2();
+   scheduleAllScenarios();
    return true;
 }
 
@@ -491,6 +486,14 @@ void Project::parseDomElem( QDomElement& parentElem )
       {
         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" )