2 * Project.cpp - TaskJuggler
4 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
5 * by Chris Schlaeger <cs@kde.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
18 #include "TjMessageHandler.h"
19 #include "tjlib-internal.h"
25 #include "VacationInterval.h"
26 #include "Optimizer.h"
27 #include "OptimizerRun.h"
28 #include "HTMLTaskReport.h"
29 #include "HTMLResourceReport.h"
30 #include "HTMLAccountReport.h"
31 #include "HTMLWeeklyCalendar.h"
32 #include "HTMLStatusReport.h"
33 #include "CSVTaskReport.h"
34 #include "CSVResourceReport.h"
35 #include "CSVAccountReport.h"
36 #include "ExportReport.h"
37 #include "ReportXML.h"
38 #include "UsageLimits.h"
40 #include "CustomAttributeDefinition.h"
42 DebugController DebugCtrl;
49 allowRedefinitions(false),
50 weekStartsMonday(true),
56 timeFormat("%Y-%m-%d %H:%M"),
57 shortTimeFormat("%H:%M"),
60 numberFormat("-", "", ",", ".", 1),
61 currencyFormat("(", ")", ",", ".", 0),
66 dailyWorkingHours(8.0),
67 yearlyWorkingDays(260.714),
69 scheduleGranularity(ONEHOUR),
82 originalResourceList(),
83 originalAccountList(),
94 /* Pick some reasonable initial number since we don't know the
95 * project time frame yet. */
98 vacationList.setAutoDelete(true);
99 accountAttributes.setAutoDelete(true);
100 taskAttributes.setAutoDelete(true);
101 resourceAttributes.setAutoDelete(true);
102 reports.setAutoDelete(true);
104 new Scenario(this, "plan", "Plan", 0);
105 scenarioList.createIndex(true);
106 scenarioList.createIndex(false);
110 /* Initialize working hours with default values that match the Monday -
111 * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
114 workingHours[0] = new QPtrList<Interval>();
115 workingHours[0]->setAutoDelete(true);
117 for (int i = 1; i < 6; ++i)
119 workingHours[i] = new QPtrList<Interval>();
120 workingHours[i]->setAutoDelete(true);
121 workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
122 workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
126 workingHours[6] = new QPtrList<Interval>();
127 workingHours[6]->setAutoDelete(true);
132 taskList.deleteContents();
133 resourceList.deleteContents();
134 Resource::deleteStaticData();
136 accountList.deleteContents();
137 shiftList.deleteContents();
138 scenarioList.deleteContents();
140 delete resourceLimits;
142 // Remove support for 1.0 XML reports for next major release. */
145 for (int i = 0; i < 7; ++i)
146 delete workingHours[i];
151 Project::addSourceFile(const QString& f)
153 if (sourceFiles.find(f) == sourceFiles.end())
154 sourceFiles.append(f);
158 Project::getSourceFiles() const
164 Project::setProgressInfo(const QString& i)
166 emit updateProgressInfo(i);
170 Project::setProgressBar(int i, int of)
172 emit updateProgressBar(i, of);
176 Project::setTimeZone(const QString& tz)
178 if (!setTimezone(tz))
186 Project::getScenario(int sc) const
189 for (ScenarioListIterator sli(scenarioList); sli; ++sli)
196 Project::getScenarioName(int sc) const
199 for (ScenarioListIterator sli(scenarioList); sli; ++sli)
201 return (*sli)->getName();
203 return QString::null;
207 Project::getScenarioId(int sc) const
210 for (ScenarioListIterator sli(scenarioList); sli; ++sli)
212 return (*sli)->getId();
214 return QString::null;
218 Project::getScenarioIndex(const QString& id) const
220 return scenarioList.getIndex(id);
224 Project::setNow(time_t n)
226 /* Align 'now' time to timing resolution. If the resolution is
227 * changed later, this has to be done again. */
228 now = (n / scheduleGranularity) * scheduleGranularity;
232 Project::setWorkingHours(int day, const QPtrList<Interval>& l)
234 if (day < 0 || day > 6)
235 qFatal("day out of range");
236 delete workingHours[day];
238 // Create a deep copy of the interval list.
239 workingHours[day] = new QPtrList<Interval>;
240 workingHours[day]->setAutoDelete(true);
241 for (QPtrListIterator<Interval> pli(l); pli; ++pli)
242 workingHours[day]->append(new Interval(**pli));
246 Project::addId(const QString& id, bool changeCurrentId)
248 if (projectIDs.findIndex(id) != -1)
251 projectIDs.append(id);
260 Project::getIdIndex(const QString& i) const
263 if ((idx = projectIDs.findIndex(i)) == -1)
268 idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
270 } while (idx > 'Z' - 'A');
276 Project::addScenario(Scenario* s)
278 scenarioList.append(s);
280 /* This is not too efficient, but since there are usually only a few
281 * scenarios in a project, this doesn't hurt too much. */
282 scenarioList.createIndex(true);
283 scenarioList.createIndex(false);
287 Project::deleteScenario(Scenario* s)
289 scenarioList.removeRef(s);
293 Project::setResourceLimits(UsageLimits* l)
296 delete resourceLimits;
301 Project::addTask(Task* t)
307 Project::deleteTask(Task* t)
309 taskList.removeRef(t);
313 Project::addTaskAttribute(const QString& id, CustomAttributeDefinition* cad)
315 if (taskAttributes.find(id))
318 taskAttributes.insert(id, cad);
322 const CustomAttributeDefinition*
323 Project::getTaskAttribute(const QString& id) const
325 return taskAttributes[id];
329 Project::addShift(Shift* s)
335 Project::deleteShift(Shift* s)
337 shiftList.removeRef(s);
341 Project::addResource(Resource* r)
343 resourceList.append(r);
347 Project::deleteResource(Resource* r)
349 resourceList.removeRef(r);
353 Project::addResourceAttribute(const QString& id,
354 CustomAttributeDefinition* cad)
356 if (resourceAttributes.find(id))
359 resourceAttributes.insert(id, cad);
363 const CustomAttributeDefinition*
364 Project::getResourceAttribute(const QString& id) const
366 return resourceAttributes[id];
370 Project::addAccount(Account* a)
372 accountList.append(a);
376 Project::deleteAccount(Account* a)
378 accountList.removeRef(a);
382 Project::addAccountAttribute(const QString& id,
383 CustomAttributeDefinition* cad)
385 if (accountAttributes.find(id))
388 accountAttributes.insert(id, cad);
392 const CustomAttributeDefinition*
393 Project::getAccountAttribute(const QString& id) const
395 return accountAttributes[id];
399 Project::isWorkingDay(time_t d) const
401 return !(workingHours[dayOfWeek(d, false)]->isEmpty() ||
406 Project::isWorkingTime(time_t d) const
411 int dow = dayOfWeek(d, false);
412 for (QPtrListIterator<Interval> ili(*(workingHours[dow])); *ili != 0; ++ili)
414 if ((*ili)->contains(secondsOfDay(d)))
421 Project::isWorkingTime(const Interval& iv) const
423 if (isVacation(iv.getStart()))
426 int dow = dayOfWeek(iv.getStart(), false);
427 for (QPtrListIterator<Interval> ili(*(workingHours[dow])); *ili != 0; ++ili)
429 if ((*ili)->contains(Interval(secondsOfDay(iv.getStart()),
430 secondsOfDay(iv.getEnd()))))
437 Project::calcWorkingDays(const Interval& iv) const
441 for (time_t s = midnight(iv.getStart()); s <= iv.getEnd();
442 s = sameTimeNextDay(s))
450 Project::convertToDailyLoad(long secs) const
452 return ((double) secs / (dailyWorkingHours * ONEHOUR));
456 Project::addJournalEntry(JournalEntry* entry)
458 journal.inSort(entry);
462 Project::getJournalIterator() const
464 return Journal::Iterator(journal);
468 Project::pass2(bool noDepCheck, bool& fatalError, int& errors, int& warnings)
470 int oldErrors = errors;
472 if (taskList.isEmpty())
474 TJMH.errorMessage(i18n("The project does not contain any tasks."));
481 /* The optimum size for the localtime hash is twice the number of time
482 * slots times 2 (because of timeslot and timeslot - 1s). */
483 initUtility(4 * ((end - start) / scheduleGranularity));
485 // Generate sequence numbers for all lists.
486 taskList.createIndex(true);
487 resourceList.createIndex(true);
488 accountList.createIndex(true);
489 shiftList.createIndex(true);
491 // Initialize random generator.
494 // Create hash to map task IDs to pointers.
495 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
497 idHash.insert((*tli)->getId(), *tli);
499 // Create cross links from dependency lists.
500 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
501 (*tli)->xRef(idHash, errors, warnings);
503 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
505 // Set dates according to implicit dependencies
506 (*tli)->implicitXRef();
508 // Sort allocations properly
509 (*tli)->sortAllocations();
511 // Save so far booked resources as specified resources
512 (*tli)->saveSpecifiedBookedResources();
515 // Save a copy of all manually booked resources.
516 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
517 (*rli)->saveSpecifiedBookings();
519 /* Now we can copy the missing values from the plan scenario to the other
521 if (scenarioList.count() > 1)
523 for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
525 overlayScenario(0, (*sli)->getSequenceNo() - 1);
528 // Now check that all tasks have sufficient data to be scheduled.
529 setProgressInfo(i18n("Checking scheduling data..."));
530 for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
531 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
532 if (!(*tli)->preScheduleOk((*sci)->getSequenceNo() - 1))
540 setProgressInfo(i18n("Searching for dependency loops ..."));
542 qDebug("Searching for dependency loops ...");
543 // Check all tasks for dependency loops.
544 LDIList chkedTaskList;
545 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
546 if ((*tli)->loopDetector(chkedTaskList))
553 setProgressInfo(i18n("Searching for underspecified tasks ..."));
555 qDebug("Searching for underspecified tasks ...");
556 for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
557 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
558 if (!(*tli)->checkDetermination((*sci)->getSequenceNo() - 1))
567 return errors == oldErrors;
571 Project::scheduleScenario(Scenario* sc, int& errors, int& warnings)
573 int oldErrors = errors;
575 setProgressInfo(i18n("Scheduling scenario %1...").arg(sc->getId()));
577 int scIdx = sc->getSequenceNo() - 1;
578 prepareScenario(scIdx);
580 if (!schedule(scIdx, errors, warnings))
583 qDebug(i18n("Scheduling errors in scenario '%1'.")
588 finishScenario(scIdx);
590 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
592 if (!(*rli)->bookingsOk(scIdx))
599 return errors == oldErrors;
603 Project::completeBuffersAndIndices()
605 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
606 (*tli)->computeBuffers();
608 /* Create indices for all lists according to their default sorting
610 taskList.createIndex();
611 resourceList.createIndex();
612 accountList.createIndex();
613 shiftList.createIndex();
617 Project::scheduleAllScenarios(int& errors, int& warnings)
619 bool schedulingOk = true;
620 for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
621 if ((*sci)->getEnabled())
624 qDebug(i18n("Scheduling scenario '%1' ...")
625 .arg((*sci)->getId()));
627 if (!scheduleScenario(*sci, errors, warnings))
628 schedulingOk = false;
633 completeBuffersAndIndices();
639 Project::overlayScenario(int base, int sc)
641 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
642 (*tli)->overlayScenario(base, sc);
644 for (ScenarioListIterator sli(scenarioList[sc]->getSubListIterator());
646 overlayScenario(sc, (*sli)->getSequenceNo() - 1);
650 Project::prepareScenario(int sc)
652 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
653 (*tli)->prepareScenario(sc);
655 /* First we compute the criticalness of the individual task without their
656 * dependency context. */
657 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
658 (*tli)->computeCriticalness(sc);
660 /* Then we compute the path criticalness that represents the criticalness
661 * of a task taking their dependency context into account. */
662 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
663 (*tli)->computePathCriticalness(sc);
665 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
666 (*tli)->propagateInitialValues(sc);
668 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
669 (*rli)->prepareScenario(sc);
672 qDebug("Allocation probabilities for the resources:");
673 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
674 qDebug("Resource %s: %f%%",
675 (*rli)->getId().latin1(),
676 (*rli)->getAllocationProbability(sc));
677 qDebug("Criticalnesses of the tasks with respect to resource "
679 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
680 qDebug("Task %s: %-5.1f %-5.1f", (*tli)->getId().latin1(),
681 (*tli)->getCriticalness(sc),
682 (*tli)->getPathCriticalness(sc));
687 Project::finishScenario(int sc)
689 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
690 (*rli)->finishScenario(sc);
691 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
692 (*tli)->finishScenario(sc);
694 /* We need to have finished the scenario for all tasks before we can
695 * calculate the completion degree. */
696 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
697 (*tli)->calcCompletionDegree(sc);
699 /* If the user has not set the minSlackRate to 0 we look for critical
701 if (getScenario(sc)->getMinSlackRate() > 0.0)
703 setProgressInfo(i18n("Computing critical pathes..."));
704 /* The critical path detector needs to know the end of the last task.
705 * So we have to find this out first. */
707 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
708 if (maxEnd < (*tli)->getEnd(sc))
709 maxEnd = (*tli)->getEnd(sc);
711 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
712 (*tli)->checkAndMarkCriticalPath
713 (sc, getScenario(sc)->getMinSlackRate(), maxEnd);
718 Project::schedule(int sc, int& errors, int& warnings)
720 int oldErrors = errors;
722 // The scheduling function only cares about leaf tasks. Container tasks
723 // are scheduled automatically when all their childern are scheduled. So
724 // we create a task list that only contains leaf tasks.
725 TaskList allLeafTasks;
726 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
727 if (!(*tli)->hasSubs())
728 allLeafTasks.append(*tli);
730 allLeafTasks.setSorting(CoreAttributesList::PrioDown, 0);
731 allLeafTasks.setSorting(CoreAttributesList::PathCriticalnessDown, 1);
732 allLeafTasks.setSorting(CoreAttributesList::SequenceUp, 2);
735 /* The workItems list contains all tasks that are ready to be scheduled at
736 * any given iteration. When a tasks has been scheduled completely, this
737 * list needs to be updated again as some tasks may now have become ready
738 * to be scheduled. */
741 for (TaskListIterator tli(allLeafTasks); *tli != 0; ++tli)
743 if ((*tli)->isReadyForScheduling())
744 workItems.append(*tli);
745 if ((*tli)->isSchedulingDone())
750 /* While the scheduling process progresses, the list contains more and
751 * more scheduled tasks. We use the cleanupTimer to remove those in
752 * certain intervals. As we potentially have already completed tasks in
753 * the list when we start, we initialize the timer with a very large
754 * number so the first round of cleanup is done right after the first
755 * scheduling pass. */
762 Task::SchedulingInfo schedulingInfo = Task::ASAP;
764 /* The task list is sorted by priority. The priority decreases towards
765 * the end of the list. We iterate through the list and look for a
766 * task that can be scheduled. It the determines the time slot that
767 * will be scheduled during this run for all subsequent tasks as well.
769 for (TaskListIterator tli(workItems); *tli != 0; ++tli)
773 /* No time slot has been set yet. Check if this task can be
774 * scheduled and provides a suggestion. */
775 slot = (*tli)->nextSlot(scheduleGranularity);
776 priority = (*tli)->getPriority();
777 schedulingInfo = (*tli)->getScheduling();
778 /* If not, try the next task. */
783 qDebug("Task '%s' (Prio %d) requests slot %s",
784 (*tli)->getId().latin1(), (*tli)->getPriority(),
785 time2ISO(slot).latin1());
786 /* If the task wants a time slot outside of the project time
787 * frame, we flag this task as a runaway and go to the next
790 slot > (end - (time_t) scheduleGranularity + 1))
792 (*tli)->setRunaway();
794 qDebug("Marking task '%s' as runaway",
795 (*tli)->getId().latin1());
802 /* Each task has a scheduling direction (forward or backward)
803 * depending on it's constrains. The task with the highest
804 * priority determins the time slot and hence the scheduling
805 * direction. Since tasks that have the other direction cannot the
806 * scheduled then, we have to stop this run as soon as we hit a
807 * task that runs in the other direction. If we would not do this,
808 * tasks with lower priority would grab resources form tasks with
809 * higher priority. */
810 if ((*tli)->getScheduling() != schedulingInfo &&
811 !(*tli)->isMilestone())
814 qDebug("Changing scheduling direction to %d due to task "
815 "'%s'", (*tli)->getScheduling(),
816 (*tli)->getId().latin1());
819 /* We must avoid that lower priority tasks get resources even
820 * though there are higher priority tasks that are ready to be
821 * scheduled but have a non-adjacent last slot. */
822 if ((*tli)->getPriority() < priority)
825 // Schedule this task for the current time slot.
826 if ((*tli)->schedule(sc, slot, scheduleGranularity))
829 int oldSortedTasks = sortedTasks;
831 for (TaskListIterator tli(allLeafTasks); *tli != 0; ++tli)
833 if ((*tli)->isReadyForScheduling())
834 workItems.append(*tli);
835 if ((*tli)->isSchedulingDone())
838 // Update the progress bar after every 10th completed tasks.
839 if (oldSortedTasks / 10 != sortedTasks / 10)
841 setProgressBar(sortedTasks - allLeafTasks.count(),
844 (i18n("Scheduling scenario %1 at %2")
845 .arg(getScenarioId(sc)).arg(time2tjp(slot)));
849 } while (!done && !breakFlag);
854 setProgressBar(0, 0);
855 TJMH.errorMessage(i18n("Scheduling aborted on user request"));
860 if (errors != oldErrors)
861 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
862 if ((*tli)->isRunaway())
863 if ((*tli)->getScheduling() == Task::ASAP)
865 (i18n("End of task '%1' does not fit into the "
866 "project time frame. Try using a later project "
868 .arg((*tli)->getId()));
871 (i18n("Start of task '%1' does not fit into the "
872 "project time frame. Try using an earlier "
873 "project start date.").arg((*tli)->getId()));
875 if (errors == oldErrors)
876 setProgressBar(100, 100);
878 /* Check that the resulting schedule meets all the requirements that the
879 * user has specified. */
880 setProgressInfo(i18n("Checking schedule of scenario %1")
881 .arg(getScenarioId(sc)));
882 checkSchedule(sc, errors, warnings);
884 return errors == oldErrors;
888 Project::breakScheduling()
894 Project::checkSchedule(int sc, int& errors, int& warnings) const
896 int oldErrors = errors;
897 for (TaskListIterator tli(taskList); *tli != 0; ++tli)
899 /* Only check top-level tasks, since they recursively check their sub
901 if ((*tli)->getParent() == 0)
902 (*tli)->scheduleOk(sc, errors, warnings);
903 if (maxErrors > 0 && errors >= maxErrors)
906 (i18n("Too many errors in %1 scenario. Giving up.")
907 .arg(getScenarioId(sc)));
912 return errors == oldErrors;
916 Project::getReport(uint idx) const
918 QPtrListIterator<Report> it(reports);
919 for (uint i = 0; *it && i < idx; ++it, ++i)
924 QPtrListIterator<Report>
925 Project::getReportListIterator() const
927 return QPtrListIterator<Report>(reports);
931 Project::generateReports() const
934 for (QPtrListIterator<Report> ri(reports); *ri != 0; ++ri)
936 // We generate all but Qt*Reports. Those are for the GUI version.
937 if (strncmp((*ri)->getType(), "Qt", 2) != 0)
940 qDebug(i18n("Generating report '%1' ...")
941 .arg((*ri)->getFileName()));
951 Project::setKotrus(Kotrus* k)
959 Project::readKotrus()
964 for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
965 (*rli)->dbLoadBookings((*rli)->getKotrusId(), 0);
971 Project::updateKotrus()
976 bool Project::generateXMLReport() const
979 return xmlreport->generate();