2 * task.cpp - TaskJuggler
4 * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of version 2 of the GNU General Public License as
8 * published by the Free Software Foundation.
14 <!-- Task element, child of projects and for subtasks -->
15 <!ELEMENT Task (Index, Name, ProjectID, Priority, complete,
20 actualStart, actualEnd,
22 startBufferSize*, ActualStartBufferEnd*, PlanStartBufferEnd*,
23 endBufferSize*, ActualEndBufferStart*, PlanEndBufferStart*,
25 SubTasks*, Previous*, Follower*,
26 Allocations*, bookedResources* )>
27 <!ATTLIST Task Id CDATA #REQUIRED>
28 <!ELEMENT Index (#PCDATA)>
29 <!ELEMENT Name (#PCDATA)>
30 <!ELEMENT ProjectID (#PCDATA)>
31 <!ELEMENT Priority (#PCDATA)>
32 <!ELEMENT complete (#PCDATA)>
33 <!ELEMENT Type (#PCDATA)>
34 <!ELEMENT ParentTask (#PCDATA)>
35 <!ELEMENT Note (#PCDATA)>
36 <!ELEMENT minStart (#PCDATA)>
37 <!ELEMENT maxStart (#PCDATA)>
38 <!ELEMENT minEnd (#PCDATA)>
39 <!ELEMENT maxEnd (#PCDATA)>
40 <!ELEMENT actualStart (#PCDATA)>
41 <!ELEMENT actualEnd (#PCDATA)>
42 <!ELEMENT planStart (#PCDATA)>
43 <!ELEMENT planEnd (#PCDATA)>
44 <!ELEMENT startBufferSize (#PCDATA)>
45 <!ELEMENT ActualStartBufferEnd (#PCDATA)>
46 <!ELEMENT PlanStartBufferEnd (#PCDATA)>
47 <!ELEMENT endBufferSize (#PCDATA)>
48 <!ELEMENT ActualEndBufferStart (#PCDATA)>
49 <!ELEMENT PlanEndBufferStart (#PCDATA)>
50 <!ELEMENT SubTasks (Task+)>
51 <!ELEMENT Previous (#PCDATA)>
52 <!ELEMENT Follower (#PCDATA)>
53 <!ELEMENT bookedResources (ResourceID+)>
55 <!-- Date values contain human readable date -->
57 humanReadable CDATA #REQUIRED>
59 humanReadable CDATA #REQUIRED>
61 humanReadable CDATA #REQUIRED>
63 humanReadable CDATA #REQUIRED>
65 humanReadable CDATA #REQUIRED>
67 humanReadable CDATA #REQUIRED>
69 humanReadable CDATA #REQUIRED>
71 humanReadable CDATA #REQUIRED>
72 <!ATTLIST ActualStartBufferEnd
73 humanReadable CDATA #REQUIRED>
74 <!ATTLIST PlanStartBufferEnd
75 humanReadable CDATA #REQUIRED>
76 <!ATTLIST ActualEndBufferStart
77 humanReadable CDATA #REQUIRED>
78 <!ATTLIST PlanEndBufferStart
79 humanReadable CDATA #REQUIRED>
89 #include "Allocation.h"
91 int Task::debugLevel = 0;
92 int Task::debugMode = -1;
95 TaskList::getTask(const QString& id)
97 for (Task* t = first(); t != 0; t = next())
104 Task::Task(Project* proj, const QString& id_, const QString& n, Task* p,
105 const QString& f, int l)
106 : CoreAttributes(proj, id_, n, p), file(f), line(l)
108 allocations.setAutoDelete(TRUE);
117 schedulingDone = FALSE;
120 scenarios[0].startBuffer = 0.0;
121 scenarios[0].endBuffer = 0.0;
122 scenarios[0].startCredit = 0.0;
123 scenarios[0].endCredit = 0.0;
127 // Inherit flags from parent task.
128 for (QStringList::Iterator it = p->flags.begin();
129 it != p->flags.end(); ++it)
132 // Set attributes that are inherited from parent task.
133 projectId = p->projectId;
134 priority = p->priority;
135 minStart = p->minStart;
136 maxStart = p->maxEnd;
137 minEnd = p->minStart;
139 responsible = p->responsible;
140 account = p->account;
141 scheduling = p->scheduling;
143 // Inherit depends from parent. Relative IDs need to get another '!'.
144 dependsIds = p->dependsIds;
145 for (QStringList::Iterator it = dependsIds.begin();
146 it != dependsIds.end(); ++it)
152 // Inherit precedes from parent. Relative IDs need to get another '!'.
153 precedesIds = p->precedesIds;
154 for (QStringList::Iterator it = precedesIds.begin();
155 it != precedesIds.end(); ++it)
163 // Set attributes that are inherited from global attributes.
164 projectId = proj->getCurrentId();
165 priority = proj->getPriority();
166 minStart = minEnd = proj->getStart();
167 maxStart = maxEnd = proj->getEnd();
171 duration = length = effort = 0.0;
175 Task::addDepends(const QString& rid)
177 dependsIds.append(rid);
182 Task::addPrecedes(const QString& rid)
184 precedesIds.append(rid);
189 Task::fatalError(const char* msg, ...) const
194 vsnprintf(buf, 1024, msg, ap);
197 qWarning("%s:%d:%s\n", file.latin1(), line, buf);
201 Task::schedule(time_t& date, time_t slotDuration)
203 // Has the task been scheduled already or is it a container?
204 if (schedulingDone || !sub.isEmpty())
208 qWarning("Trying to schedule %s at %s",
209 id.latin1(), time2tjp(date).latin1());
211 if (scheduling == Task::ASAP)
217 lastSlot = start - 1;
222 tentativeEnd = date + slotDuration - 1;
224 qWarning("Scheduling of %s starts at %s (%s)",
225 id.latin1(), time2tjp(lastSlot).latin1(),
226 time2tjp(date).latin1());
228 /* Do not schedule anything if the time slot is not directly
229 * following the time slot that was previously scheduled. */
230 if (!((date - slotDuration <= lastSlot) && (lastSlot < date)))
232 lastSlot = date + slotDuration - 1;
245 tentativeStart = date;
247 qWarning("Scheduling of ALAP task %s starts at %s (%s)",
248 id.latin1(), time2tjp(lastSlot).latin1(),
249 time2tjp(date).latin1());
251 /* Do not schedule anything if the current time slot is not
252 * directly preceeding the previously scheduled time slot. */
253 if (!((date + slotDuration <= lastSlot) &&
254 (lastSlot < date + 2 * slotDuration)))
260 qWarning("Scheduling %s at %s",
261 id.latin1(), time2tjp(date).latin1());
263 if ((duration > 0.0) || (length > 0.0))
265 /* Length specifies the number of working days (as daily load)
266 * and duration specifies the number of calender days. */
267 if (!allocations.isEmpty())
268 bookResources(date, slotDuration);
270 doneDuration += ((double) slotDuration) / ONEDAY;
271 if (project->isWorkingDay(date))
272 doneLength += ((double) slotDuration) / ONEDAY;
275 qWarning("Length: %f/%f Duration: %f/%f",
277 doneDuration, duration);
278 // Check whether we are done with this task.
279 if ((length > 0.0 && doneLength >= length * 0.999999) ||
280 (duration > 0.0 && doneDuration >= duration * 0.999999))
282 if (scheduling == ASAP)
284 if (doneEffort > 0.0)
287 date = end - slotDuration + 1;
290 end = date + slotDuration - 1;
295 if (doneEffort > 0.0)
297 start = tentativeStart;
304 schedulingDone = TRUE;
308 else if (effort > 0.0)
310 /* The effort of the task has been specified. We have to look
311 * how much the resources can contribute over the following
312 * workings days until we have reached the specified
314 bookResources(date, slotDuration);
315 // Check whether we are done with this task.
316 if (doneEffort >= effort)
318 if (scheduling == ASAP)
325 start = tentativeStart;
328 schedulingDone = TRUE;
334 // Task is a milestone.
335 if (scheduling == ASAP)
347 else if (start != 0 && end != 0)
349 // Task with start and end date but no duration criteria.
350 if (!allocations.isEmpty() && !project->isVacation(date))
351 bookResources(date, slotDuration);
353 if ((scheduling == ASAP && (date + slotDuration) >= end) ||
354 (scheduling == ALAP && date <= start))
356 schedulingDone = TRUE;
365 Task::scheduleContainer(bool safeMode)
374 // Check that this is really a container task
375 if ((t = subFirst()))
377 if (t->start == 0 || t->end == 0)
385 for (t = subNext() ; t != 0; t = subNext())
387 /* Make sure that all sub tasks have been scheduled. If not we
388 * can't yet schedule this task. */
389 if (t->start == 0 || t->end == 0)
392 if (t->start < nstart)
398 if (start == 0 || (!depends.isEmpty() && start < nstart))
401 propagateStart(safeMode);
404 if (end == 0 || (!precedes.isEmpty() && nend < end))
407 propagateEnd(safeMode);
410 schedulingDone = TRUE;
416 Task::propagateStart(bool safeMode)
422 qWarning("PS1: Setting start of %s to %s",
423 id.latin1(), time2tjp(start).latin1());
425 /* If one end of a milestone is fixed, then the other end can be set as
427 if (milestone && end == 0)
430 schedulingDone = TRUE;
431 propagateEnd(safeMode);
434 /* Set start date to all previous that have no start date yet, but are
435 * ALAP task or have no duration. */
436 for (Task* t = previous.first(); t != 0; t = previous.next())
437 if (t->end == 0 && t->latestEnd() != 0 &&
438 (t->scheduling == ALAP ||
439 (t->effort == 0.0 && t->length == 0.0 && t->duration == 0.0 &&
442 t->end = t->latestEnd();
444 qWarning("PS2: Setting end of %s to %s",
445 t->id.latin1(), time2tjp(t->end).latin1());
446 /* Recursively propagate the end date */
447 t->propagateEnd(safeMode);
450 /* Propagate start time to sub tasks which have only an implicit
451 * dependancy on the parent task. Do not touch container tasks. */
452 for (Task* t = subFirst(); t != 0; t = subNext())
454 if (t->start == 0 && t->previous.isEmpty() &&
455 t->sub.isEmpty() && t->scheduling == ASAP)
459 qWarning("PS3: Setting start of %s to %s",
460 t->id.latin1(), time2tjp(t->start).latin1());
461 /* Recursively propagate the start date */
462 t->propagateStart(safeMode);
466 if (safeMode && parent)
467 getParent()->scheduleContainer(TRUE);
471 Task::propagateEnd(bool safeMode)
477 qWarning("PE1: Setting end of %s to %s",
478 id.latin1(), time2tjp(end).latin1());
480 /* If one end of a milestone is fixed, then the other end can be set as
482 if (milestone && start == 0)
485 schedulingDone = TRUE;
486 propagateStart(safeMode);
489 /* Set start date to all followers that have no start date yet, but are
490 * ASAP task or have no duration. */
491 for (Task* t = followers.first(); t != 0; t = followers.next())
492 if (t->start == 0 && t->earliestStart() != 0 &&
493 (t->scheduling == ASAP ||
494 (t->effort == 0.0 && t->length == 0.0 && t->duration == 0.0 &&
497 t->start = t->earliestStart();
499 qWarning("PE2: Setting start of %s to %s",
500 t->id.latin1(), time2tjp(t->start).latin1());
501 /* Recursively propagate the start date */
502 t->propagateStart(safeMode);
504 /* Propagate end time to sub tasks which have only an implicit
505 * dependancy on the parent task. Do not touch container tasks. */
506 for (Task* t = subFirst(); t != 0; t = subNext())
507 if (t->end == 0 && t->followers.isEmpty() &&
508 t->sub.isEmpty() && t->scheduling == ALAP)
512 qWarning("PE3: Setting end of %s to %s",
513 t->id.latin1(), time2tjp(t->end).latin1());
514 /* Recursively propagate the end date */
515 t->propagateEnd(safeMode);
518 if (safeMode && parent)
519 getParent()->scheduleContainer(TRUE);
523 Task::propagateInitialValues()
526 propagateStart(FALSE);
529 // Check if the some data of sub tasks can already be propagated.
531 scheduleContainer(TRUE);
537 schedulingDone = TRUE;
544 /* If a container task has runaway sub tasts, it is very likely that they
545 * are the culprits. So we don't report such a container task as runaway.
547 for (Task* t = subFirst(); t; t = subNext())
555 Task::bookResources(time_t date, time_t slotDuration)
557 bool allocFound = FALSE;
559 /* If the time slot overlaps with a specified shift interval, the
560 * time slot must also be within the specified working hours of that
562 if (!shifts.isOnShift(Interval(date, date + slotDuration - 1)))
565 qDebug("Task %s is not active at %s", id.latin1(),
566 time2tjp(date).latin1());
570 for (Allocation* a = allocations.first();
571 a != 0 && (effort == 0.0 || doneEffort < effort);
572 a = allocations.next())
574 /* If a shift has been defined for a resource for this task, there
575 * must be a shift interval defined for this day and the time must
576 * be within the working hours of that shift. */
577 if (!a->isOnShift(Interval(date, date + slotDuration - 1)))
580 qDebug("Allocation not on shift at %s",
581 time2tjp(date).latin1());
584 /* If the allocation has be marked persistent and a resource
585 * has already been picked, try to book this resource again. If the
586 * resource is not available there will be no booking for this
588 if (a->isPersistent() && a->getLockedResource())
589 bookResource(a->getLockedResource(), date, slotDuration,
593 QPtrList<Resource> cl = createCandidateList(date, a);
594 for (Resource* r = cl.first(); r != 0; r = cl.next())
595 if (bookResource(r, date, slotDuration, a->getLoad()))
598 a->setLockedResource(r);
607 Task::bookResource(Resource* r, time_t date, time_t slotDuration,
611 double intervalLoad = project->convertToDailyLoad(slotDuration);
613 for (Resource* rit = r->subResourcesFirst(); rit != 0;
614 rit = r->subResourcesNext())
616 if ((*rit).isAvailable(date, slotDuration, loadFactor, this))
618 (*rit).book(new Booking(
619 Interval(date, date + slotDuration - 1), this,
620 account ? account->getKotrusId() : QString(""),
622 addBookedResource(rit);
624 /* Move the start date to make sure that there is
625 * some work going on at the start date. */
628 if (scheduling == ASAP)
630 else if (scheduling == ALAP)
631 end = date + slotDuration - 1;
633 qFatal("Unknown scheduling mode");
637 tentativeStart = date;
638 tentativeEnd = date + slotDuration - 1;
639 doneEffort += intervalLoad * (*rit).getEfficiency();
642 qDebug(" Booked resource %s (Effort: %f)",
643 (*rit).getId().latin1(), doneEffort);
651 Task::createCandidateList(time_t date, Allocation* a)
653 /* This function generates a list of resources that could be allocated to
654 * the task. The order of the list is determined by the specified
655 * selection function of the alternatives list. From this list, the
656 * first available resource is picked later on. */
657 QPtrList<Resource> candidates = a->getCandidates();
658 QPtrList<Resource> cl;
660 /* We try to minimize resource changes for consecutives time slots. So
661 * the resource used for the previous time slot is put to the 1st position
663 if (a->getLockedResource())
665 cl.append(a->getLockedResource());
666 candidates.remove(a->getLockedResource());
667 /* When an allocation is booked the resource is saved as locked
669 a->setLockedResource(0);
671 switch (a->getSelectionMode())
673 case Allocation::order:
674 while (candidates.first())
676 cl.append(candidates.first());
677 candidates.remove(candidates.first());
680 case Allocation::minLoaded:
682 while (!candidates.isEmpty())
685 Resource* minLoaded = 0;
686 for (Resource* r = candidates.first(); r != 0;
687 r = candidates.next())
690 r->getCurrentLoad(Interval(project->getStart(),
693 if (minLoaded == 0 || load < minLoad)
699 cl.append(minLoaded);
700 candidates.remove(minLoaded);
704 case Allocation::maxLoaded:
706 while (!candidates.isEmpty())
709 Resource* maxLoaded = 0;
710 for (Resource* r = candidates.first(); r != 0;
711 r = candidates.next())
714 r->getCurrentLoad(Interval(project->getStart(),
717 if (maxLoaded == 0 || load > maxLoad)
723 cl.append(maxLoaded);
724 candidates.remove(maxLoaded);
728 case Allocation::random:
730 while (candidates.first())
732 cl.append(candidates.at(rand() % candidates.count()));
733 candidates.remove(candidates.first());
738 qFatal("Illegal selection mode %d", a->getSelectionMode());
745 Task::isCompleted(int sc, time_t date) const
747 if (scenarios[sc].complete != -1)
749 // some completion degree was specified.
750 return ((scenarios[sc].complete / 100.0) *
751 (scenarios[sc].end - scenarios[sc].start)
752 + scenarios[sc].start) > date;
756 return (project->getNow() > date);
760 Task::earliestStart()
763 for (Task* t = depends.first(); t != 0; t = depends.next())
765 // All tasks this task depends on must have an end date set.
781 for (Task* t = precedes.first(); t != 0; t = precedes.next())
783 // All tasks this task preceeds must have a start date set.
786 if (date == 0 || t->start < date)
796 Task::getCalcDuration(int sc) const
798 time_t delta = scenarios[sc].end - scenarios[sc].start;
800 return (project->convertToDailyLoad(delta));
802 return (double) delta / ONEDAY;
806 Task::getLoad(int sc, const Interval& period, Resource* resource)
812 for (Task* t = subFirst(); t != 0; t = subNext())
813 load += t->getLoad(sc, period, resource);
817 load += resource->getLoad(sc, period, this);
819 for (Resource* r = scenarios[sc].bookedResources.first(); r != 0;
820 r = scenarios[sc].bookedResources.next())
821 load += r->getLoad(sc, period, this);
827 Task::getCredits(int sc, const Interval& period, Resource* resource,
830 double credits = 0.0;
832 if (recursive && subFirst())
834 for (Task* t = subFirst(); t != 0; t = subNext())
835 credits += t->getCredits(sc, period, resource, recursive);
839 credits += resource->getCredits(sc, period, this);
841 for (Resource* r = scenarios[sc].bookedResources.first(); r != 0;
842 r = scenarios[sc].bookedResources.next())
843 credits += r->getCredits(sc, period, this);
845 if (period.contains(scenarios[sc].start))
846 credits += scenarios[sc].startCredit;
847 if (period.contains(scenarios[sc].end))
848 credits += scenarios[sc].endCredit;
854 Task::xRef(QDict<Task>& hash)
859 qDebug("Creating cross references for task %s ...", id.latin1());
861 for (QStringList::Iterator it = dependsIds.begin();
862 it != dependsIds.end(); ++it)
864 QString absId = resolveId(*it);
866 if ((t = hash.find(absId)) == 0)
868 fatalError(QString("Unknown dependency '") + absId + "'");
871 else if (depends.find(t) != -1)
873 fatalError(QString("No need to specify dependency %1 multiple "
874 "times.").arg(absId));
875 // Make it a warning only for the time beeing.
882 t->followers.append(this);
884 qDebug("Registering follower %s with task %s",
885 id.latin1(), t->getId().latin1());
889 for (QStringList::Iterator it = precedesIds.begin();
890 it != precedesIds.end(); ++it)
892 QString absId = resolveId(*it);
894 if ((t = hash.find(absId)) == 0)
896 fatalError(QString("Unknown dependency '") + absId + "'");
899 else if (precedes.find(t) != -1)
901 fatalError(QString("No need to specify dependency '") + absId +
909 t->previous.append(this);
911 qDebug("Registering predecessor %s with task %s",
912 id.latin1(), t->getId().latin1());
922 /* Propagate implicit dependencies. If a task has no specified start or
923 * end date and no start or end dependencies, we check if a parent task
924 * has an explicit start or end date which can be used. */
925 if (!sub.isEmpty() || milestone)
928 bool planDurationSpec = scenarios[0].duration > 1 ||
929 scenarios[0].length > 0 || scenarios[0].effort > 0;
930 bool actualDurationSpec = scenarios[1].duration > 0 ||
931 scenarios[1].length > 0 || scenarios[1].effort > 0 || planDurationSpec;
933 if ((scenarios[0].start == 0 || scenarios[1].start == 0) &&
935 for (Task* tp = getParent(); tp; tp = tp->getParent())
937 if (tp->scenarios[0].start != 0 && scenarios[0].start == 0 &&
938 (scheduling == ASAP || !planDurationSpec))
941 qDebug("Setting plan start of %s to %s", id.latin1(),
942 time2ISO(tp->scenarios[0].start).latin1());
943 scenarios[0].start = tp->scenarios[0].start;
945 if (tp->scenarios[1].start != 0 && scenarios[1].start == 0 &&
946 (scheduling == ASAP || !actualDurationSpec))
949 qDebug("Setting actual start of %s to %s", id.latin1(),
950 time2ISO(tp->scenarios[1].start).latin1());
951 scenarios[1].start = tp->scenarios[1].start;
954 /* And the same for end values */
955 if ((scenarios[0].end == 0 || scenarios[1].end == 0) && precedes.isEmpty())
956 for (Task* tp = getParent(); tp; tp = tp->getParent())
958 if (tp->scenarios[0].end != 0 && scenarios[0].end == 0 &&
959 (scheduling == ALAP || !planDurationSpec))
962 qDebug("Setting plan end of %s to %s", id.latin1(),
963 time2ISO(tp->scenarios[0].end).latin1());
964 scenarios[0].end = tp->scenarios[0].end;
966 if (tp->scenarios[1].end != 0 && scenarios[1].end == 0 &&
967 (scheduling == ALAP || !actualDurationSpec))
970 qDebug("Setting actual end of %s to %s", id.latin1(),
971 time2ISO(tp->scenarios[1].end).latin1());
972 scenarios[1].end = tp->scenarios[1].end;
978 Task::hasYoungerBrother()
980 bool previousHasSameParent = FALSE;
981 int previousIndex = previous.at();
982 for (Task* p = previous.first();
983 p && !previousHasSameParent;
986 if (parent == p->parent)
987 previousHasSameParent = TRUE;
989 previous.at(previousIndex);
990 return previousHasSameParent;
994 Task::hasOlderBrother()
996 bool followerHasSameParent = FALSE;
997 int followersIndex = followers.at();
998 for (Task* f = followers.first();
999 f && !followerHasSameParent;
1000 f = followers.next())
1002 if (parent == f->parent)
1003 followerHasSameParent = TRUE;
1006 followers.at(followersIndex);
1007 return followerHasSameParent;
1010 Task::loopDetector()
1012 /* Only check top-level tasks. All other tasks will be checked then as
1017 qWarning("Running loop detector for task %s", id.latin1());
1019 if (loopDetection(LDIList(), FALSE, LoopDetectorInfo::fromParent))
1022 if (loopDetection(LDIList(), TRUE, LoopDetectorInfo::fromParent))
1028 Task::loopDetection(LDIList list, bool atEnd, LoopDetectorInfo::FromWhere
1032 qWarning("%sloopDetection at %s (%s)",
1033 QString().fill(' ', list.count()).latin1(), id.latin1(),
1034 atEnd ? "End" : "Start");
1036 LoopDetectorInfo thisTask(this, atEnd);
1038 /* If we find the current task (with same position) in the list, we have
1039 * detected a loop. */
1040 LDIList::iterator it;
1041 if ((it = list.find(thisTask)) != list.end())
1044 for ( ; it != list.end(); ++it)
1046 loopChain += QString("%1 (%2) -> ")
1047 .arg((*it).getTask()->getId())
1048 .arg((*it).getAtEnd() ? "End" : "Start");
1050 (*it).getTask()->fatalError("%s (%s) is part of loop",
1051 (*it).getTask()->getId().latin1(),
1056 loopChain += QString("%1 (%2)").arg(id)
1057 .arg(atEnd ? "End" : "Start");
1058 fatalError("Dependency loop detected: %s", loopChain.latin1());
1061 list.append(thisTask);
1063 /* Now we have to traverse the graph in the direction of the specified
1064 * dependencies. 'precedes' and 'depends' specify dependencies in the
1065 * opposite direction of the flow of the tasks. So we have to make sure
1066 * that we do not follow the arcs in the direction that precedes and
1067 * depends points us. Parent/Child relationships also specify a
1068 * dependency. The scheduling mode of the child determines the direction
1069 * of the flow. With help of the 'caller' parameter we make sure that we
1070 * only visit childs if we were referred to the task by a non-parent-child
1074 CoreAttributesList subCopy = sub;
1075 if (caller == LoopDetectorInfo::fromPrev ||
1076 caller == LoopDetectorInfo::fromParent)
1077 /* If we were not called from a sub task we check all sub tasks.*/
1078 for (Task* t = (Task*) subCopy.first(); t;
1079 t = (Task*) subCopy.next())
1082 qWarning("%sChecking sub task %s of %s",
1083 QString().fill(' ', list.count()).latin1(),
1084 t->getId().latin1(),
1086 if (t->loopDetection(list, FALSE,
1087 LoopDetectorInfo::fromParent))
1091 if (scheduling == ASAP && sub.isEmpty())
1093 /* Leaf task are followed in their scheduling direction. So we
1094 * move from the task start to the task end. */
1096 qWarning("%sChecking end of task %s",
1097 QString().fill(' ', list.count()).latin1(),
1099 if (loopDetection(list, TRUE, LoopDetectorInfo::fromOtherEnd))
1102 if (caller == LoopDetectorInfo::fromSub ||
1103 caller == LoopDetectorInfo::fromOtherEnd ||
1104 (caller == LoopDetectorInfo::fromPrev && scheduling == ALAP))
1109 qWarning("%sChecking parent task of %s",
1110 QString().fill(' ', list.count()).latin1(),
1112 if (getParent()->loopDetection(list, FALSE,
1113 LoopDetectorInfo::fromSub))
1117 /* Now check all previous tasks that had explicit precedes on this
1119 CoreAttributesList previousCopy = previous;
1120 for (Task* t = (Task*) previousCopy.first(); t;
1121 t = (Task*) previousCopy.next())
1122 if (t->precedes.find(this) != -1)
1125 qWarning("%sChecking previous %s of task %s",
1126 QString().fill(' ', list.count()).latin1(),
1127 t->getId().latin1(), id.latin1());
1128 if(t->loopDetection(list, TRUE, LoopDetectorInfo::fromSucc))
1135 CoreAttributesList subCopy = sub;
1136 if (caller == LoopDetectorInfo::fromSucc ||
1137 caller == LoopDetectorInfo::fromParent)
1138 /* If we were not called from a sub task we check all sub tasks.*/
1139 for (Task* t = (Task*) subCopy.first(); t;
1140 t = (Task*) subCopy.next())
1143 qWarning("%sChecking sub task %s of %s",
1144 QString().fill(' ', list.count()).latin1(),
1145 t->getId().latin1(), id.latin1());
1146 if (t->loopDetection(list, TRUE,
1147 LoopDetectorInfo::fromParent))
1151 if (scheduling == ALAP && sub.isEmpty())
1153 /* Leaf task are followed in their scheduling direction. So we
1154 * move from the task end to the task start. */
1156 qWarning("%sChecking start of task %s",
1157 QString().fill(' ', list.count()).latin1(),
1159 if (loopDetection(list, FALSE, LoopDetectorInfo::fromOtherEnd))
1162 if (caller == LoopDetectorInfo::fromOtherEnd ||
1163 caller == LoopDetectorInfo::fromSub ||
1164 (caller == LoopDetectorInfo::fromSucc && scheduling == ASAP))
1169 qWarning("%sChecking parent task of %s",
1170 QString().fill(' ', list.count()).latin1(),
1172 if (getParent()->loopDetection(list, TRUE,
1173 LoopDetectorInfo::fromSub))
1177 /* Now check all following tasks that have explicit depends on this
1179 CoreAttributesList followersCopy = followers;
1180 for (Task* t = (Task*) followersCopy.first(); t;
1181 t = (Task*) followersCopy.next())
1182 if (t->depends.find(this) != -1)
1185 qWarning("%sChecking follower %s of task %s",
1186 QString().fill(' ', list.count()).latin1(),
1187 t->getId().latin1(), id.latin1());
1188 if (t->loopDetection(list, FALSE,
1189 LoopDetectorInfo::fromPrev))
1196 qWarning("%sNo loops found in %s (%s)",
1197 QString().fill(' ', list.count()).latin1(),
1198 id.latin1(), atEnd ? "End" : "Start");
1203 Task::resolveId(QString relId)
1205 /* Converts a relative ID to an absolute ID. Relative IDs start
1206 * with a number of bangs. A set of bangs means 'Name of the n-th
1207 * parent task' with n being the number of bangs. */
1208 if (relId[0] != '!')
1213 for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
1217 fatalError(QString("Illegal relative ID '") + relId + "'");
1223 return t->id + "." + relId.right(relId.length() - i);
1225 return relId.right(relId.length() - i);
1229 Task::hasStartDependency(int sc)
1231 /* Checks whether the task has a start specification for the
1232 * scenario. This can be a fixed start time or a dependency on another
1233 * task's end or an implicit dependency on the fixed start time of a
1235 if (scenarios[sc].start != 0 || !depends.isEmpty())
1237 for (Task* p = getParent(); p; p = p->getParent())
1238 if (p->scenarios[sc].start != 0)
1244 Task::hasEndDependency(int sc)
1246 /* Checks whether the task has an end specification for the
1247 * scenario. This can be a fixed end time or a dependency on another
1248 * task's start or an implicit dependency on the fixed end time of a
1250 if (scenarios[sc].end != 0 || !precedes.isEmpty())
1252 for (Task* p = getParent(); p; p = p->getParent())
1253 if (p->scenarios[sc].end != 0)
1259 Task::preScheduleOk()
1261 for (int sc = 0; sc < project->getMaxScenarios(); sc++)
1263 if (scenarios[sc].effort > 0 && allocations.count() == 0)
1266 ("No allocations specified for effort based task %1 "
1268 .arg(1).arg(project->getScenarioName(sc)));
1272 if (scenarios[sc].startBuffer + scenarios[sc].endBuffer >= 100.0)
1275 ("Start and end buffers may not overlap in %2 scenario. "
1276 "So their sum must be smaller then 100%.")
1277 .arg(project->getScenarioName(sc)));
1281 // Check plan values.
1282 int durationSpec = 0;
1283 if (scenarios[sc].effort > 0.0)
1285 if (scenarios[sc].length > 0.0)
1287 if (scenarios[sc].duration > 0.0)
1289 if (durationSpec > 1)
1291 fatalError(QString("Task %1 may only have one duration "
1292 "criteria in %2 scenario.").arg(id)
1293 .arg(project->getScenarioName(sc)));
1298 |: fixed start or end date
1299 -: no fixed start or end date
1301 D: start or end dependency
1302 x->: ASAP task with duration criteria
1303 <-x: ALAP task with duration criteria
1304 -->: ASAP task without duration criteria
1305 <--: ALAP task without duration criteria
1309 if (durationSpec != 0)
1312 ("Container task %1 may not have a plan duration "
1313 "criteria in %2 scenario").arg(id)
1314 .arg(project->getScenarioName(sc)));
1320 if (durationSpec != 0)
1323 ("Milestone %1 may not have a plan duration "
1324 "criteria in %2 scenario").arg(id)
1325 .arg(project->getScenarioName(sc)));
1329 | M - ok |D M - ok - M - err1 -D M - ok
1330 | M | err2 |D M | err2 - M | ok -D M | ok
1331 | M -D ok |D M -D ok - M -D ok -D M -D ok
1332 | M |D err2 |D M |D err2 - M |D ok -D M |D ok
1334 /* err1: no start and end
1337 if (!hasStartDependency(sc) && !hasEndDependency(sc))
1339 fatalError(QString("Milestone %1 must have a start or end "
1340 "specification in %2 scenario.")
1341 .arg(id).arg(project->getScenarioName(sc)));
1344 /* err2: different start and end
1350 if (scenarios[sc].start != 0 && scenarios[sc].end != 0 &&
1351 scenarios[sc].start != scenarios[sc].end + 1)
1354 ("Milestone %1 may not have both a start "
1355 "and an end specification that do not "
1356 "match in %2 scenario.").arg(id)
1357 .arg(project->getScenarioName(sc)));
1364 Error table for non-container, non-milestone tasks:
1366 | x-> - ok |D x-> - ok - x-> - err3 -D x-> - ok
1367 | x-> | err1 |D x-> | err1 - x-> | err3 -D x-> | err1
1368 | x-> -D ok |D x-> -D ok - x-> -D err3 -D x-> -D ok
1369 | x-> |D err1 |D x-> |D err1 - x-> |D err3 -D x-> |D err1
1370 | --> - err2 |D --> - err2 - --> - err3 -D --> - err2
1371 | --> | ok |D --> | ok - --> | err3 -D --> | ok
1372 | --> -D ok |D --> -D ok - --> -D err3 -D --> -D ok
1373 | --> |D ok |D --> |D ok - --> |D err3 -D --> |D ok
1374 | <-x - err4 |D <-x - err4 - <-x - err4 -D <-x - err4
1375 | <-x | err1 |D <-x | err1 - <-x | ok -D <-x | ok
1376 | <-x -D err1 |D <-x -D err1 - <-x -D ok -D <-x -D ok
1377 | <-x |D err1 |D <-x |D err1 - <-x |D ok -D <-x |D ok
1378 | <-- - err4 |D <-- - err4 - <-- - err4 -D <-- - err4
1379 | <-- | ok |D <-- | ok - <-- | err2 -D <-- | ok
1380 | <-- -D ok |D <-- -D ok - <-- -D err2 -D <-- -D ok
1381 | <-- |D ok |D <-- |D ok - <-- |D err2 -D <-- |D ok
1384 err1: Overspecified (12 cases)
1398 if (((scenarios[sc].start != 0 && scenarios[sc].end != 0) ||
1399 (hasStartDependency(sc) && scenarios[sc].start == 0 &&
1400 scenarios[sc].end != 0 && scheduling == ASAP) ||
1401 (scenarios[sc].start != 0 && scheduling == ALAP &&
1402 hasEndDependency(sc) && scenarios[sc].end == 0)) &&
1405 fatalError(QString("Task %1 has a start, an end and a "
1406 "duration specification for %2 scenario.")
1407 .arg(id).arg(project->getScenarioName(sc)));
1411 err2: Underspecified (6 cases)
1419 if ((hasStartDependency(sc) ^ hasEndDependency(sc)) &&
1423 ("Task %1 has only a start or end specification "
1424 "but no plan duration for the %2 scenario.")
1425 .arg(id).arg(project->getScenarioName(sc)));
1429 err3: ASAP + Duration must have fixed start (8 cases)
1439 if (!hasStartDependency(sc) && scheduling == ASAP)
1442 ("Task %1 needs a start specification to be "
1443 "scheduled in ASAP mode in the %2 scenario.")
1444 .arg(id).arg(project->getScenarioName(sc)));
1448 err4: ALAP + Duration must have fixed end (8 cases)
1458 if (!hasEndDependency(sc) && scheduling == ALAP)
1461 ("Task %1 needs an end specification to be "
1462 "scheduled in ALAP mode in the %2 scenario.")
1463 .arg(id).arg(project->getScenarioName(sc)));
1468 double intervalLoad =
1469 project->convertToDailyLoad(project->getScheduleGranularity());
1471 for (Allocation* a = allocations.first(); a != 0; a = allocations.next())
1473 if (a->getLoad() < intervalLoad * 100.0)
1475 qWarning("Warning: Load is smaller than scheduling granularity "
1476 "(Task: %s, Resource: %s). Minimal load is %.2f.",
1477 id.latin1(), a->first()->getId().latin1(),
1478 intervalLoad + 0.005);
1479 a->setLoad((int) (intervalLoad * 100.0));
1487 Task::scheduleOk(int& errors, QString scenario)
1489 /* It is of little use to report errors of container tasks, if any of
1490 * their sub tasks has errors. */
1491 int currErrors = errors;
1492 for (Task* t = subFirst(); t; t = subNext())
1493 t->scheduleOk(errors, scenario);
1494 if (errors > currErrors)
1497 qWarning(QString("Scheduling errors in sub tasks of %1.")
1502 /* Runaway errors have already been reported. Since the data of this task
1503 * is very likely completely bogus, we just return FALSE. */
1507 /* If any of the dependant tasks is a runAway, we can safely surpress all
1508 * other error messages. */
1509 for (Task* t = depends.first(); t; t = depends.next())
1512 for (Task* t = precedes.first(); t; t = precedes.next())
1518 // Only report this for leaf tasks.
1519 if (DEBUGPS(1) || sub.isEmpty())
1520 fatalError(QString("Task '%1' has no %2 start time.")
1521 .arg(id).arg(scenario.lower()));
1525 if (start < minStart)
1527 fatalError(QString("%1 start time of task %2 is too early\n"
1530 .arg(scenario).arg(id).arg(time2tjp(start))
1531 .arg(time2tjp(minStart)));
1535 if (maxStart < start)
1537 fatalError(QString("%1 start time of task %2 is too late\n"
1540 .arg(scenario).arg(id)
1541 .arg(time2tjp(start)).arg(time2tjp(maxStart)));
1547 // Only report this for leaf tasks.
1548 if (DEBUGPS(1) || sub.isEmpty())
1549 fatalError(QString("Task '%1' has no %2 end time.")
1550 .arg(id).arg(scenario.lower()));
1553 if (end + (milestone ? 1 : 0) < minEnd)
1555 fatalError(QString("%1 end time of task %2 is too early\n"
1558 .arg(scenario).arg(id)
1559 .arg(time2tjp(end + (milestone ? 1 : 0)))
1560 .arg(time2tjp(minEnd)));
1564 if (maxEnd < end + (milestone ? 1 : 0))
1566 fatalError(QString("%1 end time of task %2 is too late\n"
1569 .arg(scenario).arg(id)
1570 .arg(time2tjp(end + (milestone ? 1 : 0)))
1571 .arg(time2tjp(maxEnd)));
1577 // All sub task must fit into their parent task.
1578 for (Task* t = subFirst(); t != 0; t = subNext())
1580 if (start > t->start)
1584 fatalError(QString("Task %1 has ealier %2 start than "
1586 .arg(id).arg(scenario.lower()));
1595 fatalError(QString("Task %1 has later %2 end than parent")
1596 .arg(id).arg(scenario.lower()));
1604 // Check if all previous tasks end before start of this task.
1605 for (Task* t = previous.first(); t != 0; t = previous.next())
1606 if (t->end > start && !t->runAway)
1608 fatalError(QString("Impossible dependency:\n"
1609 "Task %1 ends at %2 but needs to preceed\n"
1610 "task %3 which has a %4 start time of %5")
1611 .arg(t->id).arg(time2tjp(t->end).latin1())
1612 .arg(id).arg(scenario.lower()).arg(time2tjp(start)));
1616 // Check if all following task start after this tasks end.
1617 for (Task* t = followers.first(); t != 0; t = followers.next())
1618 if (end > t->start && !t->runAway)
1620 fatalError(QString("Impossible dependency:\n"
1621 "Task %1 starts at %2 but needs to follow\n"
1622 "task %3 which has a %4 end time of %5")
1623 .arg(t->id).arg(time2tjp(t->start))
1624 .arg(id).arg(scenario.lower()).arg(time2tjp(end)));
1629 if (!schedulingDone)
1631 fatalError(QString("Task %1 has not been marked completed.\n"
1632 "It is scheduled to last from %2 to %3.\n"
1633 "This might be a bug in the TaskJuggler scheduler.")
1634 .arg(id).arg(time2tjp(start)).arg(time2tjp(end)));
1643 Task::nextSlot(time_t slotDuration)
1645 if (schedulingDone || !sub.isEmpty())
1648 if (scheduling == ASAP && start != 0)
1650 if (effort == 0.0 && length == 0.0 && duration == 0.0 && !milestone &&
1656 return lastSlot + 1;
1658 if (scheduling == ALAP && end != 0)
1660 if (effort == 0.0 && length == 0.0 && duration == 0.0 && !milestone &&
1664 return end - slotDuration + 1;
1665 return lastSlot - slotDuration;
1672 Task::isActive(int sc, const Interval& period) const
1674 return period.overlaps(Interval(scenarios[sc].start,
1675 milestone ? scenarios[sc].start :
1676 scenarios[sc].end));
1680 Task::getSubTaskList(TaskList& tl)
1682 for (Task* t = subFirst(); t != 0; t = subNext())
1685 t->getSubTaskList(tl);
1690 Task::isSubTask(Task* tsk)
1692 for (Task* t = subFirst(); t != 0; t = subNext())
1693 if (t == tsk || t->isSubTask(tsk))
1700 Task::overlayScenario(int sc)
1702 /* Scenario 0 is always the baseline. If another scenario does not provide
1703 * a certain value, the value from the plan scenario is copied over. */
1704 if (scenarios[sc].start == 0.0)
1705 scenarios[sc].start = scenarios[0].start;
1706 if (scenarios[sc].end == 0.0)
1707 scenarios[sc].end = scenarios[0].end;
1708 if (scenarios[sc].duration == 0.0)
1709 scenarios[sc].duration = scenarios[0].duration;
1710 if (scenarios[sc].length == 0.0)
1711 scenarios[sc].length = scenarios[0].length;
1712 if (scenarios[sc].effort == 0.0)
1713 scenarios[sc].effort = scenarios[0].effort;
1714 if (scenarios[sc].startBuffer < 0.0)
1715 scenarios[sc].startBuffer = scenarios[0].startBuffer;
1716 if (scenarios[sc].endBuffer < 0.0)
1717 scenarios[sc].endBuffer = scenarios[0].endBuffer;
1718 if (scenarios[sc].startCredit < 0.0)
1719 scenarios[sc].startCredit = scenarios[0].startCredit;
1720 if (scenarios[sc].endCredit < 0.0)
1721 scenarios[sc].endCredit = scenarios[0].endCredit;
1722 if (scenarios[sc].complete == -1)
1723 scenarios[sc].complete = scenarios[0].complete;
1727 Task::hasExtraValues(int sc) const
1729 return scenarios[sc].start != 0 || scenarios[sc].end != 0 ||
1730 scenarios[sc].length != 0 || scenarios[sc].duration != 0 ||
1731 scenarios[sc].effort != 0 || scenarios[sc].complete != -1 ||
1732 scenarios[sc].startBuffer >= 0.0 || scenarios[sc].endBuffer >= 0.0 ||
1733 scenarios[sc].startCredit >= 0.0 || scenarios[sc].endCredit >= 0.0;
1737 Task::prepareScenario(int sc)
1739 start = scenarios[sc].start;
1740 end = scenarios[sc].end;
1742 duration = scenarios[sc].duration;
1743 length = scenarios[sc].length;
1744 effort = scenarios[sc].effort;
1746 schedulingDone = scenarios[sc].scheduled;
1748 bookedResources.clear();
1749 bookedResources = scenarios[sc].bookedResources;
1753 Task::finishScenario(int sc)
1755 scenarios[sc].start = start;
1756 scenarios[sc].end = end;
1757 scenarios[sc].duration = doneDuration;
1758 scenarios[sc].length = doneLength;
1759 scenarios[sc].effort = doneEffort;
1760 scenarios[sc].bookedResources = bookedResources;
1761 scenarios[sc].scheduled = schedulingDone;
1765 Task::computeBuffers()
1767 int sg = project->getScheduleGranularity();
1768 for (int sc = 0; sc < project->getMaxScenarios(); sc++)
1770 scenarios[sc].startBufferEnd = scenarios[sc].start - 1;
1771 scenarios[sc].endBufferStart = scenarios[sc].end + 1;
1776 if (scenarios[sc].startBuffer > 0.0)
1777 scenarios[sc].startBufferEnd = scenarios[sc].start +
1778 (time_t) ((scenarios[sc].end - scenarios[sc].start) *
1779 scenarios[sc].startBuffer / 100.0);
1780 if (scenarios[sc].endBuffer > 0.0)
1781 scenarios[sc].endBufferStart = scenarios[sc].end -
1782 (time_t) ((scenarios[sc].end - scenarios[sc].start) *
1783 scenarios[sc].endBuffer / 100.0);
1785 else if (length > 0.0)
1788 if (scenarios[sc].startBuffer > 0.0)
1790 for (l = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
1791 scenarios[sc].startBufferEnd += sg)
1793 if (project->isWorkingDay(scenarios[sc].startBufferEnd))
1794 l += (double) sg / ONEDAY;
1795 if (l >= scenarios[sc].length *
1796 scenarios[sc].startBuffer / 100.0)
1800 if (scenarios[sc].endBuffer > 0.0)
1802 for (l = 0.0; scenarios[sc].endBufferStart >
1803 scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
1805 if (project->isWorkingDay(scenarios[sc].endBufferStart))
1806 l += (double) sg / ONEDAY;
1807 if (l >= scenarios[sc].length *
1808 scenarios[sc].endBuffer / 100.0)
1813 else if (effort > 0.0)
1816 if (scenarios[sc].startBuffer > 0.0)
1818 for (e = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
1819 scenarios[sc].startBufferEnd += sg)
1822 Interval(scenarios[sc].startBufferEnd,
1823 scenarios[sc].startBufferEnd + sg));
1824 if (e >= scenarios[sc].effort *
1825 scenarios[sc].startBuffer / 100.0)
1829 if (scenarios[sc].endBuffer > 0.0)
1831 for (e = 0.0; scenarios[sc].endBufferStart >
1832 scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
1835 Interval(scenarios[sc].endBufferStart - sg,
1836 scenarios[sc].endBufferStart));
1837 if (e >= scenarios[sc].effort *
1838 scenarios[sc].endBuffer / 100.0)
1846 double Task::getCompleteAtTime(int sc, time_t timeSpot) const
1848 if( scenarios[sc].complete != -1 ) return( scenarios[sc].complete );
1850 time_t start = getStart(sc);
1851 time_t end = getEnd(sc);
1853 if( timeSpot > end ) return 100.0;
1854 if( timeSpot < start ) return 0.0;
1856 time_t interval = end - start;
1857 time_t done = timeSpot - start;
1859 return 100./interval*done;
1863 QDomElement Task::xmlElement( QDomDocument& doc, bool /* absId */ )
1865 QDomElement taskElem = doc.createElement( "Task" );
1866 QDomElement tempElem;
1868 QString idStr = getId();
1870 idStr = idStr.section( '.', -1 ); */
1872 taskElem.setAttribute( "Id", idStr );
1875 taskElem.appendChild( ReportXML::createXMLElem( doc, "Index", QString::number(getIndex()) ));
1876 taskElem.appendChild( ReportXML::createXMLElem( doc, "Name", getName() ));
1877 taskElem.appendChild( ReportXML::createXMLElem( doc, "ProjectID", projectId ));
1878 taskElem.appendChild( ReportXML::createXMLElem( doc, "Priority", QString::number(getPriority())));
1880 double cmplt = getCompleteAtTime( Task::Plan, getProject()->getNow());
1881 taskElem.appendChild( ReportXML::createXMLElem( doc, "complete", QString::number(cmplt, 'f', 1) ));
1883 QString tType = "Milestone";
1884 if( !isMilestone() )
1887 tType = "Container";
1892 taskElem.appendChild( ReportXML::createXMLElem( doc, "Type", tType ));
1894 CoreAttributes *parent = getParent();
1896 taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
1898 if( !note.isEmpty())
1899 taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
1901 tempElem = ReportXML::createXMLElem( doc, "minStart", QString::number( minStart ));
1902 tempElem.setAttribute( "humanReadable", time2ISO( minStart ));
1903 taskElem.appendChild( tempElem );
1905 tempElem = ReportXML::createXMLElem( doc, "maxStart", QString::number( maxStart ));
1906 tempElem.setAttribute( "humanReadable", time2ISO( maxStart ));
1907 taskElem.appendChild( tempElem );
1909 tempElem = ReportXML::createXMLElem( doc, "minEnd", QString::number( minEnd ));
1910 tempElem.setAttribute( "humanReadable", time2ISO( minEnd ));
1911 taskElem.appendChild( tempElem );
1913 tempElem = ReportXML::createXMLElem( doc, "maxEnd", QString::number( maxEnd ));
1914 tempElem.setAttribute( "humanReadable", time2ISO( maxEnd ));
1915 taskElem.appendChild( tempElem );
1917 tempElem = ReportXML::createXMLElem( doc, "actualStart", QString::number( scenarios[1].start ));
1918 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[1].start ));
1919 taskElem.appendChild( tempElem );
1921 tempElem = ReportXML::createXMLElem( doc, "actualEnd", QString::number( scenarios[1].end ));
1922 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[1].end ));
1923 taskElem.appendChild( tempElem );
1925 tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( scenarios[0].start ));
1926 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].start ));
1927 taskElem.appendChild( tempElem );
1929 tempElem = ReportXML::createXMLElem( doc, "planEnd", QString::number( scenarios[0].end ));
1930 tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].end ));
1931 taskElem.appendChild( tempElem );
1933 /* Start- and Endbuffer */
1934 if( getStartBuffer(Task::Plan) > 0.01 )
1936 /* startbuffer exists */
1937 tempElem = ReportXML::createXMLElem( doc, "startBufferSize",
1939 getStartBuffer(Task::Plan)));
1940 taskElem.appendChild( tempElem );
1942 tempElem = ReportXML::createXMLElem( doc, "ActualStartBufferEnd",
1943 QString::number( getStartBufferEnd(Task::Actual)));
1944 tempElem.setAttribute( "humanReadable",
1945 time2ISO(getStartBufferEnd(Task::Actual)));
1946 taskElem.appendChild( tempElem );
1948 tempElem = ReportXML::createXMLElem( doc, "PlanStartBufferEnd",
1949 QString::number( getStartBufferEnd(Task::Plan)));
1950 tempElem.setAttribute( "humanReadable",
1951 time2ISO(getStartBufferEnd(Task::Plan)));
1952 taskElem.appendChild( tempElem );
1956 if( getEndBuffer(Task::Plan) > 0.01 )
1958 /* startbuffer exists */
1959 tempElem = ReportXML::createXMLElem( doc, "EndBufferSize",
1961 getEndBuffer(Task::Plan)));
1962 taskElem.appendChild( tempElem );
1964 tempElem = ReportXML::createXMLElem( doc, "ActualEndBufferStart",
1965 QString::number( getEndBufferStart(Task::Actual)));
1966 tempElem.setAttribute( "humanReadable",
1967 time2ISO(getEndBufferStart(Task::Actual)));
1968 taskElem.appendChild( tempElem );
1970 tempElem = ReportXML::createXMLElem( doc, "PlanEndBufferStart",
1971 QString::number( getEndBufferStart(Task::Plan)));
1972 tempElem.setAttribute( "humanReadable",
1973 time2ISO(getStartBufferEnd(Task::Plan)));
1974 taskElem.appendChild( tempElem );
1977 /* Responsible persons */
1978 if( getResponsible() )
1979 taskElem.appendChild( getResponsible()->xmlIDElement( doc ));
1981 /* Now start the subtasks */
1983 QDomElement subTaskElem = doc.createElement( "SubTasks" );
1984 for (Task* t = subFirst(); t != 0; t = subNext())
1988 QDomElement sTask = t->xmlElement( doc, false );
1989 subTaskElem.appendChild( sTask );
1994 taskElem.appendChild( subTaskElem);
1996 /* list of tasks by id which are previous */
1997 if( previous.count() > 0 )
1999 TaskList tl( previous );
2000 for (Task* t = tl.first(); t != 0; t = tl.next())
2004 taskElem.appendChild( ReportXML::createXMLElem( doc, "Previous", t->getId()));
2009 /* list of tasks by id which follow */
2010 if( followers.count() > 0 )
2012 TaskList tl( followers );
2013 for (Task* t = tl.first(); t != 0; t = tl.next())
2017 taskElem.appendChild( ReportXML::createXMLElem( doc, "Follower", t->getId()));
2022 /** Allocations and Booked Resources
2023 * With the following code, the task in XML contains simply a list of all Allocations
2024 * wiht the ResourceID for which resource the allocation is. After that, there comes
2025 * a list of all Resources, again having the Resource Id as key. That could be put
2026 * in a hirarchy like
2027 * <Resource Id="dev2" >Larry Bono
2028 * <Income>1000</Income>
2031 * <Persistent>Yes</Persistent>
2035 * But we do not ;-) to have full flexibility.
2039 if( allocations.count() > 0 )
2041 QPtrList<Allocation> al(allocations);
2042 for (Allocation* a = al.first(); a != 0; a = al.next())
2044 taskElem.appendChild( a->xmlElement( doc ));
2048 /* booked Ressources */
2049 if( bookedResources.count() > 0 )
2051 QPtrList<Resource> br(bookedResources);
2052 for (Resource* r = br.first(); r != 0; r = br.next())
2054 taskElem.appendChild( r->xmlIDElement( doc ));
2062 TaskList::isSupportedSortingCriteria(CoreAttributesList::SortCriteria sc)
2070 case ActualStartDown:
2078 case ResponsibleDown:
2081 return CoreAttributesList::isSupportedSortingCriteria(sc);
2086 TaskList::compareItemsLevel(Task* t1, Task* t2, int level)
2088 if (level < 0 || level >= maxSortingLevel)
2091 switch (sorting[level])
2095 return compareTreeItemsT(this, t1, t2);
2097 return t1->getSequenceNo() == t2->getSequenceNo() ? 0 :
2098 t1->getSequenceNo() < t2->getSequenceNo() ? -1 : 1;
2100 return t1->scenarios[0].start == t2->scenarios[0].start ? 0 :
2101 t1->scenarios[0].start < t2->scenarios[0].start ? -1 : 1;
2103 return t1->scenarios[0].start == t2->scenarios[0].start ? 0 :
2104 t1->scenarios[0].start > t2->scenarios[0].start ? -1 : 1;
2106 return t1->scenarios[1].start == t2->scenarios[1].start ? 0 :
2107 t1->scenarios[1].start < t2->scenarios[1].start ? -1 : 1;
2108 case ActualStartDown:
2109 return t1->scenarios[1].start == t2->scenarios[1].start ? 0 :
2110 t1->scenarios[1].start > t2->scenarios[1].start ? -1 : 1;
2112 return t1->scenarios[0].end == t2->scenarios[0].end ? 0 :
2113 t1->scenarios[0].end < t2->scenarios[0].end ? -1 : 1;
2115 return t1->scenarios[0].end == t2->scenarios[0].end ? 0 :
2116 t1->scenarios[0].end > t2->scenarios[0].end ? -1 : 1;
2118 return t1->scenarios[1].end == t2->scenarios[1].end ? 0 :
2119 t1->scenarios[1].end < t2->scenarios[1].end ? -1 : 1;
2121 return t1->scenarios[1].end == t2->scenarios[1].end ? 0 :
2122 t1->scenarios[1].end > t2->scenarios[1].end ? -1 : 1;
2124 if (t1->priority == t2->priority)
2127 return (t1->priority - t2->priority);
2129 if (t1->priority == t2->priority)
2132 return (t2->priority - t1->priority);
2136 t1->responsible->getFullName(fn1);
2138 t2->responsible->getFullName(fn2);
2139 return - fn1.compare(fn2);
2141 case ResponsibleDown:
2144 t1->responsible->getFullName(fn1);
2146 t2->responsible->getFullName(fn2);
2147 return fn1.compare(fn2);
2150 return CoreAttributesList::compareItemsLevel(t1, t2, level);
2155 TaskList::compareItems(QCollection::Item i1, QCollection::Item i2)
2157 Task* t1 = static_cast<Task*>(i1);
2158 Task* t2 = static_cast<Task*>(i2);
2161 for (int i = 0; i < CoreAttributesList::maxSortingLevel; ++i)
2162 if ((res = compareItemsLevel(t1, t2, i)) != 0)
2170 void Task::toTodo( KCal::Todo* todo, KCal::CalendarLocal* /* cal */ )
2175 // todo->setReadOnly( true );
2177 /* Start-Time of the task */
2178 dt.setTime_t( getPlanStart() );
2179 todo->setDtStart( dt );
2180 todo->setHasDueDate( true );
2182 /* Due-Time of the todo -> plan End */
2183 dt.setTime_t( getPlanEnd());
2184 todo->setDtDue( dt );
2185 todo->setHasStartDate(true);
2187 /* Description and summary -> project ID */
2188 todo->setDescription( getNote() );
2189 todo->setSummary( getName() );
2190 todo->setPriority( getPriority() );
2191 todo->setCompleted( getComplete() );
2194 QPtrList<Resource> resList;
2195 resList = getPlanBookedResources();
2196 QStringList strList;
2199 for ( res = resList.first(); res; res = resList.next() )
2201 strList.append( res->getName());
2203 todo->setResources(strList);
2207 #endif /* HAVE_KDE */
2208 #endif /* HAVE_ICAL */
2210 void Task::loadFromXML( QDomElement& parent, Project *project )
2212 QDomElement elem = parent.firstChild().toElement();
2214 for( ; !elem.isNull(); elem = elem.nextSibling().toElement() )
2216 // qDebug( "**Task -elemType: " + elem.tagName() );
2217 QString elemTagName = elem.tagName();
2219 if( elemTagName == "Name" )
2221 setName( elem.text());
2223 else if( elemTagName == "SubTasks" )
2225 QDomElement subTaskElem = elem.firstChild().toElement();
2226 for( ; !subTaskElem.isNull(); subTaskElem = subTaskElem.nextSibling().toElement() )
2228 /* Recursive call for more tasks */
2229 QString stId = subTaskElem.attribute("Id");
2230 qDebug( "Recursing to elem " + stId );
2231 Task *t = new Task( project, stId, QString(), this, QString(), 0 );
2232 t->loadFromXML( subTaskElem, project );
2233 project->addTask(t);
2234 qDebug( "Recursing to elem " + stId + " <FIN>");
2237 else if( elemTagName == "Type" )
2239 if( elem.text() == "Milestone" )
2241 /* Container and Task are detected automatically */
2243 else if( elemTagName == "Previous" )
2245 addDepends( elem.text() );
2247 else if( elemTagName == "Follower" )
2249 addPrecedes( elem.text() );
2251 else if( elemTagName == "Index" )
2252 setIndex( elem.text().toUInt());
2253 else if( elemTagName == "Priority" )
2254 setPriority( elem.text().toInt() );
2255 else if( elemTagName == "complete" )
2256 setComplete(Task::Plan, elem.text().toInt() );
2259 else if( elemTagName == "minStart" )
2260 setMinStart( elem.text().toLong());
2261 else if( elemTagName == "maxStart" )
2262 setMaxStart( elem.text().toLong() );
2263 else if( elemTagName == "minEnd" )
2264 setMinEnd( elem.text().toLong() );
2265 else if( elemTagName == "maxEnd" )
2266 setMaxEnd( elem.text().toLong() );
2267 else if( elemTagName == "actualStart" )
2268 setStart(Task::Actual, elem.text().toLong() );
2269 else if( elemTagName == "actualEnd" )
2270 setEnd(Task::Actual, elem.text().toLong() );
2271 else if( elemTagName == "planStart" )
2272 setStart(Task::Plan, elem.text().toLong() );
2273 else if( elemTagName == "planEnd" )
2274 setEnd(Task::Plan, elem.text().toLong() );