2 * Task.cpp - TaskJuggler
4 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
5 * 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.
20 #include "TjMessageHandler.h"
21 #include "tjlib-internal.h"
25 #include "ResourceTreeIterator.h"
26 #include "Allocation.h"
28 #include "ReportXML.h"
30 #include "CustomAttributeDefinition.h"
31 #include "UsageLimits.h"
33 Task::Task(Project* proj, const QString& id_, const QString& n, Task* p,
34 const QString& df, int dl) :
35 CoreAttributes(proj, id_, n, p, df, dl),
54 scenarios(new TaskScenario[proj->getMaxScenarios()]),
67 schedulingDone(false),
71 allocations.setAutoDelete(true);
72 shifts.setAutoDelete(true);
73 depends.setAutoDelete(true);
74 precedes.setAutoDelete(true);
78 for (int i = 0; i < proj->getMaxScenarios(); i++)
80 scenarios[i].task = this;
81 scenarios[i].index = i;
84 scenarios[0].startBuffer = 0.0;
85 scenarios[0].endBuffer = 0.0;
86 scenarios[0].startCredit = 0.0;
87 scenarios[0].endCredit = 0.0;
89 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
91 scenarios[sc].minStart = scenarios[sc].minEnd = 0;
92 scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
98 project->deleteTask(this);
103 Task::inheritValues()
105 Task* p = static_cast<Task*>(parent);
108 // Inherit flags from parent task.
109 for (QStringList::Iterator it = p->flags.begin();
110 it != p->flags.end(); ++it)
113 // Set attributes that are inherited from parent task.
114 projectId = p->projectId;
115 priority = p->priority;
116 responsible = p->responsible;
117 account = p->account;
118 scheduling = p->scheduling;
120 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
122 scenarios[sc].minStart = p->scenarios[sc].minStart;
123 scenarios[sc].maxStart = p->scenarios[sc].maxEnd;
124 scenarios[sc].minEnd = p->scenarios[sc].minStart;
125 scenarios[sc].maxEnd = p->scenarios[sc].maxEnd;
127 // Inherit depends from parent. Relative IDs need to get another '!'.
128 for (QPtrListIterator<TaskDependency> tdi(p->depends); tdi; ++tdi)
130 QString id = (*tdi)->getTaskRefId();
133 TaskDependency* td = new TaskDependency(id,
134 project->getMaxScenarios());
135 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
137 td->setGapDuration(sc, (*tdi)->getGapDurationNR(sc));
138 td->setGapLength(sc, (*tdi)->getGapLengthNR(sc));
143 // Inherit precedes from parent. Relative IDs need to get another '!'.
144 for (QPtrListIterator<TaskDependency> tdi(p->precedes); *tdi; ++tdi)
146 QString id = (*tdi)->getTaskRefId();
149 TaskDependency* td = new TaskDependency(id,
150 project->getMaxScenarios());
151 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
153 td->setGapDuration(sc, (*tdi)->getGapDurationNR(sc));
154 td->setGapLength(sc, (*tdi)->getGapLengthNR(sc));
159 // Inherit allocations from parent.
160 for (QPtrListIterator<Allocation> ali(p->allocations); *ali; ++ali)
161 allocations.append(new Allocation(**ali));
163 // Inherit inheritable custom attributes
164 inheritCustomAttributes(project->getTaskAttributeDict());
168 // Set attributes that are inherited from global attributes.
169 projectId = project->getCurrentId();
170 priority = project->getPriority();
171 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
173 scenarios[sc].minStart = scenarios[sc].minEnd = 0;
174 scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
180 Task::addJournalEntry(JournalEntry* entry)
182 journal.inSort(entry);
186 Task::getJournalIterator() const
188 return Journal::Iterator(journal);
192 Task::addDepends(const QString& rid)
194 TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
200 Task::addPrecedes(const QString& rid)
202 TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
208 Task::addShift(const Interval& i, Shift* s)
210 return shifts.insert(new ShiftSelection(i, s));
214 Task::errorMessage(const QString& msg) const
216 TJMH.errorMessage(msg, definitionFile, definitionLine);
220 Task::warningMessage(const QString& msg) const
222 TJMH.warningMessage(msg, definitionFile, definitionLine);
226 Task::schedule(int sc, time_t& date, time_t slotDuration)
228 // Has the task been scheduled already or is it a container?
229 if (schedulingDone || !sub->isEmpty())
233 qDebug("Trying to schedule %s at %s",
234 id.latin1(), time2tjp(date).latin1());
236 if (scheduling == Task::ASAP)
239 (effort == 0.0 && length == 0.0 && duration == 0.0 && end == 0))
244 lastSlot = start - 1;
245 tentativeEnd = date + slotDuration - 1;
247 qDebug("Scheduling of ASAP task %s starts at %s (%s)",
248 id.latin1(), time2tjp(start).latin1(),
249 time2tjp(date).latin1());
251 /* Do not schedule anything if the time slot is not directly
252 * following the time slot that was previously scheduled. */
253 if (!((date - slotDuration <= lastSlot) && (lastSlot < date)))
256 lastSlot = date + slotDuration - 1;
261 (effort == 0.0 && length == 0.0 && duration == 0.0 && start == 0))
267 tentativeStart = date;
269 qDebug("Scheduling of ALAP task %s starts at %s (%s)",
270 id.latin1(), time2tjp(lastSlot).latin1(),
271 time2tjp(date).latin1());
273 /* Do not schedule anything if the current time slot is not
274 * directly preceding the previously scheduled time slot. */
275 if (!((date + slotDuration <= lastSlot) &&
276 (lastSlot < date + 2 * slotDuration)))
282 qDebug("Scheduling %s at %s",
283 id.latin1(), time2tjp(date).latin1());
285 if ((duration > 0.0) || (length > 0.0))
287 /* Length specifies the number of working days (as daily load)
288 * and duration specifies the number of calender days. */
289 if (!allocations.isEmpty())
290 bookResources(sc, date, slotDuration);
292 doneDuration += ((double) slotDuration) / ONEDAY;
293 if (project->isWorkingTime(Interval(date, date + slotDuration - 1)))
294 doneLength += project->convertToDailyLoad(slotDuration);
297 qDebug("Length: %f/%f Duration: %f/%f",
299 doneDuration, duration);
300 // Check whether we are done with this task.
301 /* The accumulated done* values contain rounding errors. This prevents
302 * exact float comparisons. To avoid rounding problems we compare the
303 * rounded values of the done* values multiplied by 2048. This should
304 * result in worst case errors of smaller than a minute. The value
305 * 2048 was chosen in the hope that a compiler is clever enough to
306 * avoid a costly multiplication if possible. */
308 qRound(doneLength * 2048) >= qRound(length * 2048)) ||
310 qRound(doneDuration * 2048) >= qRound(duration * 2048)))
312 if (scheduling == ASAP)
313 propagateEnd(sc, date + slotDuration - 1);
315 propagateStart(sc, date);
316 schedulingDone = true;
318 qDebug("Scheduling of task %s completed", id.latin1());
322 else if (effort > 0.0)
324 /* The effort of the task has been specified. We have to look
325 * how much the resources can contribute over the following
326 * workings days until we have reached the specified
328 bookResources(sc, date, slotDuration);
329 // Check whether we are done with this task.
330 if (qRound(doneEffort * 2048) >= qRound(effort * 2048))
332 if (scheduling == ASAP)
333 propagateEnd(sc, tentativeEnd);
335 propagateStart(sc, tentativeStart);
336 schedulingDone = true;
338 qDebug("Scheduling of task %s completed", id.latin1());
344 // Task is a milestone.
345 if (scheduling == ASAP)
346 propagateEnd(sc, start - 1);
348 propagateStart(sc, end + 1);
352 else if (start != 0 && end != 0)
354 // Task with start and end date but no duration criteria.
355 if (!allocations.isEmpty() && !project->isVacation(date))
356 bookResources(sc, date, slotDuration);
358 if ((scheduling == ASAP && (date + slotDuration) >= end) ||
359 (scheduling == ALAP && date <= start))
361 schedulingDone = true;
363 qDebug("Scheduling of task %s completed", id.latin1());
372 Task::scheduleContainer(int sc)
374 if (schedulingDone || !isContainer())
380 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
382 /* Make sure that all sub tasks have been scheduled. If not we
383 * can't yet schedule this task. */
384 if ((*tli)->start == 0 || (*tli)->end == 0)
387 if (nStart == 0 || (*tli)->start < nStart)
388 nStart = (*tli)->start;
389 if ((*tli)->end > nEnd)
393 if (start == 0 || start > nStart)
394 propagateStart(sc, nStart);
396 if (end == 0 || end < nEnd)
397 propagateEnd(sc, nEnd);
400 qDebug("Scheduling of task %s completed", id.latin1());
401 schedulingDone = true;
407 Task::propagateStart(int sc, time_t date)
412 qDebug("PS1: Setting start of %s to %s",
413 id.latin1(), time2tjp(start).latin1());
415 /* If one end of a milestone is fixed, then the other end can be set as
419 schedulingDone = true;
421 propagateEnd(sc, start - 1);
424 /* Set start date to all previous that have no start date yet, but are
425 * ALAP task or have no duration. */
426 for (TaskListIterator tli(previous); *tli != 0; ++tli)
427 if ((*tli)->end == 0 && (*tli)->latestEnd(sc) != 0 &&
428 !(*tli)->schedulingDone &&
429 ((*tli)->scheduling == ALAP ||
430 ((*tli)->effort == 0.0 && (*tli)->length == 0.0 &&
431 (*tli)->duration == 0.0 && !(*tli)->milestone)))
433 /* Recursively propagate the end date */
434 (*tli)->propagateEnd(sc, (*tli)->latestEnd(sc));
437 /* Propagate start time to sub tasks which have only an implicit
438 * dependancy on the parent task. Do not touch container tasks. */
439 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
441 if (!(*tli)->hasStartDependency() && !(*tli)->schedulingDone)
443 /* Recursively propagate the start date */
444 (*tli)->propagateStart(sc, start);
451 qDebug("Scheduling parent of %s", id.latin1());
452 getParent()->scheduleContainer(sc);
457 Task::propagateEnd(int sc, time_t date)
462 qDebug("PE1: Setting end of %s to %s",
463 id.latin1(), time2tjp(end).latin1());
465 /* If one end of a milestone is fixed, then the other end can be set as
470 qDebug("Scheduling of task %s completed", id.latin1());
471 schedulingDone = true;
473 propagateStart(sc, end + 1);
476 /* Set start date to all followers that have no start date yet, but are
477 * ASAP task or have no duration. */
478 for (TaskListIterator tli(followers); *tli != 0; ++tli)
479 if ((*tli)->start == 0 && (*tli)->earliestStart(sc) != 0 &&
480 !(*tli)->schedulingDone &&
481 ((*tli)->scheduling == ASAP ||
482 ((*tli)->effort == 0.0 && (*tli)->length == 0.0 &&
483 (*tli)->duration == 0.0 && !(*tli)->milestone)))
485 /* Recursively propagate the start date */
486 (*tli)->propagateStart(sc, (*tli)->earliestStart(sc));
488 /* Propagate end time to sub tasks which have only an implicit
489 * dependancy on the parent task. Do not touch container tasks. */
490 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
491 if (!(*tli)->hasEndDependency() && !(*tli)->schedulingDone)
493 /* Recursively propagate the end date */
494 (*tli)->propagateEnd(sc, end);
500 qDebug("Scheduling parent of %s", id.latin1());
501 getParent()->scheduleContainer(sc);
506 Task::propagateInitialValues(int sc)
509 propagateStart(sc, start);
511 propagateEnd(sc, end);
513 // Check if the some data of sub tasks can already be propagated.
515 scheduleContainer(sc);
521 schedulingDone = true;
526 Task::isRunaway() const
528 /* If a container task has runaway sub tasts, it is very likely that they
529 * are the culprits. So we don't report such a container task as runaway.
531 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
532 if ((*tli)->isRunaway())
539 Task::bookResources(int sc, time_t date, time_t slotDuration)
541 /* If the time slot overlaps with a specified shift interval, the
542 * time slot must also be within the specified working hours of that
544 if (!shifts.isOnShift(Interval(date, date + slotDuration - 1)))
547 qDebug("Task %s is not active at %s", id.latin1(),
548 time2tjp(date).latin1());
552 /* In projection mode we do not allow bookings prior to the current date
553 * for any task (in strict mode) or tasks which have user specified
554 * bookings (sloppy mode). */
555 if (project->getScenario(sc)->getProjectionMode() &&
556 date < project->getNow() &&
557 (project->getScenario(sc)->getStrictBookings() ||
558 !scenarios[sc].specifiedBookedResources.isEmpty()))
561 qDebug("No allocations prior to current date for task %s",
566 /* If any of the resources is marked as being mandatory, we have to check
567 * if this resource is available. In case it's not available we do not
568 * allocate any of the other resources for the time slot. */
569 bool allMandatoriesAvailables = true;
570 for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
571 if ((*ali)->isMandatory())
573 if (!(*ali)->isOnShift(Interval(date, date + slotDuration - 1)))
575 allMandatoriesAvailables = false;
578 if ((*ali)->isPersistent() && (*ali)->getLockedResource())
581 if ((availability = (*ali)->getLockedResource()->
582 isAvailable(date)) > 0)
584 allMandatoriesAvailables = false;
585 if (availability >= 4 && !(*ali)->getConflictStart())
586 (*ali)->setConflictStart(date);
592 /* For a mandatory allocation with alternatives at least one
593 * of the resources or resource groups must be available. */
595 int maxAvailability = 0;
596 QPtrList<Resource> candidates = (*ali)->getCandidates();
597 for (QPtrListIterator<Resource> rli(candidates);
598 *rli && !found; ++rli)
600 /* If a resource group is marked mandatory, all members
601 * of the group must be available. */
603 bool allAvailable = true;
604 for (ResourceTreeIterator rti(*rli); *rti != 0; ++rti)
606 (*rti)->isAvailable(date)) > 0)
608 allAvailable = false;
609 if (availability >= maxAvailability)
610 maxAvailability = availability;
617 if (maxAvailability >= 4 && !(*ali)->getConflictStart())
618 (*ali)->setConflictStart(date);
619 allMandatoriesAvailables = false;
625 for (QPtrListIterator<Allocation> ali(allocations);
626 *ali != 0 && allMandatoriesAvailables &&
627 (effort == 0.0 || doneEffort < effort); ++ali)
629 /* If a shift has been defined for a resource for this task, there
630 * must be a shift interval defined for this day and the time must
631 * be within the working hours of that shift. */
632 if (!(*ali)->isOnShift(Interval(date, date + slotDuration - 1)))
635 qDebug("Allocation not on shift at %s",
636 time2tjp(date).latin1());
640 /* Now check the limits set for this allocation. */
641 const UsageLimits* limits = (*ali)->getLimits();
642 /* This variable holds the number of slots that are still available to
643 * hit the nearest limit. -1 means unlimited slots. */
644 int slotsToLimit = -1;
647 QPtrList<Resource> resources = (*ali)->getCandidates();
649 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
650 resStr += (*rli)->getId() + " ";
651 if (limits->getDailyMax() > 0)
654 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
655 slotCount += (*rli)->getCurrentDaySlots(date, this);
656 int freeSlots = limits->getDailyMax() - slotCount;
660 qDebug(" Resource(s) %soverloaded", resStr.latin1());
663 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
664 slotsToLimit = freeSlots;
666 if (limits->getWeeklyMax() > 0)
669 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
670 slotCount += (*rli)->getCurrentWeekSlots(date, this);
671 int freeSlots = limits->getWeeklyMax() - slotCount;
675 qDebug(" Resource(s) %soverloaded", resStr.latin1());
678 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
679 slotsToLimit = freeSlots;
681 if (limits->getMonthlyMax() > 0)
684 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
685 slotCount += (*rli)->getCurrentMonthSlots(date, this);
686 int freeSlots = limits->getMonthlyMax() - slotCount;
690 qDebug(" Resource(s) %soverloaded", resStr.latin1());
693 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
694 slotsToLimit = freeSlots;
698 /* If the allocation has be marked persistent and a resource
699 * has already been picked, try to book this resource again. If the
700 * resource is not available there will be no booking for this
702 int maxAvailability = 0;
703 if ((*ali)->isPersistent() && (*ali)->getLockedResource())
705 if (!bookResource((*ali)->getLockedResource(), date, slotDuration,
706 slotsToLimit, maxAvailability))
708 if (maxAvailability >= 4 && !(*ali)->getConflictStart())
709 (*ali)->setConflictStart(date);
711 else if ((*ali)->getConflictStart())
714 qDebug("Resource %s is not available for task '%s' "
716 (*ali)->getLockedResource()->getId().latin1(),
718 time2ISO((*ali)->getConflictStart()).latin1(),
719 time2ISO(date).latin1());
720 (*ali)->setConflictStart(0);
725 QPtrList<Resource> cl = createCandidateList(sc, date, *ali);
728 for (QPtrListIterator<Resource> rli(cl); *rli != 0; ++rli)
729 if (bookResource((*rli), date, slotDuration, slotsToLimit,
732 (*ali)->setLockedResource(*rli);
736 if (!found && maxAvailability >= 4 && !(*ali)->getConflictStart())
737 (*ali)->setConflictStart(date);
738 else if (found && (*ali)->getConflictStart())
744 for (QPtrListIterator<Resource> rli(cl); *rli != 0; ++rli)
750 candidates += (*rli)->getId();
752 qDebug("No resource of the allocation (%s) is available "
753 "for task '%s' from %s to %s",
756 time2ISO((*ali)->getConflictStart()).latin1(),
757 time2ISO(date).latin1());
759 (*ali)->setConflictStart(0);
766 Task::bookResource(Resource* r, time_t date, time_t slotDuration,
767 int& slotsToLimit, int& maxAvailability)
770 double intervalLoad = project->convertToDailyLoad(slotDuration);
772 for (ResourceTreeIterator rti(r); *rti != 0; ++rti)
776 (*rti)->isAvailable(date)) == 0)
778 (*rti)->book(new Booking(Interval(date, date + slotDuration - 1),
780 addBookedResource(*rti);
782 /* Move the start date to make sure that there is
783 * some work going on at the start date. */
786 if (scheduling == ASAP)
788 else if (scheduling == ALAP)
789 end = date + slotDuration - 1;
791 qFatal("Unknown scheduling mode");
795 tentativeStart = date;
796 tentativeEnd = date + slotDuration - 1;
797 doneEffort += intervalLoad * (*rti)->getEfficiency();
800 qDebug(" Booked resource %s (Effort: %f)",
801 (*rti)->getId().latin1(), doneEffort);
804 if (slotsToLimit > 0 && --slotsToLimit <= 0)
807 else if (availability > maxAvailability)
808 maxAvailability = availability;
814 Task::createCandidateList(int sc, time_t date, Allocation* a)
816 /* This function generates a list of resources that could be allocated to
817 * the task. The order of the list is determined by the specified
818 * selection function of the alternatives list. From this list, the
819 * first available resource is picked later on. */
820 QPtrList<Resource> candidates = a->getCandidates();
821 QPtrList<Resource> cl;
823 /* We try to minimize resource changes for consecutives time slots. So
824 * the resource used for the previous time slot is put to the 1st position
826 if (a->getLockedResource())
828 cl.append(a->getLockedResource());
829 candidates.remove(a->getLockedResource());
830 /* When an allocation is booked the resource is saved as locked
832 a->setLockedResource(0);
834 switch (a->getSelectionMode())
836 case Allocation::order:
839 while (candidates.getFirst())
841 cl.append(candidates.getFirst());
842 candidates.remove(candidates.getFirst());
845 case Allocation::minAllocationProbability:
848 qDebug("minAllocationProbability");
849 /* This is another heuristic to optimize scheduling results. The
850 * idea is to pick the resource that is most likely to be used
851 * least during this project (because of the specified
852 * allocations) and try to use it first. Unfortunately this
853 * algorithm can make things worse in certain plan setups. */
854 while (!candidates.isEmpty())
856 /* Find canidate with smallest allocationProbability and
857 * append it to the candidate list. */
858 double minProbability = 0;
859 Resource* minProbResource = 0;
860 for (QPtrListIterator<Resource> rli(candidates);
863 double probability = (*rli)->getAllocationProbability(sc);
864 if (minProbability == 0 || probability < minProbability)
866 minProbability = probability;
867 minProbResource = *rli;
870 cl.append(minProbResource);
871 candidates.remove(minProbResource);
875 case Allocation::minLoaded:
879 while (!candidates.isEmpty())
882 Resource* minLoaded = 0;
883 for (QPtrListIterator<Resource> rli(candidates);
886 /* We calculate the load as a relative value to the daily
887 * max load. This way part time people will reach their
888 * max as slowly as the full timers. */
890 (*rli)->getCurrentLoad(Interval(project->getStart(),
892 (((*rli)->getLimits() &&
893 (*rli)->getLimits()->getDailyMax() > 0) ?
894 project->convertToDailyLoad
895 ((*rli)->getLimits()->getDailyMax() *
896 project->getScheduleGranularity()) : 1.0);
898 if (minLoaded == 0 || load < minLoad)
904 cl.append(minLoaded);
905 candidates.remove(minLoaded);
909 case Allocation::maxLoaded:
913 while (!candidates.isEmpty())
916 Resource* maxLoaded = 0;
917 for (QPtrListIterator<Resource> rli(candidates);
920 /* We calculate the load as a relative value to the daily
921 * max load. This way part time people will reach their
922 * max as fast as the full timers. */
924 (*rli)->getCurrentLoad(Interval(project->getStart(),
926 (((*rli)->getLimits() &&
927 (*rli)->getLimits()->getDailyMax() > 0) ?
928 project->convertToDailyLoad
929 ((*rli)->getLimits()->getDailyMax() *
930 project->getScheduleGranularity()) : 1.0);
932 if (maxLoaded == 0 || load > maxLoad)
938 cl.append(maxLoaded);
939 candidates.remove(maxLoaded);
943 case Allocation::random:
947 while (candidates.getFirst())
949 cl.append(candidates.at(rand() % candidates.count()));
950 candidates.remove(candidates.getFirst());
955 qFatal("Illegal selection mode %d", a->getSelectionMode());
962 Task::getSchedulingText() const
966 return scheduling == ASAP ? "ASAP |-->|" : "ALAP |<--|";
972 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
975 text = (*tli)->getSchedulingText();
976 else if (text != (*tli)->getSchedulingText())
984 return QString::null;
988 Task::getStatusText(int sc) const
991 switch (getStatus(sc))
994 text = i18n("Not yet started");
997 text = i18n("Behind schedule");
1000 text = i18n("Work in progress");
1003 text = i18n("On schedule");
1005 case InProgressEarly:
1006 text = i18n("Ahead of schedule");
1009 text = i18n("Finished");
1012 text = i18n("Late");
1015 text = i18n("Unknown status");
1022 Task::isCompleted(int sc, time_t date) const
1024 if (scenarios[sc].reportedCompletion >= 0.0)
1026 if (scenarios[sc].reportedCompletion >= 100.0)
1029 // some completion degree has been specified.
1030 if (scenarios[sc].effort > 0.0)
1032 return qRound((scenarios[sc].effort *
1033 (scenarios[sc].reportedCompletion / 100.0)) * 1000)
1034 >= qRound(getLoad(sc, Interval(scenarios[sc].start, date), 0)
1040 scenarios[sc].start +
1041 static_cast<int>((scenarios[sc].reportedCompletion /
1042 100.0) * (scenarios[sc].end -
1043 scenarios[sc].start)));
1050 scenarios[sc].start +
1051 static_cast<int>((scenarios[sc].containerCompletion /
1052 100.0) * (scenarios[sc].end -
1053 scenarios[sc].start)));
1056 return (project->getNow() > date);
1060 Task::isBuffer(int sc, const Interval& iv) const
1062 return iv.overlaps(Interval(scenarios[sc].start,
1063 scenarios[sc].startBufferEnd)) ||
1064 iv.overlaps(Interval(scenarios[sc].endBufferStart,
1065 scenarios[sc].end));
1069 Task::earliestStart(int sc) const
1072 // All tasks this task depends on must have an end date set.
1073 for (TaskListIterator tli(previous); *tli; ++tli)
1074 if ((*tli)->end == 0)
1076 if ((*tli)->scheduling == ASAP)
1079 else if ((*tli)->end + 1 > date)
1080 date = (*tli)->end + 1;
1082 for (QPtrListIterator<TaskDependency> tdi(depends); *tdi != 0; ++tdi)
1084 /* Add the gapDuration and/or gapLength to the end of the dependent
1086 time_t potentialDate = (*tdi)->getTaskRef()->end + 1;
1087 time_t dateAfterLengthGap;
1088 long gapLength = (*tdi)->getGapLength(sc);
1089 for (dateAfterLengthGap = potentialDate;
1090 gapLength > 0 && dateAfterLengthGap < project->getEnd();
1091 dateAfterLengthGap += project->getScheduleGranularity())
1092 if (project->isWorkingTime(dateAfterLengthGap))
1093 gapLength -= project->getScheduleGranularity();
1094 if (dateAfterLengthGap > potentialDate + (*tdi)->getGapDuration(sc))
1095 potentialDate = dateAfterLengthGap;
1097 potentialDate += (*tdi)->getGapDuration(sc);
1098 // Set 'date' to the latest end date plus gaps of all preceding tasks.
1099 if (potentialDate > date)
1100 date = potentialDate;
1102 /* If any of the parent tasks has an explicit start date, the task must
1103 * start at or after this date. */
1104 for (Task* t = getParent(); t; t = t->getParent())
1105 if (t->start > date)
1112 Task::latestEnd(int sc) const
1115 // All tasks this task precedes must have a start date set.
1116 for (TaskListIterator tli(followers); *tli; ++tli)
1117 if ((*tli)->start == 0)
1119 if ((*tli)->scheduling == ALAP)
1122 else if (date == 0 || (*tli)->start - 1 < date)
1123 date = (*tli)->start - 1;
1125 for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
1127 /* Subtract the gapDuration and/or gapLength from the start of the
1128 * following task. */
1129 time_t potentialDate = (*tdi)->getTaskRef()->start - 1;
1130 time_t dateBeforeLengthGap;
1131 long gapLength = (*tdi)->getGapLength(sc);
1132 for (dateBeforeLengthGap = potentialDate;
1133 gapLength > 0 && dateBeforeLengthGap >= project->getStart();
1134 dateBeforeLengthGap -= project->getScheduleGranularity())
1135 if (project->isWorkingTime(dateBeforeLengthGap))
1136 gapLength -= project->getScheduleGranularity();
1137 if (dateBeforeLengthGap < potentialDate - (*tdi)->getGapDuration(sc))
1138 potentialDate = dateBeforeLengthGap;
1140 potentialDate -= (*tdi)->getGapDuration(sc);
1142 /* Set 'date' to the earliest end date minus gaps of all following
1144 if (date == 0 || potentialDate < date)
1145 date = potentialDate;
1147 /* If any of the parent tasks has an explicit end date, the task must
1148 * end at or before this date. */
1149 for (Task* t = getParent(); t; t = t->getParent())
1150 if (t->end != 0 && t->end < date)
1157 Task::getCalcEffort(int sc) const
1162 return getLoad(sc, Interval(scenarios[sc].start, scenarios[sc].end));
1166 Task::getCalcDuration(int sc) const
1171 return static_cast<double>(scenarios[sc].end + 1 - scenarios[sc].start) / ONEDAY;
1175 Task::getLoad(int sc, const Interval& period, const Resource* resource) const
1184 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1185 load += (*tli)->getLoad(sc, period, resource);
1190 load += resource->getEffectiveLoad(sc, period, AllAccounts, this);
1192 for (ResourceListIterator rli(scenarios[sc].bookedResources);
1194 load += (*rli)->getEffectiveLoad(sc, period, AllAccounts, this);
1201 Task::getAllocatedTimeLoad(int sc, const Interval& period,
1202 const Resource* resource) const
1204 return project->convertToDailyLoad
1205 (getAllocatedTime(sc, period, resource));
1209 Task::getAllocatedTime(int sc, const Interval& period,
1210 const Resource* resource) const
1215 long allocatedTime = 0;
1219 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1220 allocatedTime += (*tli)->getAllocatedTime(sc, period, resource);
1225 allocatedTime += resource->getAllocatedTime(sc, period, AllAccounts,
1228 for (ResourceListIterator rli(scenarios[sc].bookedResources);
1230 allocatedTime += (*rli)->getAllocatedTime(sc, period,
1234 return allocatedTime;
1238 Task::getCredits(int sc, const Interval& period, AccountType acctType,
1239 const Resource* resource, bool recursive) const
1241 double credits = 0.0;
1243 if (recursive && !sub->isEmpty())
1245 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1246 credits += (*tli)->getCredits(sc, period, acctType, resource,
1250 if (acctType != AllAccounts &&
1251 (account == 0 || acctType != account->getAcctType()))
1255 credits += resource->getCredits(sc, period, acctType, this);
1257 for (ResourceListIterator rli(scenarios[sc].bookedResources);
1259 credits += (*rli)->getCredits(sc, period, acctType, this);
1261 if (period.contains(scenarios[sc].start))
1262 credits += scenarios[sc].startCredit;
1263 if (period.contains(scenarios[sc].end + (milestone ? 1 : 0)))
1264 credits += scenarios[sc].endCredit;
1270 Task::xRef(QDict<Task>& hash)
1273 qDebug("Creating cross references for task %s ...", id.latin1());
1276 QPtrList<TaskDependency> brokenDeps;
1277 for (QPtrListIterator<TaskDependency> tdi(depends); *tdi; ++tdi)
1279 QString absId = resolveId((*tdi)->getTaskRefId());
1281 if ((t = hash.find(absId)) == 0)
1283 errorMessage(i18n("Unknown dependency '%1'").arg(absId));
1284 brokenDeps.append(*tdi);
1289 for (QPtrListIterator<TaskDependency> tdi2(depends); *tdi2; ++tdi2)
1290 if ((*tdi2)->getTaskRef() == t)
1292 warningMessage(i18n("No need to specify dependency %1 "
1293 "multiple times.").arg(absId));
1299 (*tdi)->setTaskRef(t);
1302 errorMessage(i18n("Task '%1' cannot depend on self.")
1306 if (t->isDescendantOf(this))
1308 errorMessage(i18n("Task '%1' cannot depend on child.")
1312 if (isDescendantOf(t))
1314 errorMessage(i18n("Task '%1' cannot depend on parent.")
1318 // Unidirectional link
1319 predecessors.append(t);
1320 // Bidirectional link
1322 t->followers.append(this);
1324 qDebug("Registering follower %s with task %s",
1325 id.latin1(), t->getId().latin1());
1329 // Remove broken dependencies as they can cause trouble later on.
1330 for (QPtrListIterator<TaskDependency> tdi(brokenDeps); *tdi; ++tdi)
1331 depends.removeRef(*tdi);
1334 for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
1336 QString absId = resolveId((*tdi)->getTaskRefId());
1338 if ((t = hash.find(absId)) == 0)
1340 errorMessage(i18n("Unknown dependency '%1'").arg(absId));
1341 brokenDeps.append(*tdi);
1345 for (QPtrListIterator<TaskDependency> tdi2(precedes); *tdi2; ++tdi2)
1346 if ((*tdi2)->getTaskRef() == t)
1348 warningMessage(i18n("No need to specify dependency '%1'"
1349 "multiple times").arg(absId));
1354 (*tdi)->setTaskRef(t);
1357 errorMessage(i18n("Task '%1' cannot precede self.")
1361 if (t->isDescendantOf(this))
1363 errorMessage(i18n("Task '%1' cannot precede a child.")
1367 if (isDescendantOf(t))
1369 errorMessage(i18n("Task '%1' cannot precede parent.")
1373 // Unidirectional link
1374 successors.append(t);
1375 // Bidirectional link
1376 followers.append(t);
1377 t->previous.append(this);
1379 qDebug("Registering predecessor %s with task %s",
1380 id.latin1(), t->getId().latin1());
1384 // Remove broken dependencies as they can cause trouble later on.
1385 for (QPtrListIterator<TaskDependency> tdi(brokenDeps); *tdi; ++tdi)
1386 precedes.removeRef(*tdi);
1393 Task::implicitXRef()
1395 /* Every time the scheduling related information of a single task has been
1396 * changed, we have to reset the cache flags for the start and end
1397 * determinability. */
1398 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1400 scenarios[sc].startCanBeDetermined = false;
1401 scenarios[sc].endCanBeDetermined = false;
1404 if (!sub->isEmpty())
1407 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1409 /* Propagate implicit dependencies. If a task has no specified start or
1410 * end date and no start or end dependencies, we check if a parent task
1411 * has an explicit start or end date which can be used. */
1415 if (scenarios[sc].specifiedStart != 0 &&
1416 scenarios[sc].specifiedEnd == 0)
1417 scenarios[sc].specifiedEnd = scenarios[sc].specifiedStart - 1;
1418 if (scenarios[sc].specifiedEnd != 0 &&
1419 scenarios[sc].specifiedStart == 0)
1420 scenarios[sc].specifiedStart = scenarios[sc].specifiedEnd + 1;
1422 bool hasDurationSpec = scenarios[sc].duration != 0 ||
1423 scenarios[sc].length != 0 ||
1424 scenarios[sc].effort != 0;
1426 if (scenarios[sc].specifiedStart == 0 && depends.isEmpty() &&
1427 !(hasDurationSpec && scheduling == ALAP))
1428 for (Task* tp = getParent(); tp; tp = tp->getParent())
1430 if (tp->scenarios[sc].specifiedStart != 0)
1433 qDebug("Setting start of task '%s' in scenario %s to "
1435 project->getScenarioId(sc).latin1(),
1436 time2ISO(tp->scenarios[sc].specifiedStart)
1438 scenarios[sc].specifiedStart =
1439 tp->scenarios[sc].specifiedStart;
1443 /* And the same for end values */
1444 if (scenarios[sc].specifiedEnd == 0 && precedes.isEmpty() &&
1445 !(hasDurationSpec && scheduling == ASAP))
1446 for (Task* tp = getParent(); tp; tp = tp->getParent())
1448 if (tp->scenarios[sc].specifiedEnd != 0)
1451 qDebug("Setting end of task '%s' in scenario %s to %s",
1453 project->getScenarioId(sc).latin1(),
1454 time2ISO(tp->scenarios[sc].specifiedEnd)
1456 scenarios[sc].specifiedEnd = tp->scenarios[sc].specifiedEnd;
1462 if (!isMilestone() && isLeaf())
1464 /* Automatic milestone marker. As a convenience we convert tasks that
1465 * only have a start or end criteria as a milestone. This is handy
1466 * when in the early stage of a project draft, when you just want to
1467 * specify the project outline and fill in subtasks and details
1469 bool hasStartSpec = false;
1470 bool hasEndSpec = false;
1471 bool hasDurationSpec = false;
1472 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1474 if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
1475 hasStartSpec = true;
1476 if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
1478 if (scenarios[sc].duration != 0 || scenarios[sc].length != 0 ||
1479 scenarios[sc].effort != 0)
1480 hasDurationSpec = true;
1482 if (!hasDurationSpec && (hasStartSpec ^ hasEndSpec))
1488 Task::sortAllocations()
1490 if (allocations.isEmpty())
1493 allocations.setAutoDelete(false);
1494 for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; )
1496 QPtrListIterator<Allocation> tmp = ali;
1498 if (!(*tmp)->isWorker())
1500 /* If the resource does not do any work we move it to the front of
1501 * the list. That way the 0 effective resources are always
1502 * allocated no matter if the effort limit has been reached or not.
1503 * At least in the same booking call. */
1504 Allocation* a = *tmp;
1505 allocations.removeRef(a);
1506 allocations.prepend(a);
1510 allocations.setAutoDelete(true);
1514 Task::saveSpecifiedBookedResources()
1516 /* The project file readers use the same resource booking mechanism as the
1517 * scheduler. So we need to save the up to now booked resources as
1518 * specified resources. */
1519 for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1520 scenarios[sc].specifiedBookedResources =
1521 scenarios[sc].bookedResources;
1525 Task::loopDetector(LDIList& chkedTaskList) const
1527 /* Only check top-level tasks. All other tasks will be checked then as
1532 qDebug("Running loop detector for task %s", id.latin1());
1535 if (loopDetection(list, chkedTaskList, false, true))
1538 if (loopDetection(list, chkedTaskList, true, true))
1544 Task::loopDetection(LDIList& list, LDIList& chkedTaskList, bool atEnd,
1545 bool fromOutside) const
1548 qDebug("%sloopDetection at %s (%s)",
1549 QString().fill(' ', list.count() + 1).latin1(), id.latin1(),
1550 atEnd ? "End" : "Start");
1552 // First, check whether the task has already been checked for loops.
1554 LoopDetectorInfo thisTask(this, atEnd);
1555 if (chkedTaskList.find(&thisTask))
1562 if (checkPathForLoops(list, atEnd))
1565 /* Now we have to traverse the graph in the direction of the specified
1566 * dependencies. 'precedes' and 'depends' specify dependencies in the
1567 * opposite direction of the flow of the tasks. So we have to make sure
1568 * that we do not follow the arcs in the direction that precedes and
1569 * depends points us. Parent/Child relationships also specify a
1570 * dependency. The scheduling mode of the child determines the direction
1571 * of the flow. With help of the 'caller' parameter we make sure that we
1572 * only visit childs if we were referred to the task by a non-parent-child
1587 /* If we were not called from a sub task we check all sub tasks.*/
1588 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1591 qDebug("%sChecking sub task %s of %s",
1592 QString().fill(' ', list.count()).latin1(),
1593 (*tli)->getId().latin1(),
1595 if ((*tli)->loopDetection(list, chkedTaskList, false, true))
1607 qDebug("%sChecking end of task %s",
1608 QString().fill(' ', list.count()).latin1(),
1610 if (loopDetection(list, chkedTaskList, true, false))
1627 qDebug("%sChecking parent task of %s",
1628 QString().fill(' ', list.count()).latin1(),
1630 if (getParent()->loopDetection(list, chkedTaskList, false,
1642 // Now check all previous tasks.
1643 for (TaskListIterator tli(previous); *tli != 0; ++tli)
1646 qDebug("%sChecking previous %s of task %s",
1647 QString().fill(' ', list.count()).latin1(),
1648 (*tli)->getId().latin1(), id.latin1());
1649 if((*tli)->loopDetection(list, chkedTaskList, true, true))
1667 /* If we were not called from a sub task we check all sub tasks.*/
1668 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1671 qDebug("%sChecking sub task %s of %s",
1672 QString().fill(' ', list.count()).latin1(),
1673 (*tli)->getId().latin1(), id.latin1());
1674 if ((*tli)->loopDetection(list, chkedTaskList, true, true))
1686 qDebug("%sChecking start of task %s",
1687 QString().fill(' ', list.count()).latin1(),
1689 if (loopDetection(list, chkedTaskList, false, false))
1706 qDebug("%sChecking parent task of %s",
1707 QString().fill(' ', list.count()).latin1(),
1709 if (getParent()->loopDetection(list, chkedTaskList, true,
1721 // Now check all following tasks.
1722 for (TaskListIterator tli(followers); *tli != 0; ++tli)
1725 qDebug("%sChecking follower %s of task %s",
1726 QString().fill(' ', list.count()).latin1(),
1727 (*tli)->getId().latin1(), id.latin1());
1728 if ((*tli)->loopDetection(list, chkedTaskList, false, true))
1733 chkedTaskList.append(list.popLast());
1736 qDebug("%sNo loops found in %s (%s)",
1737 QString().fill(' ', list.count()).latin1(),
1738 id.latin1(), atEnd ? "End" : "Start");
1743 Task::checkPathForLoops(LDIList& list, bool atEnd) const
1745 /* If we find the current task (with same position) in the list, we have
1746 * detected a loop. In case there is no loop detected we add this tasks at
1747 * the end of the list. */
1748 LoopDetectorInfo* thisTask = new LoopDetectorInfo(this, atEnd);
1749 if (list.find(thisTask))
1752 LoopDetectorInfo* it;
1753 /* Find the first occurence of this task in the list. This is the
1754 * start of the loop. */
1755 for (it = list.first(); *it != *thisTask; it = it->next())
1757 /* Then copy all loop elements to the loopChain string. */
1758 for ( ; it != 0; it = it->next())
1760 loopChain += QString("%1 (%2) -> ")
1761 .arg(it->getTask()->getId())
1762 .arg(it->getAtEnd() ? "End" : "Start");
1764 loopChain += QString("%1 (%2)").arg(id)
1765 .arg(atEnd ? "End" : "Start");
1767 errorMessage(i18n("Dependency loop detected: %1").arg(loopChain));
1770 list.append(thisTask);
1776 Task::checkDetermination(int sc) const
1778 /* Check if the task and it's dependencies have enough information to
1779 * produce a fixed determined schedule. */
1781 qDebug("Checking determination of task %s",
1785 if (!startCanBeDetermined(list, sc))
1787 /* The error message must only be shown if the task has prececessors.
1788 * If not, is has been reported before already. */
1789 if (!previous.isEmpty())
1791 (i18n("The start of task '%1' (scenario '%2') is "
1792 "underspecified. This is caused by underspecified "
1793 "dependent tasks. You must use more fixed dates to "
1794 "solve this problem.")
1795 .arg(id).arg(project->getScenarioId(sc)));
1799 if (!endCanBeDetermined(list, sc))
1801 /* The error message must only be shown if the task has followers.
1802 * If not, is has been reported before already. */
1803 if (!followers.isEmpty())
1805 (i18n("The end of task '%1' (scenario '%2') is underspecified. "
1806 "This is caused by underspecified dependent tasks. You "
1807 "must use more fixed dates to solve this problem.")
1808 .arg(id).arg(project->getScenarioId(sc)));
1816 Task::startCanBeDetermined(LDIList& list, int sc) const
1819 qDebug("Checking if start of task %s can be determined", id.latin1());
1821 if (scenarios[sc].startCanBeDetermined)
1824 qDebug("Start of task %s can be determined (cached)", id.latin1());
1828 if (checkPathForLoops(list, false))
1831 for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
1832 if (scenarios[sc].specifiedStart != 0)
1835 qDebug("Start of task %s can be determined (fixed date)",
1840 if (scheduling == ALAP &&
1841 (scenarios[sc].duration != 0.0 || scenarios[sc].length != 0.0 ||
1842 scenarios[sc].effort != 0.0 || milestone) &&
1843 endCanBeDetermined(list, sc))
1846 qDebug("Start of task %s can be determined (end + fixed length)",
1851 for (TaskListIterator tli(predecessors); *tli; ++tli)
1852 if ((*tli)->endCanBeDetermined(list, sc))
1855 qDebug("Start of task %s can be determined (dependency)",
1862 for (TaskListIterator tli = getSubListIterator(); *tli; ++tli)
1863 if (!(*tli)->startCanBeDetermined(list, sc))
1864 goto isNotDetermined;
1867 qDebug("Start of task %s can be determined (children)",
1874 qDebug("*** Start of task %s cannot be determined",
1881 scenarios[sc].startCanBeDetermined = true;
1886 Task::endCanBeDetermined(LDIList& list, int sc) const
1889 qDebug("Checking if end of task %s can be determined", id.latin1());
1891 if (scenarios[sc].endCanBeDetermined)
1894 qDebug("End of task %s can be determined", id.latin1());
1898 if (checkPathForLoops(list, true))
1901 for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
1902 if (scenarios[sc].specifiedEnd != 0)
1905 qDebug("End of task %s can be determined (fixed date)",
1910 if (scheduling == ASAP &&
1911 (scenarios[sc].duration != 0.0 || scenarios[sc].length != 0.0 ||
1912 scenarios[sc].effort != 0.0 || milestone) &&
1913 startCanBeDetermined(list, sc))
1916 qDebug("End of task %s can be determined (end + fixed length)",
1921 for (TaskListIterator tli(successors); *tli; ++tli)
1922 if ((*tli)->startCanBeDetermined(list, sc))
1925 qDebug("End of task %s can be determined (dependency)",
1932 for (TaskListIterator tli = getSubListIterator(); *tli; ++tli)
1933 if (!(*tli)->endCanBeDetermined(list, sc))
1936 qDebug("End of task %s cannot be determined (child %s)",
1937 id.latin1(), (*tli)->id.latin1());
1938 goto isNotDetermined;
1942 qDebug("End of task %s can be determined (children)",
1949 qDebug("*** End of task %s cannot be determined",
1956 scenarios[sc].endCanBeDetermined = true;
1961 Task::resolveId(QString relId)
1963 /* Converts a relative ID to an absolute ID. Relative IDs start
1964 * with a number of bangs. A set of bangs means 'Name of the n-th
1965 * parent task' with n being the number of bangs. */
1966 if (relId[0] != '!')
1971 for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
1975 errorMessage(i18n("Illegal relative ID '%1'").arg(relId));
1981 return t->id + "." + relId.right(relId.length() - i);
1983 return relId.right(relId.length() - i);
1987 Task::hasStartDependency(int sc) const
1989 /* Checks whether the task has a start specification for the
1990 * scenario. This can be a fixed start time or a dependency on another
1991 * task's end or an implicit dependency on the fixed start time of a
1993 if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
1995 for (Task* p = getParent(); p; p = p->getParent())
1996 if (p->scenarios[sc].specifiedStart != 0)
2002 Task::hasEndDependency(int sc) const
2004 /* Checks whether the task has an end specification for the
2005 * scenario. This can be a fixed end time or a dependency on another
2006 * task's start or an implicit dependency on the fixed end time of a
2008 if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
2010 for (Task* p = getParent(); p; p = p->getParent())
2011 if (p->scenarios[sc].specifiedEnd != 0)
2017 Task::hasStartDependency() const
2019 /* Check whether the task or any of it's sub tasks has a start
2021 if (start != 0 || !previous.isEmpty() || scheduling == ALAP)
2024 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2025 if ((*tli)->hasStartDependency())
2032 Task::hasEndDependency() const
2034 /* Check whether the task or any of it's sub tasks has an end
2036 if (end != 0 || !followers.isEmpty() || scheduling == ASAP)
2039 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2040 if ((*tli)->hasEndDependency())
2047 Task::preScheduleOk(int sc)
2049 if (account && !account->isLeaf())
2052 ("Task '%1' must not have an account group ('%2') "
2054 .arg(id).arg(account->getId()));
2058 if (hasSubs() && !scenarios[sc].bookedResources.isEmpty())
2061 ("Task '%1' is a container task and must not have "
2062 "bookings assigned to it.").arg(id));
2066 if (milestone && !scenarios[sc].bookedResources.isEmpty())
2069 ("Task '%1' is a milestone task and must not have "
2070 "bookings assigned to it.").arg(id));
2074 if (scenarios[sc].specifiedScheduled && !sub->isEmpty() &&
2075 (scenarios[sc].specifiedStart == 0 ||
2076 scenarios[sc].specifiedEnd == 0))
2079 ("Task '%1' is marked as scheduled but does not have "
2080 "a fixed start and end date.").arg(id));
2084 if (scenarios[sc].effort > 0.0 && allocations.count() == 0 &&
2085 !scenarios[sc].specifiedScheduled)
2088 ("No allocations specified for effort based task '%1' "
2090 .arg(id).arg(project->getScenarioId(sc)));
2094 if (scenarios[sc].startBuffer + scenarios[sc].endBuffer >= 100.0)
2097 ("Start and end buffers may not overlap in '%2' "
2098 "scenario. So their sum must be smaller then 100%.")
2099 .arg(project->getScenarioId(sc)));
2103 int durationSpec = 0;
2104 if (scenarios[sc].effort > 0.0)
2106 if (scenarios[sc].length > 0.0)
2108 if (scenarios[sc].duration > 0.0)
2110 if (durationSpec > 1)
2112 errorMessage(i18n("Task '%1' may only have one duration "
2113 "criteria in '%2' scenario.").arg(id)
2114 .arg(project->getScenarioId(sc)));
2119 |: fixed start or end date
2120 -: no fixed start or end date
2122 D: start or end dependency
2123 x->: ASAP task with duration criteria
2124 <-x: ALAP task with duration criteria
2125 -->: ASAP task without duration criteria
2126 <--: ALAP task without duration criteria
2128 bool hasStartDep = hasStartDependency(sc);
2129 bool hasEndDep = hasEndDependency(sc);
2130 if (!sub->isEmpty())
2132 if (durationSpec != 0)
2135 ("Container task '%1' may not have a duration "
2136 "criteria in '%2' scenario").arg(id)
2137 .arg(project->getScenarioId(sc)));
2143 ("The container task '%1' may not be a "
2144 "milestone.").arg(id));
2150 if (durationSpec != 0)
2153 ("Milestone '%1' may not have a duration "
2154 "criteria in '%2' scenario").arg(id)
2155 .arg(project->getScenarioId(sc)));
2159 | M - ok |D M - ok - M - err1 -D M - ok
2160 | M | err2 |D M | err2 - M | ok -D M | ok
2161 | M -D ok |D M -D ok - M -D ok -D M -D ok
2162 | M |D err2 |D M |D err2 - M |D ok -D M |D ok
2164 /* err1: no start and end
2167 if (!hasStartDep && !hasEndDep)
2169 errorMessage(i18n("Milestone '%1' must have a start or end "
2170 "specification in '%2' scenario.")
2171 .arg(id).arg(project->getScenarioId(sc)));
2174 /* err2: different start and end
2180 if (scenarios[sc].specifiedStart != 0 &&
2181 scenarios[sc].specifiedEnd != 0 &&
2182 scenarios[sc].specifiedStart != scenarios[sc].specifiedEnd + 1)
2185 ("Milestone '%1' may not have both a start "
2186 "and an end specification that do not "
2187 "match in the '%2' scenario.").arg(id)
2188 .arg(project->getScenarioId(sc)));
2195 Error table for non-container, non-milestone tasks:
2197 | x-> - ok |D x-> - ok - x-> - err3 -D x-> - ok
2198 | x-> | err1 |D x-> | err1 - x-> | err3 -D x-> | err1
2199 | x-> -D ok |D x-> -D ok - x-> -D err3 -D x-> -D ok
2200 | x-> |D err1 |D x-> |D err1 - x-> |D err3 -D x-> |D err1
2201 | --> - err2 |D --> - err2 - --> - err3 -D --> - err2
2202 | --> | ok |D --> | ok - --> | err3 -D --> | ok
2203 | --> -D ok |D --> -D ok - --> -D err3 -D --> -D ok
2204 | --> |D ok |D --> |D ok - --> |D err3 -D --> |D ok
2205 | <-x - err4 |D <-x - err4 - <-x - err4 -D <-x - err4
2206 | <-x | err1 |D <-x | err1 - <-x | ok -D <-x | ok
2207 | <-x -D err1 |D <-x -D err1 - <-x -D ok -D <-x -D ok
2208 | <-x |D err1 |D <-x |D err1 - <-x |D ok -D <-x |D ok
2209 | <-- - err4 |D <-- - err4 - <-- - err4 -D <-- - err4
2210 | <-- | ok |D <-- | ok - <-- | err2 -D <-- | ok
2211 | <-- -D ok |D <-- -D ok - <-- -D err2 -D <-- -D ok
2212 | <-- |D ok |D <-- |D ok - <-- |D err2 -D <-- |D ok
2215 err1: Overspecified (12 cases)
2229 if (((scenarios[sc].specifiedStart != 0 &&
2230 scenarios[sc].specifiedEnd != 0) ||
2231 (hasStartDep && scenarios[sc].specifiedStart == 0 &&
2232 scenarios[sc].specifiedEnd != 0 && scheduling == ASAP) ||
2233 (scenarios[sc].specifiedStart != 0 && scheduling == ALAP &&
2234 hasEndDep && scenarios[sc].specifiedEnd == 0)) &&
2235 durationSpec != 0 && !scenarios[sc].specifiedScheduled)
2237 errorMessage(i18n("Task '%1' has a start, an end and a "
2238 "duration specification for '%2' scenario.")
2239 .arg(id).arg(project->getScenarioId(sc)));
2243 err2: Underspecified (6 cases)
2251 if ((hasStartDep ^ hasEndDep) && durationSpec == 0)
2254 ("Task '%1' has only a start or end specification "
2255 "but no duration for the '%2' scenario.")
2256 .arg(id).arg(project->getScenarioId(sc)));
2260 err3: ASAP + Duration must have fixed start (8 cases)
2270 if (!hasStartDep && scheduling == ASAP)
2273 ("Task '%1' needs a start specification to be "
2274 "scheduled in ASAP mode in the '%2' scenario.")
2275 .arg(id).arg(project->getScenarioId(sc)));
2279 err4: ALAP + Duration must have fixed end (8 cases)
2289 if (!hasEndDep && scheduling == ALAP)
2292 ("Task '%1' needs an end specification to be "
2293 "scheduled in ALAP mode in the '%2' scenario.")
2294 .arg(id).arg(project->getScenarioId(sc)));
2300 (scenarios[sc].startCredit > 0.0 || scenarios[sc].endCredit > 0.0))
2303 ("Task '%1' has a specified start- or endcredit "
2304 "but no account assigned in scenario '%2'.")
2305 .arg(id).arg(project->getScenarioId(sc)));
2309 if (!scenarios[sc].bookedResources.isEmpty() && scheduling == ALAP &&
2310 !scenarios[sc].specifiedScheduled)
2313 (i18n("Error in task '%1' (scenario '%2'). "
2314 "An ALAP task can only have bookings if it has been "
2315 "completely scheduled. The 'scheduled' attribute must be "
2316 "present. Keep in mind that certain attributes such as "
2317 "'precedes' or 'end' implicitly set the scheduling mode "
2318 "to ALAP. Put 'scheduling asap' at the end of the task "
2319 "definition to avoid the problem.")
2320 .arg(id).arg(project->getScenarioId(sc)));
2328 Task::scheduleOk(int sc) const
2330 const QString scenario = project->getScenarioId(sc);
2332 /* It is of little use to report errors of container tasks, if any of
2333 * their sub tasks has errors. */
2334 int oldErrors = TJMH.getErrors();
2335 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2336 (*tli)->scheduleOk(sc);
2337 if (oldErrors != TJMH.getErrors())
2340 tjDebug(QString("Scheduling errors in sub tasks of '%1'.")
2345 /* Runaway errors have already been reported. Since the data of this task
2346 * is very likely completely bogus, we just return false. */
2351 qDebug("Checking task %s", id.latin1());
2353 /* If any of the dependant tasks is a runAway, we can safely surpress all
2354 * other error messages. */
2355 for (QPtrListIterator<TaskDependency> tdi(depends); *tdi; ++tdi)
2356 if ((*tdi)->getTaskRef()->runAway)
2358 for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
2359 if ((*tdi)->getTaskRef()->runAway)
2364 errorMessage(i18n("Task '%1' has no start time for the '%2'"
2366 .arg(id).arg(scenario));
2369 if (start < project->getStart() || start > project->getEnd())
2371 errorMessage(i18n("Start time '%1' of task '%2' is outside of the "
2372 "project interval (%3 - %4) in '%5' scenario.")
2373 .arg(time2tjp(start))
2375 .arg(time2tjp(project->getStart()))
2376 .arg(time2tjp(project->getEnd()))
2380 if (scenarios[sc].minStart != 0 && start < scenarios[sc].minStart)
2382 warningMessage(i18n("'%1' start time of task '%2' is too early\n"
2385 .arg(scenario).arg(id).arg(time2tjp(start))
2386 .arg(time2tjp(scenarios[sc].minStart)));
2389 if (scenarios[sc].maxStart != 0 && start > scenarios[sc].maxStart)
2391 warningMessage(i18n("'%1' start time of task '%2' is too late\n"
2394 .arg(scenario).arg(id)
2395 .arg(time2tjp(start))
2396 .arg(time2tjp(scenarios[sc].maxStart)));
2401 errorMessage(i18n("Task '%1' has no '%2' end time.")
2402 .arg(id).arg(scenario.lower()));
2405 if ((end + 1) < project->getStart() || (end > project->getEnd()))
2407 errorMessage(i18n("End time '%1' of task '%2' is outside of the "
2408 "project interval (%3 - %4) in '%5' scenario.")
2409 .arg(time2tjp(end + 1))
2411 .arg(time2tjp(project->getStart()))
2412 .arg(time2tjp(project->getEnd() + 1))
2416 if (scenarios[sc].minEnd != 0 && end < scenarios[sc].minEnd)
2418 warningMessage(i18n("'%1' end time of task '%2' is too early\n"
2421 .arg(scenario).arg(id)
2422 .arg(time2tjp(end + 1))
2423 .arg(time2tjp(scenarios[sc].minEnd + 1)));
2426 if (scenarios[sc].maxEnd != 0 && end > scenarios[sc].maxEnd)
2428 warningMessage(i18n("'%1' end time of task '%2' is too late\n"
2431 .arg(scenario).arg(id)
2432 .arg(time2tjp(end + 1))
2433 .arg(time2tjp(scenarios[sc].maxEnd + 1)));
2436 if (!sub->isEmpty())
2438 // All sub task must fit into their parent task.
2439 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2441 if (start > (*tli)->start)
2443 if (!(*tli)->runAway)
2445 errorMessage(i18n("Task '%1' has earlier '%2' start than "
2447 "%3 start date: %4\n"
2448 "%5 start date: %6")
2449 .arg((*tli)->getId()).arg(scenario)
2451 .arg(time2ISO(start).latin1())
2452 .arg((*tli)->getId().latin1())
2453 .arg(time2ISO((*tli)->start).latin1()));
2457 if (end < (*tli)->end)
2459 if (!(*tli)->runAway)
2461 errorMessage(i18n("Task '%1' has later '%2' end than "
2463 .arg(id).arg(scenario));
2470 // Check if all previous tasks end before start of this task.
2471 for (TaskListIterator tli(predecessors); *tli != 0; ++tli)
2472 if ((*tli)->end > start && !(*tli)->runAway)
2474 errorMessage(i18n("Impossible dependency:\n"
2475 "Task '%1' ends at %2 but needs to precede\n"
2476 "task '%3' which has a '%4' start time of %5")
2477 .arg((*tli)->id).arg(time2tjp((*tli)->end).latin1())
2478 .arg(id).arg(scenario).arg(time2tjp(start)));
2481 // Check if all following task start after this tasks end.
2482 for (TaskListIterator tli(successors); *tli != 0; ++tli)
2483 if (end > (*tli)->start && !(*tli)->runAway)
2485 errorMessage(i18n("Impossible dependency:\n"
2486 "Task '%1' starts at %2 but needs to follow\n"
2487 "task %3 which has a '%4' end time of %5")
2488 .arg((*tli)->id).arg(time2tjp((*tli)->start))
2489 .arg(id).arg(scenario).arg(time2tjp(end + 1)));
2493 if (!schedulingDone)
2495 errorMessage(i18n("Task '%1' has not been marked completed.\n"
2496 "It is scheduled to last from %2 to %3.\n"
2497 "This might be a bug in the TaskJuggler scheduler.")
2498 .arg(id).arg(time2tjp(start)).arg(time2tjp(end + 1)));
2506 Task::nextSlot(time_t slotDuration) const
2508 if (scheduling == ASAP)
2512 return lastSlot + 1;
2517 return end - slotDuration + 1;
2519 return lastSlot - slotDuration;
2526 Task::isReadyForScheduling() const
2528 /* This function returns true if the tasks has all the necessary
2529 * information to be scheduled and has not been completely scheduled yet.
2534 if (scheduling == ASAP)
2538 if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
2539 !milestone && end == 0)
2549 if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
2550 !milestone && start == 0)
2561 Task::isActive(int sc, const Interval& period) const
2563 return period.overlaps(Interval(scenarios[sc].start,
2564 milestone ? scenarios[sc].start :
2565 scenarios[sc].end));
2569 Task::isSubTask(Task* tsk) const
2571 for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2572 if (*tli == tsk || (*tli)->isSubTask(tsk))
2579 Task::overlayScenario(int base, int sc)
2581 /* Copy all values that the scenario sc does not provide, but that are
2582 * provided by the base scenario to the scenario sc. */
2583 if (scenarios[sc].specifiedStart == 0)
2584 scenarios[sc].specifiedStart = scenarios[base].specifiedStart;
2585 if (scenarios[sc].specifiedEnd == 0)
2586 scenarios[sc].specifiedEnd = scenarios[base].specifiedEnd;
2587 if (scenarios[sc].minStart == 0)
2588 scenarios[sc].minStart = scenarios[base].minStart;
2589 if (scenarios[sc].maxStart == 0)
2590 scenarios[sc].maxStart = scenarios[base].maxStart;
2591 if (scenarios[sc].minEnd == 0)
2592 scenarios[sc].minEnd = scenarios[base].minEnd;
2593 if (scenarios[sc].maxEnd == 0)
2594 scenarios[sc].maxEnd = scenarios[base].maxEnd;
2595 if (scenarios[sc].duration == 0.0)
2596 scenarios[sc].duration = scenarios[base].duration;
2597 if (scenarios[sc].length == 0.0)
2598 scenarios[sc].length = scenarios[base].length;
2599 if (scenarios[sc].effort == 0.0)
2600 scenarios[sc].effort = scenarios[base].effort;
2601 if (scenarios[sc].startBuffer < 0.0)
2602 scenarios[sc].startBuffer = scenarios[base].startBuffer;
2603 if (scenarios[sc].endBuffer < 0.0)
2604 scenarios[sc].endBuffer = scenarios[base].endBuffer;
2605 if (scenarios[sc].startCredit < 0.0)
2606 scenarios[sc].startCredit = scenarios[base].startCredit;
2607 if (scenarios[sc].endCredit < 0.0)
2608 scenarios[sc].endCredit = scenarios[base].endCredit;
2609 if (scenarios[sc].reportedCompletion < 0.0)
2610 scenarios[sc].reportedCompletion = scenarios[base].reportedCompletion;
2614 Task::prepareScenario(int sc)
2616 start = scenarios[sc].start = scenarios[sc].specifiedStart;
2617 end = scenarios[sc].end = scenarios[sc].specifiedEnd;
2618 schedulingDone = scenarios[sc].scheduled = scenarios[sc].specifiedScheduled;
2619 scenarios[sc].isOnCriticalPath = false;
2621 duration = scenarios[sc].duration;
2622 length = scenarios[sc].length;
2623 effort = scenarios[sc].effort;
2628 tentativeStart = tentativeEnd = 0;
2629 workStarted = false;
2631 bookedResources.clear();
2632 bookedResources = scenarios[sc].specifiedBookedResources;
2634 /* The user could have made manual bookings already. The effort of these
2635 * bookings needs to be calculated so that the scheduler only schedules
2636 * the still missing effort. Scheduling will begin after the last booking.
2637 * This will only work for ASAP tasks. ALAP tasks cannot be partly booked.
2639 time_t firstSlot = 0;
2640 for (ResourceListIterator rli(bookedResources); *rli != 0; ++rli)
2642 double effort = (*rli)->getEffectiveLoad
2643 (sc, Interval(project->getStart(), project->getEnd()),
2647 doneEffort += effort;
2648 if (firstSlot == 0 ||
2649 firstSlot > (*rli)->getStartOfFirstSlot(sc, this))
2651 firstSlot = (*rli)->getStartOfFirstSlot(sc, this);
2653 time_t ls = (*rli)->getEndOfLastSlot(sc, this);
2663 /* If the done flag is set, the user declares that the task is
2664 * done. If no end date has been specified, set the start and end
2665 * date to the begin of the first slot and end of the last slot.
2667 if (scenarios[sc].start == 0)
2668 start = scenarios[sc].start = firstSlot;
2669 if (scenarios[sc].end == 0)
2670 end = scenarios[sc].end = lastSlot;
2674 /* Some bookings have been specified for the task, but it is not
2675 * marked completed yet. */
2677 // Trim start to first booked time slot.
2680 /* In projection mode, we assume that the completed work has been
2681 * reported with booking attributes. Now we compute the completion
2682 * degree according to the overall effort. Then the end date of
2683 * the task is calculated. */
2684 if (project->getScenario(sc)->getProjectionMode() && effort > 0.0)
2686 scenarios[sc].reportedCompletion = doneEffort / effort * 100.0;
2687 if (scenarios[sc].reportedCompletion > 100.0)
2688 scenarios[sc].reportedCompletion = 100.0;
2690 if (doneEffort >= effort)
2692 /* In case the required effort is reached or exceeded by
2693 * the specified bookings for this task, we set the task
2694 * end to the last booking and mark the task as completely
2696 end = scenarios[sc].end = lastSlot;
2697 schedulingDone = true;
2699 /* We allow up to one time slot fuzziness before we
2700 * generate a warning. */
2701 if (project->getScenario(sc)->getStrictBookings() &&
2702 doneEffort > effort +
2703 project->convertToDailyLoad
2704 (project->getScheduleGranularity() - 1))
2706 /* In case the bookings exceed the specified effort
2707 * in strict mode, show a warning. */
2708 warningMessage(i18n("Bookings exceed effort on task "
2709 "%1 in scenario %2\n"
2710 "Reported Bookings: %3d (%4h)\n"
2711 "Specified Effort: %5d (%6h)\n")
2713 .arg(project->getScenarioId(sc))
2716 project->getDailyWorkingHours())
2719 project->getDailyWorkingHours()));
2723 lastSlot = project->getNow() - 1;
2729 * To determine the criticalness of an effort based task, we need to
2730 * determine the allocation probability of all of the resources. The more
2731 * the resources that are allocated to a task are allocated the smaller is
2732 * the likelyhood that the task will get it's allocation, the more
2735 * The allocation probability of a resource for this task is basically
2736 * effort divided by number of allocated resources. Since the efficiency
2737 * of resources can vary we need to determine the overall efficiency
2740 * TODO: We need to respect limits and shifts here!
2742 double allocationEfficiency = 0;
2743 for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
2746 if ((*ali)->isPersistent() && !bookedResources.isEmpty())
2748 /* If the allocation is persistent and we have already bookings,
2749 * we need to find the resource with the last booking for this
2750 * task and save it as looked resource. */
2751 time_t lastSlot = 0;
2752 Resource* lastResource = 0;
2753 for (QPtrListIterator<Resource> rli =
2754 (*ali)->getCandidatesIterator(); *rli; ++rli)
2755 for (ResourceTreeIterator rti(*rli); *rti; ++rti)
2756 if (bookedResources.findRef(*rti) != -1 &&
2757 (lastResource == 0 ||
2758 lastSlot < (*rti)->getEndOfLastSlot(sc, this)))
2760 lastSlot = (*rti)->getEndOfLastSlot(sc, this);
2761 lastResource = *rli;
2764 (*ali)->setLockedResource(lastResource);
2766 if (scenarios[sc].effort > 0.0)
2768 double maxEfficiency = 0;
2769 for (QPtrListIterator<Resource> rli =
2770 (*ali)->getCandidatesIterator(); *rli; ++rli)
2772 for (ResourceTreeIterator rti(*rli); *rti; ++rti)
2773 if ((*rti)->getEfficiency() > maxEfficiency)
2774 maxEfficiency = (*rti)->getEfficiency();
2776 allocationEfficiency += maxEfficiency;
2779 if (scenarios[sc].effort > 0.0)
2781 /* Now we can add the allocation probability for this task to all the
2782 * individual resources. */
2783 double effortPerResource = effort / allocationEfficiency;
2784 for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
2785 for (QPtrListIterator<Resource> rli =
2786 (*ali)->getCandidatesIterator(); *rli; ++rli)
2787 for (ResourceTreeIterator rti(*rli); *rti; ++rti)
2788 (*rti)->addAllocationProbability
2789 (sc, effortPerResource * (*rti)->getEfficiency());
2794 Task::computeCriticalness(int sc)
2796 if (scenarios[sc].effort > 0.0)
2798 double overallAllocationProbability = 0;
2799 for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
2801 /* We assume that out of the candidates for an allocation the
2802 * one with the smallest overall allocation probability will
2803 * be assigned to the task. */
2804 double smallestAllocationProbablity = 0;
2805 for (QPtrListIterator<Resource> rli =
2806 (*ali)->getCandidatesIterator(); *rli; ++rli)
2808 /* If the candidate is a resource group we use the average
2809 * allocation probablility of all the resources of the group.
2812 double averageProbability = 0.0;
2813 for (ResourceTreeIterator rti(*rli); *rti; ++rti, ++resources)
2814 averageProbability +=
2815 (*rti)->getAllocationProbability(sc);
2817 averageProbability /= resources;
2819 if (smallestAllocationProbablity == 0 ||
2820 averageProbability < smallestAllocationProbablity)
2821 smallestAllocationProbablity = averageProbability;
2823 overallAllocationProbability += smallestAllocationProbablity;
2825 /* New we normalize the allocationProbability to the duration of the
2826 * project (working days). For a resource that is statistically
2827 * allocated no more and no less than the number of working days in
2828 * the expected project time the probability will be one. This
2829 * certainly neglects many things like vacations, shifts, parallel
2830 * assignements and other factors. But we don't know enough about
2831 * these factors yet, to take them into account. So we have to live
2832 * with what we got. */
2833 overallAllocationProbability /=
2834 allocations.count() *
2835 ((project->getEnd() - project->getStart()) / (60.0 * 60 * 24)) *
2836 (project->getYearlyWorkingDays() / 365.0);
2837 /* Weight the average allocation probability with the effort of the
2838 * task. The higher the effort and the higher the probability that the
2839 * resources are allocated, the more critical the task rating gets. To
2840 * ensure that the criticalness is at least as high as a comparable
2841 * 'length' tasks use the effort value as a baseline and add the
2842 * weighted effort ontop. */
2843 scenarios[sc].criticalness = (1 + overallAllocationProbability) *
2844 scenarios[sc].effort;
2846 else if (scenarios[sc].duration > 0.0)
2847 scenarios[sc].criticalness = duration;
2848 else if (scenarios[sc].length > 0.0)
2849 scenarios[sc].criticalness = length *
2850 (365 / project->getYearlyWorkingDays());
2851 else if (isMilestone())
2853 /* People think of milestones usually as something important. So let's
2854 * assume a milestone has the importance of a full working day. This
2855 * is only done to raise the criticalness of pathes that contain
2857 scenarios[sc].criticalness = 1.0;
2860 scenarios[sc].criticalness = 0;
2865 Task::computePathCriticalness(int sc)
2868 * The path criticalness is a measure for the overall criticalness of the
2869 * task taking the dependencies into account. The fact that a task is part
2870 * of a chain of effort-based task raises all the task in the chain to a
2871 * higher criticalness level than the individual tasks. In fact, the path
2872 * criticalness of this chain is equal to the sum of the individual
2873 * criticalnesses of the tasks.
2875 * Since both the forward and backward functions include the
2876 * criticalness of this function we have to subtract it again.
2878 scenarios[sc].pathCriticalness = computeBackwardCriticalness(sc) -
2879 scenarios[sc].criticalness + computeForwardCriticalness(sc);
2883 Task::computeBackwardCriticalness(int sc)
2885 double maxCriticalness = 0.0;
2887 double criticalness;
2888 /* We only need to check the previous tasks of leaf tasks. Parent task
2889 * dependencies have been inherited by the sub tasks and will be checked
2892 for (TaskListIterator tli(previous); *tli; ++tli)
2893 if ((criticalness = (*tli)->computeBackwardCriticalness(sc)) >
2895 maxCriticalness = criticalness;
2898 ((criticalness = static_cast<Task*>
2899 (parent)->computeBackwardCriticalness(sc)) > maxCriticalness))
2900 maxCriticalness = criticalness;
2902 return scenarios[sc].criticalness + maxCriticalness;
2906 Task::computeForwardCriticalness(int sc)
2908 double maxCriticalness = 0.0;
2910 double criticalness;
2911 /* We only need to check the followers of leaf tasks. Parent task
2912 * dependencies have been inherited by the sub tasks and will be checked
2915 for (TaskListIterator tli(followers); *tli; ++tli)
2916 if ((criticalness = (*tli)->computeForwardCriticalness(sc)) >
2918 maxCriticalness = criticalness;
2921 ((criticalness = static_cast<Task*>(parent)->computeForwardCriticalness(sc)) >
2923 maxCriticalness = criticalness;
2925 return scenarios[sc].criticalness + maxCriticalness;
2929 Task::checkAndMarkCriticalPath(int sc, double minSlack, time_t maxEnd)
2931 // The algorithm has to start at a leaf task that has no predecessors.
2932 if (hasSubs() || !previous.isEmpty())
2936 qDebug("Starting critical path search at %s", id.latin1());
2938 long worstMinSlackTime = static_cast<long>((maxEnd - getStart(sc)) *
2942 analyzePath(sc, minSlack, getStart(sc), 0, worstMinSlackTime, checks,
2947 Task::analyzePath(int sc, double minSlack, time_t pathStart, long busyTime,
2948 long worstMinSlackTime, long& checks, long& found)
2950 /* Saveguard to limit the runtime for this NP hard algorithm. */
2951 long maxPaths = project->getScenario(sc)->getMaxPaths();
2952 if (maxPaths > 0 && checks >= maxPaths)
2956 qDebug(" * Checking task %s", id.latin1());
2958 bool critical = false;
2963 qDebug(" > Sub check started for %s", id.latin1());
2965 for (TaskListIterator tli(*sub); *tli; ++tli)
2966 if ((*tli)->analyzePath(sc, minSlack, pathStart, busyTime,
2967 worstMinSlackTime, checks, found))
2971 qDebug(" < Sub check finished for %s", id.latin1());
2975 busyTime += (getEnd(sc) + 1 - getStart(sc));
2977 /* If we have enough slack already that the path cannot be critical,
2978 * we stop looking at the rest of the path. */
2979 long currentSlack = (getEnd(sc) + 1 - pathStart) - busyTime;
2980 if (currentSlack > worstMinSlackTime)
2984 qDebug("Path cannot be critical. Stopping at task %s",
2989 /* Find out if any of the followers is a sibling of the parent of this
2991 bool hasBrotherFollower = false;
2992 for (TaskListIterator tli(followers); tli && !hasBrotherFollower; ++tli)
2993 for (Task* t = *tli; t; t = t->getParent())
2994 if (t == getParent())
2996 hasBrotherFollower = true;
3000 /* We first have to gather a list of all followers of this task. This
3001 * list must also include the followers registered for all parent
3002 * tasks of this task as they are followers as well. */
3003 TaskList allFollowers;
3004 for (Task* task = this; task; task = task->getParent())
3006 for (TaskListIterator tli(task->followers); *tli; ++tli)
3007 if (allFollowers.findRef(*tli) < 0)
3008 allFollowers.append(*tli);
3009 /* If the task has a follower that is a sibling of the same parent
3010 * we ignore the parent followers. */
3011 if (hasBrotherFollower)
3015 /* Get a list of all transient followers that follow the direct and
3016 * indirect followers of this task. If the allFollowers list contain
3017 * any of the transient followers, we can ignore it later. */
3018 TaskList transientFollowers;
3019 for (TaskListIterator tli(allFollowers); *tli; ++tli)
3020 (*tli)->collectTransientFollowers(transientFollowers);
3022 /* For inherited dependencies we only follow the bottommost task that
3023 * is a follower. All parents in the allFollowers list are ignored. */
3024 TaskList ignoreList;
3025 for (TaskListIterator tli(allFollowers); *tli; ++tli)
3026 for (Task* p = (*tli)->getParent(); p; p = p->getParent())
3027 if (allFollowers.findRef(p) >= 0 && ignoreList.findRef(p) < 0)
3028 ignoreList.append(p);
3030 /* Now we can check the paths through the remaining followers. */
3031 for (TaskListIterator tli(allFollowers); *tli; ++tli)
3033 if (ignoreList.findRef(*tli) >= 0 ||
3034 transientFollowers.findRef(*tli) >= 0)
3038 qDebug(" > Follower check started for %s",
3039 (*tli)->id.latin1());
3041 if ((*tli)->analyzePath(sc, minSlack, pathStart, busyTime,
3042 worstMinSlackTime, checks, found))
3044 if (scenarios[sc].criticalLinks.findRef(*tli) < 0)
3047 qDebug(" +++ Critical link %s -> %s",
3048 id.latin1(), (*tli)->id.latin1());
3049 scenarios[sc].criticalLinks.append(*tli);
3056 qDebug(" < Follower check finished for %s",
3057 (*tli)->id.latin1());
3060 if (allFollowers.isEmpty())
3062 // We've reached the end of a path. Now let's see if it's critical.
3063 long overallDuration = getEnd(sc) + 1 - pathStart;
3064 /* A path is considered critical if the ratio of busy time and
3065 * overall path time is above the minSlack threshold and the path
3066 * contains more than one task. */
3067 critical = overallDuration > 0 &&
3068 ((double) busyTime / overallDuration) > (1.0 - minSlack);
3073 qDebug("Critical path with %.2lf%% slack ending at %s "
3075 100.0 - ((double) busyTime / overallDuration) *
3076 100.0, id.latin1());
3081 qDebug("Path ending at %s is not critical", id.latin1());
3083 if (++checks == maxPaths)
3085 warningMessage(i18n("Maximum number of paths reached during "
3086 "critical path analysis. Set 'maxPaths' "
3087 "to 0 if you want an exhaustive search. "
3088 "Aborting critical paths detection."));
3091 if (checks % 100000 == 0 && DEBUGPA(1))
3092 qDebug("Already check %ld paths. %ld critical found.",
3098 scenarios[sc].isOnCriticalPath = true;
3101 qDebug(" - Check of task %s completed (%d)", id.latin1(), critical);
3106 Task::collectTransientFollowers(TaskList& list)
3110 for (TaskListIterator tli(followers); *tli; ++tli)
3111 if (list.findRef(*tli) < 0)
3114 (*tli)->collectTransientFollowers(list);
3119 for (Task* task = getParent(); task; task = task->getParent())
3120 for (TaskListIterator tli(task->followers); *tli; ++tli)
3121 if (list.findRef(*tli) < 0)
3124 (*tli)->collectTransientFollowers(list);
3130 Task::finishScenario(int sc)
3132 scenarios[sc].start = start;
3133 scenarios[sc].end = end;
3134 scenarios[sc].bookedResources = bookedResources;
3135 scenarios[sc].scheduled = schedulingDone;
3139 Task::computeBuffers()
3141 int sg = project->getScheduleGranularity();
3142 for (int sc = 0; sc < project->getMaxScenarios(); sc++)
3144 scenarios[sc].startBufferEnd = scenarios[sc].start - 1;
3145 scenarios[sc].endBufferStart = scenarios[sc].end + 1;
3147 if (scenarios[sc].start == 0 || scenarios[sc].end == 0)
3149 scenarios[sc].startBufferEnd = scenarios[sc].endBufferStart = 0;
3155 if (scenarios[sc].startBuffer > 0.0)
3156 scenarios[sc].startBufferEnd = scenarios[sc].start +
3157 static_cast<time_t>((scenarios[sc].end -
3158 scenarios[sc].start) *
3159 scenarios[sc].startBuffer / 100.0);
3160 if (scenarios[sc].endBuffer > 0.0)
3161 scenarios[sc].endBufferStart = scenarios[sc].end -
3162 static_cast<time_t>((scenarios[sc].end -
3163 scenarios[sc].start) *
3164 scenarios[sc].endBuffer / 100.0);
3166 else if (length > 0.0)
3169 if (scenarios[sc].startBuffer > 0.0)
3171 for (l = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
3172 scenarios[sc].startBufferEnd += sg)
3174 if (project->isWorkingDay(scenarios[sc].startBufferEnd))
3175 l += (double) sg / ONEDAY;
3176 if (l >= scenarios[sc].length *
3177 scenarios[sc].startBuffer / 100.0)
3181 if (scenarios[sc].endBuffer > 0.0)
3183 for (l = 0.0; scenarios[sc].endBufferStart >
3184 scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
3186 if (project->isWorkingDay(scenarios[sc].endBufferStart))
3187 l += (double) sg / ONEDAY;
3188 if (l >= scenarios[sc].length *
3189 scenarios[sc].endBuffer / 100.0)
3194 else if (effort > 0.0)
3197 if (scenarios[sc].startBuffer > 0.0)
3199 for (e = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
3200 scenarios[sc].startBufferEnd += sg)
3203 Interval(scenarios[sc].startBufferEnd,
3204 scenarios[sc].startBufferEnd + sg));
3205 if (e >= scenarios[sc].effort *
3206 scenarios[sc].startBuffer / 100.0)
3210 if (scenarios[sc].endBuffer > 0.0)
3212 for (e = 0.0; scenarios[sc].endBufferStart >
3213 scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
3216 Interval(scenarios[sc].endBufferStart - sg,
3217 scenarios[sc].endBufferStart));
3218 if (e >= scenarios[sc].effort *
3219 scenarios[sc].endBuffer / 100.0)
3228 Task::calcCompletionDegree(int sc)
3230 time_t now = project->getNow();
3232 /* In-progress container task are pretty complex to deal with. The mixture
3233 * of effort, length and duration tasks makes it impossible to use one
3234 * coherent criteria to determine the progress of a task. */
3235 if (isContainer() && scenarios[sc].start < now && now <= scenarios[sc].end)
3236 calcContainerCompletionDegree(sc, now);
3238 /* Calc completion for simple tasks and determine the task state. */
3239 scenarios[sc].calcCompletionDegree(now);
3243 Task::getCompletionDegree(int sc) const
3245 if(scenarios[sc].reportedCompletion >= 0.0)
3246 return(scenarios[sc].reportedCompletion);
3248 return isContainer() && scenarios[sc].containerCompletion >= 0.0 ?
3249 scenarios[sc].containerCompletion : scenarios[sc].completionDegree;
3253 Task::getCalcedCompletionDegree(int sc) const
3255 return scenarios[sc].completionDegree;
3259 Task::calcContainerCompletionDegree(int sc, time_t now)
3261 assert(isContainer());
3262 assert(scenarios[sc].start < now && now <= scenarios[sc].end);
3264 scenarios[sc].status = InProgress;
3266 int totalMilestones = 0;
3267 int completedMilestones = 0;
3268 int reportedCompletedMilestones = 0;
3269 if (countMilestones(sc, now, totalMilestones, completedMilestones,
3270 reportedCompletedMilestones))
3272 scenarios[sc].completionDegree = completedMilestones * 100.0 /
3274 scenarios[sc].containerCompletion = reportedCompletedMilestones
3275 * 100.0 / totalMilestones;
3279 double totalEffort = 0.0;
3280 double completedEffort = 0.0;
3281 double reportedCompletedEffort = 0.0;
3282 if (sumUpEffort(sc, now, totalEffort, completedEffort,
3283 reportedCompletedEffort))
3285 scenarios[sc].completionDegree = completedEffort * 100.0 /
3287 scenarios[sc].containerCompletion = reportedCompletedEffort * 100.0 /
3292 /* We can't determine the completion degree for mixed work/non-work
3293 * tasks. So we use -1.0 as "in progress" value. */
3295 if (scenarios[sc].start > now)
3296 comp = 0.0; // not yet started
3297 else if (scenarios[sc].end < now)
3298 comp = 100.0; // completed
3299 scenarios[sc].completionDegree =
3300 scenarios[sc].containerCompletion = comp;
3305 Task::getCompletedLoad(int sc) const
3307 return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
3308 getCompletionDegree(sc) / 100.0;
3312 Task::getRemainingLoad(int sc) const
3314 return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
3315 (1.0 - getCompletionDegree(sc) / 100.0);
3319 Task::countMilestones(int sc, time_t now, int& totalMilestones,
3320 int& completedMilestones,
3321 int& reportedCompletedMilestones)
3325 for (TaskListIterator tli(*sub); *tli; ++tli)
3326 if (!(*tli)->countMilestones(sc, now, totalMilestones,
3327 completedMilestones,
3328 reportedCompletedMilestones))
3331 /* A reported completion for a container always overrides the computed
3333 if (scenarios[sc].reportedCompletion >= 0.0)
3334 reportedCompletedMilestones = static_cast<int>(totalMilestones *
3335 scenarios[sc].reportedCompletion / 100.0);
3339 else if (isMilestone())
3342 if (scenarios[sc].start <= now)
3343 completedMilestones++;
3345 if (scenarios[sc].reportedCompletion >= 100.0)
3346 reportedCompletedMilestones++;
3348 if (scenarios[sc].start <= now)
3349 reportedCompletedMilestones++;
3358 Task::sumUpEffort(int sc, time_t now, double& totalEffort,
3359 double& completedEffort, double& reportedCompletedEffort)
3363 for (TaskListIterator tli(*sub); *tli; ++tli)
3364 if (!(*tli)->sumUpEffort(sc, now, totalEffort, completedEffort,
3365 reportedCompletedEffort))
3368 /* A reported completion for a container always overrides the computed
3370 if (scenarios[sc].reportedCompletion >= 0.0)
3371 reportedCompletedEffort = totalEffort *
3372 scenarios[sc].reportedCompletion / 100.0;
3376 if (scenarios[sc].effort > 0.0)
3378 /* Pure effort based tasks are simple to handle. The total effort is
3379 * specified and the effort up to 'now' can be computed. */
3380 totalEffort += scenarios[sc].effort;
3381 double load = getLoad(sc, Interval(scenarios[sc].start, now));
3382 if (scenarios[sc].start < now)
3383 completedEffort += load;
3385 /* If the user reported a completion we use this instead of the
3386 * calculated completion. */
3387 if (scenarios[sc].reportedCompletion >= 0.0)
3388 reportedCompletedEffort +=
3389 getLoad(sc, Interval(scenarios[sc].start, scenarios[sc].end)) *
3390 scenarios[sc].reportedCompletion / 100.0;
3392 reportedCompletedEffort += load;
3396 if (!allocations.isEmpty())
3398 /* This is for length and duration tasks that have allocations. We
3399 * handle them similar to effort tasks. Since there is no specified
3400 * total effort, we calculate the total allocated effort. */
3401 double totalLoad = getLoad(sc, Interval(scenarios[sc].start,
3402 scenarios[sc].end));
3403 totalEffort += totalLoad;
3404 double load = getLoad(sc, Interval(scenarios[sc].start, now));
3405 if (scenarios[sc].start < now)
3406 completedEffort += load;
3408 /* If the user reported a completion we use this instead of the
3409 * calculated completion. */
3410 if (scenarios[sc].reportedCompletion >= 0.0)
3411 reportedCompletedEffort += totalLoad *
3412 scenarios[sc].reportedCompletion / 100.0;
3414 reportedCompletedEffort += load;
3420 /* We assume that milestones are only dependent on sub tasks of this
3421 * tasks. So we can ignore them for the completion degree. In case
3422 * there is a non completed task that the milestone depends on, the
3423 * milestone is accounted for as well. This approximation should work
3424 * fine for most real world projects. */
3431 QDomElement Task::xmlElement( QDomDocument& doc, bool /* absId */ )
3433 QDomElement taskElem = doc.createElement( "Task" );
3434 QDomElement tempElem;
3436 QString idStr = getId();
3438 idStr = idStr.section( '.', -1 ); */
3440 taskElem.setAttribute( "Id", idStr );
3443 taskElem.appendChild( ReportXML::createXMLElem( doc, "Index", QString::number(getIndex()) ));
3444 taskElem.appendChild( ReportXML::createXMLElem( doc, "Name", getName() ));
3445 taskElem.appendChild( ReportXML::createXMLElem( doc, "ProjectID", projectId ));
3446 taskElem.appendChild( ReportXML::createXMLElem( doc, "Priority", QString::number(getPriority())));
3448 double cmplt = getCompletionDegree(0);
3449 taskElem.appendChild( ReportXML::createXMLElem( doc, "complete", QString::number(cmplt, 'f', 1) ));
3451 QString tType = "Milestone";
3452 if( !isMilestone() )
3455 tType = "Container";
3460 taskElem.appendChild( ReportXML::createXMLElem( doc, "Type", tType ));
3462 CoreAttributes *parent = getParent();
3464 taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
3466 if( !note.isEmpty())
3467 taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
3469 taskElem.appendChild(ReportXML::createXMLElem(doc, "Reference",
3471 if(!refLabel.isEmpty())
3472 taskElem.appendChild(ReportXML::createXMLElem(doc, "ReferenceLabel",
3475 if (scenarios[0].minStart != 0)
3477 tempElem = ReportXML::createXMLElem
3478 ( doc, "minStart", QString::number(scenarios[0].minStart));
3479 tempElem.setAttribute( "humanReadable",
3480 time2ISO(scenarios[0].minStart));
3481 taskElem.appendChild( tempElem );
3484 if (scenarios[0].maxStart != 0)
3486 tempElem = ReportXML::createXMLElem
3487 (doc, "maxStart", QString::number(scenarios[0].maxStart));
3488 tempElem.setAttribute( "humanReadable",
3489 time2ISO(scenarios[0].maxStart));
3490 taskElem.appendChild( tempElem );
3493 if (scenarios[0].minEnd != 0)
3495 tempElem = ReportXML::createXMLElem
3496 (doc, "minEnd", QString::number(scenarios[0].minEnd));
3497 tempElem.setAttribute( "humanReadable",
3498 time2ISO(scenarios[0].minEnd));
3499 taskElem.appendChild( tempElem );
3502 if (scenarios[0].maxEnd != 0)
3504 tempElem = ReportXML::createXMLElem
3505 (doc, "maxEnd", QString::number(scenarios[0].maxEnd));
3506 tempElem.setAttribute( "humanReadable",
3507 time2ISO(scenarios[0].maxEnd));
3508 taskElem.appendChild( tempElem );
3510 if (project->getMaxScenarios() > 1)
3512 tempElem = ReportXML::createXMLElem( doc, "actualStart",
3513 QString::number(scenarios[1].start));
3514 tempElem.setAttribute( "humanReadable",
3515 time2ISO(scenarios[1].start));
3516 taskElem.appendChild( tempElem );
3518 tempElem = ReportXML::createXMLElem( doc, "actualEnd",
3519 QString::number(scenarios[1].end + 1));
3520 tempElem.setAttribute( "humanReadable",
3521 time2ISO(scenarios[1].end + 1));
3522 taskElem.appendChild( tempElem );
3525 tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( scenarios[0].start ));
3526 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].start ));
3527 taskElem.appendChild( tempElem );
3529 tempElem = ReportXML::createXMLElem( doc, "planEnd",
3530 QString::number(scenarios[0].end + 1));
3531 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].end + 1));
3532 taskElem.appendChild( tempElem );
3534 /* Start- and Endbuffer */
3535 if(getStartBuffer(0) > 0.01)
3537 /* startbuffer exists */
3538 tempElem = ReportXML::createXMLElem
3539 (doc, "startBufferSize",
3540 QString::number(getStartBuffer(0)));
3541 taskElem.appendChild( tempElem );
3543 tempElem = ReportXML::createXMLElem
3544 (doc, "PlanStartBufferEnd",
3545 QString::number(getStartBufferEnd(0)));
3546 tempElem.setAttribute("humanReadable",
3547 time2ISO(getStartBufferEnd(0)));
3548 taskElem.appendChild(tempElem);
3550 tempElem = ReportXML::createXMLElem
3551 (doc, "PlanStartBufferEnd",
3552 QString::number(getStartBufferEnd(0)));
3553 tempElem.setAttribute("humanReadable",
3554 time2ISO(getStartBufferEnd(0)));
3555 taskElem.appendChild(tempElem);
3558 if(getEndBuffer(0) > 0.01)
3560 /* startbuffer exists */
3561 tempElem = ReportXML::createXMLElem
3562 (doc, "EndBufferSize", QString::number(getEndBuffer(0)));
3563 taskElem.appendChild(tempElem);
3565 tempElem = ReportXML::createXMLElem
3566 (doc, "PlanEndBufferStart",
3567 QString::number(getEndBufferStart(0)));
3568 tempElem.setAttribute("humanReadable",
3569 time2ISO(getEndBufferStart(0)));
3570 taskElem.appendChild(tempElem);
3572 tempElem = ReportXML::createXMLElem
3573 (doc, "PlanEndBufferStart",
3574 QString::number(getEndBufferStart(0)));
3575 tempElem.setAttribute("humanReadable",
3576 time2ISO(getStartBufferEnd(0)));
3577 taskElem.appendChild(tempElem);
3580 /* Responsible persons */
3581 if( getResponsible() )
3582 taskElem.appendChild( getResponsible()->xmlIDElement( doc ));
3584 /* Now start the subtasks */
3586 QDomElement subTaskElem = doc.createElement( "SubTasks" );
3587 for (Task* t = subFirst(); t != 0; t = subNext())
3591 QDomElement sTask = t->xmlElement( doc, false );
3592 subTaskElem.appendChild( sTask );
3597 taskElem.appendChild( subTaskElem);
3599 /* list of tasks by id which are previous */
3600 if( previous.count() > 0 )
3602 for (TaskListIterator tli(previous); *tli != 0; ++tli)
3606 taskElem.appendChild( ReportXML::createXMLElem( doc, "Previous",
3612 /* list of tasks by id which follow */
3613 if( followers.count() > 0 )
3615 for (TaskListIterator tli(followers); *tli != 0; ++tli)
3619 taskElem.appendChild( ReportXML::createXMLElem( doc, "Follower",
3625 /** Allocations and Booked Resources
3626 * With the following code, the task in XML contains simply a list of all Allocations
3627 * wiht the ResourceID for which resource the allocation is. After that, there comes
3628 * a list of all Resources, again having the Resource Id as key. That could be put
3629 * in a hirarchy like
3630 * <Resource Id="dev2" >Larry Bono
3631 * <Income>1000</Income>
3634 * <Persistent>Yes</Persistent>
3638 * But we do not ;-) to have full flexibility.
3642 if( allocations.count() > 0 )
3644 QPtrList<Allocation> al(allocations);
3645 for (QPtrListIterator<Allocation> ali(al); *ali != 0; ++ali)
3647 taskElem.appendChild( (*ali)->xmlElement( doc ));
3651 /* booked Ressources */
3652 if( bookedResources.count() > 0 )
3654 for (ResourceListIterator rli(bookedResources); *rli != 0; ++rli)
3656 taskElem.appendChild( (*rli)->xmlIDElement( doc ));