OSDN Git Service

Added columns 'completedeffort' and 'remainingeffort' to reports.
[tjqt4port/tj2qt4.git] / taskjuggler / Task.cpp
1 /*
2  * Task.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
5  * Chris Schlaeger <cs@kde.org>
6  *
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.
10  *
11  * $Id$
12  */
13
14 #include "Task.h"
15
16 #include <stdlib.h>
17 #include <math.h>
18 #include <assert.h>
19
20 #include "TjMessageHandler.h"
21 #include "tjlib-internal.h"
22 #include "Resource.h"
23 #include "Account.h"
24 #include "Project.h"
25 #include "ResourceTreeIterator.h"
26 #include "Allocation.h"
27 #include "Booking.h"
28 #include "ReportXML.h"
29 #include "Scenario.h"
30 #include "CustomAttributeDefinition.h"
31 #include "UsageLimits.h"
32
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),
36     note(),
37     journal(),
38     ref(),
39     refLabel(),
40     depends(),
41     precedes(),
42     predecessors(),
43     successors(),
44     previous(),
45     followers(),
46     projectId(),
47     milestone(false),
48     priority(0),
49     scheduling(ASAP),
50     responsible(0),
51     shifts(),
52     allocations(),
53     account(0),
54     scenarios(new TaskScenario[proj->getMaxScenarios()]),
55     start(0),
56     end(0),
57     length(0.0),
58     effort(0.0),
59     duration(0.0),
60     doneEffort(0.0),
61     doneLength(0.0),
62     doneDuration(0.0),
63     workStarted(false),
64     tentativeStart(0),
65     tentativeEnd(0),
66     lastSlot(0),
67     schedulingDone(false),
68     runAway(false),
69     bookedResources()
70 {
71     allocations.setAutoDelete(true);
72     shifts.setAutoDelete(true);
73     depends.setAutoDelete(true);
74     precedes.setAutoDelete(true);
75
76     proj->addTask(this);
77
78     for (int i = 0; i < proj->getMaxScenarios(); i++)
79     {
80         scenarios[i].task = this;
81         scenarios[i].index = i;
82     }
83
84     scenarios[0].startBuffer = 0.0;
85     scenarios[0].endBuffer = 0.0;
86     scenarios[0].startCredit = 0.0;
87     scenarios[0].endCredit = 0.0;
88
89     for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
90     {
91         scenarios[sc].minStart = scenarios[sc].minEnd = 0;
92         scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
93     }
94 }
95
96 Task::~Task()
97 {
98     project->deleteTask(this);
99     delete [] scenarios;
100 }
101
102 void
103 Task::inheritValues()
104 {
105     Task* p = static_cast<Task*>(parent);
106     if (p)
107     {
108         // Inherit flags from parent task.
109         for (QStringList::Iterator it = p->flags.begin();
110              it != p->flags.end(); ++it)
111             addFlag(*it);
112
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;
119
120         for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
121         {
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;
126         }
127         // Inherit depends from parent. Relative IDs need to get another '!'.
128         for (QPtrListIterator<TaskDependency> tdi(p->depends); tdi; ++tdi)
129         {
130             QString id = (*tdi)->getTaskRefId();
131             if (id[0] == '!')
132                 id = '!' + id;
133             TaskDependency* td = new TaskDependency(id,
134                                                     project->getMaxScenarios());
135             for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
136             {
137                 td->setGapDuration(sc, (*tdi)->getGapDurationNR(sc));
138                 td->setGapLength(sc, (*tdi)->getGapLengthNR(sc));
139             }
140             depends.append(td);
141         }
142
143         // Inherit precedes from parent. Relative IDs need to get another '!'.
144         for (QPtrListIterator<TaskDependency> tdi(p->precedes); *tdi; ++tdi)
145         {
146             QString id = (*tdi)->getTaskRefId();
147             if (id[0] == '!')
148                 id = '!' + id;
149             TaskDependency* td = new TaskDependency(id,
150                                                     project->getMaxScenarios());
151             for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
152             {
153                 td->setGapDuration(sc, (*tdi)->getGapDurationNR(sc));
154                 td->setGapLength(sc, (*tdi)->getGapLengthNR(sc));
155             }
156             precedes.append(td);
157         }
158
159         // Inherit allocations from parent.
160         for (QPtrListIterator<Allocation> ali(p->allocations); *ali; ++ali)
161             allocations.append(new Allocation(**ali));
162
163         // Inherit inheritable custom attributes
164         inheritCustomAttributes(project->getTaskAttributeDict());
165     }
166     else
167     {
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)
172         {
173             scenarios[sc].minStart = scenarios[sc].minEnd = 0;
174             scenarios[sc].maxStart = scenarios[sc].maxEnd = 0;
175         }
176     }
177 }
178
179 void
180 Task::addJournalEntry(JournalEntry* entry)
181 {
182     journal.inSort(entry);
183 }
184
185 Journal::Iterator
186 Task::getJournalIterator() const
187 {
188     return Journal::Iterator(journal);
189 }
190
191 TaskDependency*
192 Task::addDepends(const QString& rid)
193 {
194     TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
195     depends.append(td);
196     return td;
197 }
198
199 TaskDependency*
200 Task::addPrecedes(const QString& rid)
201 {
202     TaskDependency* td = new TaskDependency(rid, project->getMaxScenarios());
203     precedes.append(td);
204     return td;
205 }
206
207 bool
208 Task::addShift(const Interval& i, Shift* s)
209 {
210     return shifts.insert(new ShiftSelection(i, s));
211 }
212
213 void
214 Task::errorMessage(const QString& msg) const
215 {
216     TJMH.errorMessage(msg, definitionFile, definitionLine);
217 }
218
219 void
220 Task::warningMessage(const QString& msg) const
221 {
222     TJMH.warningMessage(msg, definitionFile, definitionLine);
223 }
224
225 bool
226 Task::schedule(int sc, time_t& date, time_t slotDuration)
227 {
228     // Has the task been scheduled already or is it a container?
229     if (schedulingDone || !sub->isEmpty())
230         return false;
231
232     if (DEBUGTS(15))
233         qDebug("Trying to schedule %s at %s",
234                id.latin1(), time2tjp(date).latin1());
235
236     if (scheduling == Task::ASAP)
237     {
238         if (start == 0 ||
239             (effort == 0.0 && length == 0.0 && duration == 0.0 && end == 0))
240             return false;
241
242         if (lastSlot == 0)
243         {
244             lastSlot = start - 1;
245             tentativeEnd = date + slotDuration - 1;
246             if (DEBUGTS(5))
247                 qDebug("Scheduling of ASAP task %s starts at %s (%s)",
248                        id.latin1(), time2tjp(start).latin1(),
249                        time2tjp(date).latin1());
250         }
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)))
254             return false;
255
256         lastSlot = date + slotDuration - 1;
257     }
258     else
259     {
260         if (end == 0 ||
261             (effort == 0.0 && length == 0.0 && duration == 0.0 && start == 0))
262             return false;
263
264         if (lastSlot == 0)
265         {
266             lastSlot = end + 1;
267             tentativeStart = date;
268             if (DEBUGTS(5))
269                 qDebug("Scheduling of ALAP task %s starts at %s (%s)",
270                        id.latin1(), time2tjp(lastSlot).latin1(),
271                        time2tjp(date).latin1());
272         }
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)))
277             return false;
278         lastSlot = date;
279     }
280
281     if (DEBUGTS(10))
282         qDebug("Scheduling %s at %s",
283                id.latin1(), time2tjp(date).latin1());
284
285     if ((duration > 0.0) || (length > 0.0))
286     {
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);
291
292         doneDuration += ((double) slotDuration) / ONEDAY;
293         if (project->isWorkingTime(Interval(date, date + slotDuration - 1)))
294             doneLength += project->convertToDailyLoad(slotDuration);
295
296         if (DEBUGTS(10))
297             qDebug("Length: %f/%f   Duration: %f/%f",
298                    doneLength, length,
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. */
307         if ((length > 0.0 &&
308              qRound(doneLength * 2048) >= qRound(length * 2048)) ||
309             (duration > 0.0 &&
310              qRound(doneDuration * 2048) >= qRound(duration * 2048)))
311         {
312             if (scheduling == ASAP)
313                 propagateEnd(sc, date + slotDuration - 1);
314             else
315                 propagateStart(sc, date);
316             schedulingDone = true;
317             if (DEBUGTS(4))
318                 qDebug("Scheduling of task %s completed", id.latin1());
319             return true;
320         }
321     }
322     else if (effort > 0.0)
323     {
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
327          * effort. */
328         bookResources(sc, date, slotDuration);
329         // Check whether we are done with this task.
330         if (qRound(doneEffort * 2048) >= qRound(effort * 2048))
331         {
332             if (scheduling == ASAP)
333                 propagateEnd(sc, tentativeEnd);
334             else
335                 propagateStart(sc, tentativeStart);
336             schedulingDone = true;
337             if (DEBUGTS(4))
338                 qDebug("Scheduling of task %s completed", id.latin1());
339             return true;
340         }
341     }
342     else if (milestone)
343     {
344         // Task is a milestone.
345         if (scheduling == ASAP)
346             propagateEnd(sc, start - 1);
347         else
348             propagateStart(sc, end + 1);
349
350         return true;
351     }
352     else if (start != 0 && end != 0)
353     {
354         // Task with start and end date but no duration criteria.
355         if (!allocations.isEmpty() && !project->isVacation(date))
356             bookResources(sc, date, slotDuration);
357
358         if ((scheduling == ASAP && (date + slotDuration) >= end) ||
359             (scheduling == ALAP && date <= start))
360         {
361             schedulingDone = true;
362             if (DEBUGTS(4))
363                 qDebug("Scheduling of task %s completed", id.latin1());
364             return true;
365         }
366     }
367
368     return false;
369 }
370
371 bool
372 Task::scheduleContainer(int sc)
373 {
374     if (schedulingDone || !isContainer())
375         return true;
376
377     time_t nStart = 0;
378     time_t nEnd = 0;
379
380     for (TaskListIterator tli(*sub); *tli != 0; ++tli)
381     {
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)
385             return true;
386
387         if (nStart == 0 || (*tli)->start < nStart)
388             nStart = (*tli)->start;
389         if ((*tli)->end > nEnd)
390             nEnd = (*tli)->end;
391     }
392
393     if (start == 0 || start > nStart)
394         propagateStart(sc, nStart);
395
396     if (end == 0 || end < nEnd)
397         propagateEnd(sc, nEnd);
398
399     if (DEBUGTS(4))
400         qDebug("Scheduling of task %s completed", id.latin1());
401     schedulingDone = true;
402
403     return false;
404 }
405
406 void
407 Task::propagateStart(int sc, time_t date)
408 {
409     start = date;
410
411     if (DEBUGTS(11))
412         qDebug("PS1: Setting start of %s to %s",
413                id.latin1(), time2tjp(start).latin1());
414
415     /* If one end of a milestone is fixed, then the other end can be set as
416      * well. */
417     if (milestone)
418     {
419         schedulingDone = true;
420         if (end == 0)
421             propagateEnd(sc, start - 1);
422     }
423
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)))
432         {
433             /* Recursively propagate the end date */
434             (*tli)->propagateEnd(sc, (*tli)->latestEnd(sc));
435         }
436
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)
440     {
441         if (!(*tli)->hasStartDependency() && !(*tli)->schedulingDone)
442         {
443             /* Recursively propagate the start date */
444             (*tli)->propagateStart(sc, start);
445         }
446     }
447
448     if (parent)
449     {
450         if (DEBUGTS(11))
451             qDebug("Scheduling parent of %s", id.latin1());
452         getParent()->scheduleContainer(sc);
453     }
454 }
455
456 void
457 Task::propagateEnd(int sc, time_t date)
458 {
459     end = date;
460
461     if (DEBUGTS(11))
462         qDebug("PE1: Setting end of %s to %s",
463                id.latin1(), time2tjp(end).latin1());
464
465     /* If one end of a milestone is fixed, then the other end can be set as
466      * well. */
467     if (milestone)
468     {
469         if (DEBUGTS(4))
470             qDebug("Scheduling of task %s completed", id.latin1());
471         schedulingDone = true;
472         if (start == 0)
473             propagateStart(sc, end + 1);
474     }
475
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)))
484         {
485             /* Recursively propagate the start date */
486             (*tli)->propagateStart(sc, (*tli)->earliestStart(sc));
487         }
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)
492         {
493             /* Recursively propagate the end date */
494             (*tli)->propagateEnd(sc, end);
495         }
496
497     if (parent)
498     {
499         if (DEBUGTS(11))
500             qDebug("Scheduling parent of %s", id.latin1());
501         getParent()->scheduleContainer(sc);
502     }
503 }
504
505 void
506 Task::propagateInitialValues(int sc)
507 {
508     if (start != 0)
509         propagateStart(sc, start);
510     if (end != 0)
511         propagateEnd(sc, end);
512
513     // Check if the some data of sub tasks can already be propagated.
514     if (!sub->isEmpty())
515         scheduleContainer(sc);
516 }
517
518 void
519 Task::setRunaway()
520 {
521     schedulingDone = true;
522     runAway = true;
523 }
524
525 bool
526 Task::isRunaway() const
527 {
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.
530      */
531     for (TaskListIterator tli(*sub); *tli != 0; ++tli)
532         if ((*tli)->isRunaway())
533             return false;
534
535     return runAway;
536 }
537
538 void
539 Task::bookResources(int sc, time_t date, time_t slotDuration)
540 {
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
543      * shift interval. */
544     if (!shifts.isOnShift(Interval(date, date + slotDuration - 1)))
545     {
546         if (DEBUGRS(15))
547             qDebug("Task %s is not active at %s", id.latin1(),
548                    time2tjp(date).latin1());
549         return;
550     }
551
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()))
559     {
560         if (DEBUGRS(15))
561             qDebug("No allocations prior to current date for task %s",
562                    id.latin1());
563         return;
564     }
565
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())
572         {
573             if (!(*ali)->isOnShift(Interval(date, date + slotDuration - 1)))
574             {
575                 allMandatoriesAvailables = false;
576                 break;
577             }
578             if ((*ali)->isPersistent() && (*ali)->getLockedResource())
579             {
580                 int availability;
581                 if ((availability = (*ali)->getLockedResource()->
582                      isAvailable(date)) > 0)
583                 {
584                     allMandatoriesAvailables = false;
585                     if (availability >= 4 && !(*ali)->getConflictStart())
586                         (*ali)->setConflictStart(date);
587                     break;
588                 }
589             }
590             else
591             {
592                 /* For a mandatory allocation with alternatives at least one
593                  * of the resources or resource groups must be available. */
594                 bool found = false;
595                 int maxAvailability = 0;
596                 QPtrList<Resource> candidates = (*ali)->getCandidates();
597                 for (QPtrListIterator<Resource> rli(candidates);
598                      *rli && !found; ++rli)
599                 {
600                     /* If a resource group is marked mandatory, all members
601                      * of the group must be available. */
602                     int availability;
603                     bool allAvailable = true;
604                     for (ResourceTreeIterator rti(*rli); *rti != 0; ++rti)
605                         if ((availability =
606                              (*rti)->isAvailable(date)) > 0)
607                         {
608                             allAvailable = false;
609                             if (availability >= maxAvailability)
610                                 maxAvailability = availability;
611                         }
612                     if (allAvailable)
613                         found = true;
614                 }
615                 if (!found)
616                 {
617                     if (maxAvailability >= 4 && !(*ali)->getConflictStart())
618                         (*ali)->setConflictStart(date);
619                     allMandatoriesAvailables = false;
620                     break;
621                 }
622             }
623         }
624
625     for (QPtrListIterator<Allocation> ali(allocations);
626          *ali != 0 && allMandatoriesAvailables &&
627          (effort == 0.0 || doneEffort < effort); ++ali)
628     {
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)))
633         {
634             if (DEBUGRS(15))
635                 qDebug("Allocation not on shift at %s",
636                        time2tjp(date).latin1());
637             continue;
638         }
639
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;
645         if (limits)
646         {
647             QPtrList<Resource> resources = (*ali)->getCandidates();
648             QString resStr = "";
649             for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
650                 resStr += (*rli)->getId() + " ";
651             if (limits->getDailyMax() > 0)
652             {
653                 uint slotCount = 0;
654                 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
655                     slotCount += (*rli)->getCurrentDaySlots(date, this);
656                 int freeSlots = limits->getDailyMax() - slotCount;
657                 if (freeSlots <= 0)
658                 {
659                     if (DEBUGRS(6))
660                         qDebug("  Resource(s) %soverloaded", resStr.latin1());
661                     continue;
662                 }
663                 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
664                     slotsToLimit = freeSlots;
665             }
666             if (limits->getWeeklyMax() > 0)
667             {
668                 uint slotCount = 0;
669                 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
670                     slotCount += (*rli)->getCurrentWeekSlots(date, this);
671                 int freeSlots = limits->getWeeklyMax() - slotCount;
672                 if (freeSlots <= 0)
673                 {
674                     if (DEBUGRS(6))
675                         qDebug("  Resource(s) %soverloaded", resStr.latin1());
676                     continue;
677                 }
678                 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
679                     slotsToLimit = freeSlots;
680             }
681             if (limits->getMonthlyMax() > 0)
682             {
683                 uint slotCount = 0;
684                 for (QPtrListIterator<Resource> rli(resources); *rli; ++rli)
685                     slotCount += (*rli)->getCurrentMonthSlots(date, this);
686                 int freeSlots = limits->getMonthlyMax() - slotCount;
687                 if (freeSlots <= 0)
688                 {
689                     if (DEBUGRS(6))
690                         qDebug("  Resource(s) %soverloaded", resStr.latin1());
691                     continue;
692                 }
693                 else if (slotsToLimit < 0 || slotsToLimit > freeSlots)
694                     slotsToLimit = freeSlots;
695             }
696         }
697
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
701          * time slot. */
702         int maxAvailability = 0;
703         if ((*ali)->isPersistent() && (*ali)->getLockedResource())
704         {
705             if (!bookResource((*ali)->getLockedResource(), date, slotDuration,
706                               slotsToLimit, maxAvailability))
707             {
708                 if (maxAvailability >= 4 && !(*ali)->getConflictStart())
709                     (*ali)->setConflictStart(date);
710             }
711             else if ((*ali)->getConflictStart())
712             {
713                 if (DEBUGRS(2))
714                     qDebug("Resource %s is not available for task '%s' "
715                            "from %s to %s",
716                            (*ali)->getLockedResource()->getId().latin1(),
717                            id.latin1(),
718                            time2ISO((*ali)->getConflictStart()).latin1(),
719                            time2ISO(date).latin1());
720                 (*ali)->setConflictStart(0);
721             }
722         }
723         else
724         {
725             QPtrList<Resource> cl = createCandidateList(sc, date, *ali);
726
727             bool found = false;
728             for (QPtrListIterator<Resource> rli(cl); *rli != 0; ++rli)
729                 if (bookResource((*rli), date, slotDuration, slotsToLimit,
730                                  maxAvailability))
731                 {
732                     (*ali)->setLockedResource(*rli);
733                     found = true;
734                     break;
735                 }
736             if (!found && maxAvailability >= 4 && !(*ali)->getConflictStart())
737                 (*ali)->setConflictStart(date);
738             else if (found && (*ali)->getConflictStart())
739             {
740                 if (DEBUGRS(2))
741                 {
742                     QString candidates;
743                     bool first = true;
744                     for (QPtrListIterator<Resource> rli(cl); *rli != 0; ++rli)
745                     {
746                         if (first)
747                             first = false;
748                         else
749                             candidates += ", ";
750                         candidates += (*rli)->getId();
751                     }
752                     qDebug("No resource of the allocation (%s) is available "
753                            "for task '%s' from %s to %s",
754                            candidates.latin1(),
755                            id.latin1(),
756                            time2ISO((*ali)->getConflictStart()).latin1(),
757                            time2ISO(date).latin1());
758                 }
759                 (*ali)->setConflictStart(0);
760             }
761         }
762     }
763 }
764
765 bool
766 Task::bookResource(Resource* r, time_t date, time_t slotDuration,
767                    int& slotsToLimit, int& maxAvailability)
768 {
769     bool booked = false;
770     double intervalLoad = project->convertToDailyLoad(slotDuration);
771
772     for (ResourceTreeIterator rti(r); *rti != 0; ++rti)
773     {
774         int availability;
775         if ((availability =
776              (*rti)->isAvailable(date)) == 0)
777         {
778             (*rti)->book(new Booking(Interval(date, date + slotDuration - 1),
779                                      this));
780             addBookedResource(*rti);
781
782             /* Move the start date to make sure that there is
783              * some work going on at the start date. */
784             if (!workStarted)
785             {
786                 if (scheduling == ASAP)
787                     start = date;
788                 else if (scheduling == ALAP)
789                     end = date + slotDuration - 1;
790                 else
791                     qFatal("Unknown scheduling mode");
792                 workStarted = true;
793             }
794
795             tentativeStart = date;
796             tentativeEnd = date + slotDuration - 1;
797             doneEffort += intervalLoad * (*rti)->getEfficiency();
798
799             if (DEBUGTS(6))
800                 qDebug(" Booked resource %s (Effort: %f)",
801                        (*rti)->getId().latin1(), doneEffort);
802             booked = true;
803
804             if (slotsToLimit > 0 && --slotsToLimit <= 0)
805                 return true;
806         }
807         else if (availability > maxAvailability)
808             maxAvailability = availability;
809     }
810     return booked;
811 }
812
813 QPtrList<Resource>
814 Task::createCandidateList(int sc, time_t date, Allocation* a)
815 {
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;
822
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
825      * of the list. */
826     if (a->getLockedResource())
827     {
828         cl.append(a->getLockedResource());
829         candidates.remove(a->getLockedResource());
830         /* When an allocation is booked the resource is saved as locked
831          * resource. */
832         a->setLockedResource(0);
833     }
834     switch (a->getSelectionMode())
835     {
836         case Allocation::order:
837             if (DEBUGTS(25))
838                 qDebug("order");
839             while (candidates.getFirst())
840             {
841                 cl.append(candidates.getFirst());
842                 candidates.remove(candidates.getFirst());
843             }
844             break;
845         case Allocation::minAllocationProbability:
846         {
847             if (DEBUGTS(25))
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())
855             {
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);
861                      *rli != 0; ++rli)
862                 {
863                     double probability = (*rli)->getAllocationProbability(sc);
864                     if (minProbability == 0 || probability < minProbability)
865                     {
866                         minProbability = probability;
867                         minProbResource = *rli;
868                     }
869                 }
870                 cl.append(minProbResource);
871                 candidates.remove(minProbResource);
872             }
873             break;
874         }
875         case Allocation::minLoaded:
876         {
877             if (DEBUGTS(25))
878                 qDebug("minLoad");
879             while (!candidates.isEmpty())
880             {
881                 double minLoad = 0;
882                 Resource* minLoaded = 0;
883                 for (QPtrListIterator<Resource> rli(candidates);
884                      *rli != 0; ++rli)
885                 {
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. */
889                     double load =
890                         (*rli)->getCurrentLoad(Interval(project->getStart(),
891                                                         date), 0) /
892                         (((*rli)->getLimits() &&
893                           (*rli)->getLimits()->getDailyMax() > 0) ?
894                          project->convertToDailyLoad
895                          ((*rli)->getLimits()->getDailyMax() *
896                           project->getScheduleGranularity()) : 1.0);
897
898                     if (minLoaded == 0 || load < minLoad)
899                     {
900                         minLoad = load;
901                         minLoaded = *rli;
902                     }
903                 }
904                 cl.append(minLoaded);
905                 candidates.remove(minLoaded);
906             }
907             break;
908         }
909         case Allocation::maxLoaded:
910         {
911             if (DEBUGTS(25))
912                 qDebug("maxLoad");
913             while (!candidates.isEmpty())
914             {
915                 double maxLoad = 0;
916                 Resource* maxLoaded = 0;
917                 for (QPtrListIterator<Resource> rli(candidates);
918                      *rli != 0; ++rli)
919                 {
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. */
923                     double load =
924                         (*rli)->getCurrentLoad(Interval(project->getStart(),
925                                                         date), 0) /
926                         (((*rli)->getLimits() &&
927                           (*rli)->getLimits()->getDailyMax() > 0) ?
928                          project->convertToDailyLoad
929                          ((*rli)->getLimits()->getDailyMax() *
930                           project->getScheduleGranularity()) : 1.0);
931
932                     if (maxLoaded == 0 || load > maxLoad)
933                     {
934                         maxLoad = load;
935                         maxLoaded = *rli;
936                     }
937                 }
938                 cl.append(maxLoaded);
939                 candidates.remove(maxLoaded);
940             }
941             break;
942         }
943         case Allocation::random:
944         {
945             if (DEBUGTS(25))
946                 qDebug("random");
947             while (candidates.getFirst())
948             {
949                 cl.append(candidates.at(rand() % candidates.count()));
950                 candidates.remove(candidates.getFirst());
951             }
952             break;
953         }
954         default:
955             qFatal("Illegal selection mode %d", a->getSelectionMode());
956     }
957
958     return cl;
959 }
960
961 QString
962 Task::getSchedulingText() const
963 {
964     if (isLeaf())
965     {
966         return scheduling == ASAP ? "ASAP |-->|" : "ALAP |<--|";
967     }
968     else
969     {
970         QString text;
971
972         for (TaskListIterator tli(*sub); *tli != 0; ++tli)
973         {
974             if (text.isEmpty())
975                 text = (*tli)->getSchedulingText();
976             else if (text != (*tli)->getSchedulingText())
977             {
978                 text = "Mixed";
979                 break;
980             }
981         }
982         return text;
983     }
984     return QString::null;
985 }
986
987 QString
988 Task::getStatusText(int sc) const
989 {
990     QString text;
991     switch (getStatus(sc))
992     {
993         case NotStarted:
994             text = i18n("Not yet started");
995             break;
996         case InProgressLate:
997             text = i18n("Behind schedule");
998             break;
999         case InProgress:
1000             text = i18n("Work in progress");
1001             break;
1002         case OnTime:
1003             text = i18n("On schedule");
1004             break;
1005         case InProgressEarly:
1006             text = i18n("Ahead of schedule");
1007             break;
1008         case Finished:
1009             text = i18n("Finished");
1010             break;
1011         case Late:
1012             text = i18n("Late");
1013             break;
1014         default:
1015             text = i18n("Unknown status");
1016             break;
1017     }
1018     return text;
1019 }
1020
1021 bool
1022 Task::isCompleted(int sc, time_t date) const
1023 {
1024     if (scenarios[sc].reportedCompletion >= 0.0)
1025     {
1026         if (scenarios[sc].reportedCompletion >= 100.0)
1027             return true;
1028
1029         // some completion degree has been specified.
1030         if (scenarios[sc].effort > 0.0)
1031         {
1032             return qRound((scenarios[sc].effort *
1033                            (scenarios[sc].reportedCompletion / 100.0)) * 1000)
1034                 >= qRound(getLoad(sc, Interval(scenarios[sc].start, date), 0)
1035                          * 1000);
1036         }
1037         else
1038         {
1039             return (date <=
1040                     scenarios[sc].start +
1041                     static_cast<int>((scenarios[sc].reportedCompletion /
1042                                       100.0) * (scenarios[sc].end -
1043                                                 scenarios[sc].start)));
1044         }
1045     }
1046
1047     if (isContainer())
1048     {
1049         return (date <=
1050                 scenarios[sc].start +
1051                 static_cast<int>((scenarios[sc].containerCompletion /
1052                                   100.0) * (scenarios[sc].end -
1053                                             scenarios[sc].start)));
1054     }
1055
1056     return (project->getNow() > date);
1057 }
1058
1059 bool
1060 Task::isBuffer(int sc, const Interval& iv) const
1061 {
1062     return iv.overlaps(Interval(scenarios[sc].start,
1063                                 scenarios[sc].startBufferEnd)) ||
1064         iv.overlaps(Interval(scenarios[sc].endBufferStart,
1065                              scenarios[sc].end));
1066 }
1067
1068 time_t
1069 Task::earliestStart(int sc) const
1070 {
1071     time_t date = 0;
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)
1075         {
1076             if ((*tli)->scheduling == ASAP)
1077                 return 0;
1078         }
1079         else if ((*tli)->end + 1 > date)
1080             date = (*tli)->end + 1;
1081
1082     for (QPtrListIterator<TaskDependency> tdi(depends); *tdi != 0; ++tdi)
1083     {
1084         /* Add the gapDuration and/or gapLength to the end of the dependent
1085          * task. */
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;
1096         else
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;
1101     }
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)
1106             return t->start;
1107
1108     return date;
1109 }
1110
1111 time_t
1112 Task::latestEnd(int sc) const
1113 {
1114     time_t date = 0;
1115     // All tasks this task precedes must have a start date set.
1116     for (TaskListIterator tli(followers); *tli; ++tli)
1117         if ((*tli)->start == 0)
1118         {
1119             if ((*tli)->scheduling == ALAP)
1120                 return 0;
1121         }
1122         else if (date == 0 || (*tli)->start - 1 < date)
1123             date = (*tli)->start - 1;
1124
1125     for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
1126     {
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;
1139         else
1140             potentialDate -= (*tdi)->getGapDuration(sc);
1141
1142         /* Set 'date' to the earliest end date minus gaps of all following
1143          * tasks. */
1144         if (date == 0 || potentialDate < date)
1145             date = potentialDate;
1146     }
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)
1151             return t->end;
1152
1153     return date;
1154 }
1155
1156 double
1157 Task::getCalcEffort(int sc) const
1158 {
1159     if (milestone)
1160         return 0.0;
1161
1162     return getLoad(sc, Interval(scenarios[sc].start, scenarios[sc].end));
1163 }
1164
1165 double
1166 Task::getCalcDuration(int sc) const
1167 {
1168     if (milestone)
1169         return 0.0;
1170
1171     return static_cast<double>(scenarios[sc].end + 1 - scenarios[sc].start) / ONEDAY;
1172 }
1173
1174 double
1175 Task::getLoad(int sc, const Interval& period, const Resource* resource) const
1176 {
1177     if (milestone)
1178         return 0.0;
1179
1180     double load = 0.0;
1181
1182     if (isContainer())
1183     {
1184         for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1185             load += (*tli)->getLoad(sc, period, resource);
1186     }
1187     else
1188     {
1189         if (resource)
1190             load += resource->getEffectiveLoad(sc, period, AllAccounts, this);
1191         else
1192             for (ResourceListIterator rli(scenarios[sc].bookedResources);
1193                  *rli != 0; ++rli)
1194                 load += (*rli)->getEffectiveLoad(sc, period, AllAccounts, this);
1195     }
1196
1197     return load;
1198 }
1199
1200 double
1201 Task::getAllocatedTimeLoad(int sc, const Interval& period,
1202                            const Resource* resource) const
1203 {
1204     return project->convertToDailyLoad
1205         (getAllocatedTime(sc, period, resource));
1206 }
1207
1208 long
1209 Task::getAllocatedTime(int sc, const Interval& period,
1210                        const Resource* resource) const
1211 {
1212     if (milestone)
1213         return 0;
1214
1215     long allocatedTime = 0;
1216
1217     if (isContainer())
1218     {
1219         for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1220             allocatedTime += (*tli)->getAllocatedTime(sc, period, resource);
1221     }
1222     else
1223     {
1224         if (resource)
1225             allocatedTime += resource->getAllocatedTime(sc, period, AllAccounts,
1226                                                         this);
1227         else
1228             for (ResourceListIterator rli(scenarios[sc].bookedResources);
1229                  *rli != 0; ++rli)
1230                 allocatedTime += (*rli)->getAllocatedTime(sc, period,
1231                                                           AllAccounts, this);
1232     }
1233
1234     return allocatedTime;
1235 }
1236
1237 double
1238 Task::getCredits(int sc, const Interval& period, AccountType acctType,
1239                  const Resource* resource, bool recursive) const
1240 {
1241     double credits = 0.0;
1242
1243     if (recursive && !sub->isEmpty())
1244     {
1245         for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1246             credits += (*tli)->getCredits(sc, period, acctType, resource,
1247                                           recursive);
1248     }
1249
1250     if (acctType != AllAccounts &&
1251         (account == 0 || acctType != account->getAcctType()))
1252         return credits;
1253
1254     if (resource)
1255         credits += resource->getCredits(sc, period, acctType, this);
1256     else
1257         for (ResourceListIterator rli(scenarios[sc].bookedResources);
1258              *rli != 0; ++rli)
1259             credits += (*rli)->getCredits(sc, period, acctType, this);
1260
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;
1265
1266     return credits;
1267 }
1268
1269 bool
1270 Task::xRef(QDict<Task>& hash)
1271 {
1272     if (DEBUGPF(5))
1273         qDebug("Creating cross references for task %s ...", id.latin1());
1274     int errors = 0;
1275
1276     QPtrList<TaskDependency> brokenDeps;
1277     for (QPtrListIterator<TaskDependency> tdi(depends); *tdi; ++tdi)
1278     {
1279         QString absId = resolveId((*tdi)->getTaskRefId());
1280         Task* t;
1281         if ((t = hash.find(absId)) == 0)
1282         {
1283             errorMessage(i18n("Unknown dependency '%1'").arg(absId));
1284             brokenDeps.append(*tdi);
1285             errors++;
1286         }
1287         else
1288         {
1289             for (QPtrListIterator<TaskDependency> tdi2(depends); *tdi2; ++tdi2)
1290                 if ((*tdi2)->getTaskRef() == t)
1291                 {
1292                     warningMessage(i18n("No need to specify dependency %1 "
1293                                       "multiple times.").arg(absId));
1294                     break;
1295                 }
1296
1297             if (errors == 0)
1298             {
1299                 (*tdi)->setTaskRef(t);
1300                 if (t == this)
1301                 {
1302                     errorMessage(i18n("Task '%1' cannot depend on self.")
1303                                  .arg(id));
1304                     break;
1305                 }
1306                 if (t->isDescendantOf(this))
1307                 {
1308                     errorMessage(i18n("Task '%1' cannot depend on child.")
1309                                  .arg(id));
1310                     break;
1311                 }
1312                 if (isDescendantOf(t))
1313                 {
1314                     errorMessage(i18n("Task '%1' cannot depend on parent.")
1315                                  .arg(t->id));
1316                     break;
1317                 }
1318                 // Unidirectional link
1319                 predecessors.append(t);
1320                 // Bidirectional link
1321                 previous.append(t);
1322                 t->followers.append(this);
1323                 if (DEBUGPF(11))
1324                     qDebug("Registering follower %s with task %s",
1325                            id.latin1(), t->getId().latin1());
1326             }
1327         }
1328     }
1329     // Remove broken dependencies as they can cause trouble later on.
1330     for (QPtrListIterator<TaskDependency> tdi(brokenDeps); *tdi; ++tdi)
1331         depends.removeRef(*tdi);
1332     brokenDeps.clear();
1333
1334     for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
1335     {
1336         QString absId = resolveId((*tdi)->getTaskRefId());
1337         Task* t;
1338         if ((t = hash.find(absId)) == 0)
1339         {
1340             errorMessage(i18n("Unknown dependency '%1'").arg(absId));
1341             brokenDeps.append(*tdi);
1342         }
1343         else
1344         {
1345             for (QPtrListIterator<TaskDependency> tdi2(precedes); *tdi2; ++tdi2)
1346                 if ((*tdi2)->getTaskRef() == t)
1347                 {
1348                     warningMessage(i18n("No need to specify dependency '%1'"
1349                                         "multiple times").arg(absId));
1350                     break;
1351                 }
1352             if (errors == 0)
1353             {
1354                 (*tdi)->setTaskRef(t);
1355                 if (t == this)
1356                 {
1357                     errorMessage(i18n("Task '%1' cannot precede self.")
1358                                  .arg(id));
1359                     break;
1360                 }
1361                 if (t->isDescendantOf(this))
1362                 {
1363                     errorMessage(i18n("Task '%1' cannot precede a child.")
1364                                  .arg(id));
1365                     break;
1366                 }
1367                 if (isDescendantOf(t))
1368                 {
1369                     errorMessage(i18n("Task '%1' cannot precede parent.")
1370                                  .arg(t->id));
1371                     break;
1372                 }
1373                 // Unidirectional link
1374                 successors.append(t);
1375                 // Bidirectional link
1376                 followers.append(t);
1377                 t->previous.append(this);
1378                 if (DEBUGPF(11))
1379                     qDebug("Registering predecessor %s with task %s",
1380                            id.latin1(), t->getId().latin1());
1381             }
1382         }
1383     }
1384     // Remove broken dependencies as they can cause trouble later on.
1385     for (QPtrListIterator<TaskDependency> tdi(brokenDeps); *tdi; ++tdi)
1386         precedes.removeRef(*tdi);
1387     brokenDeps.clear();
1388
1389     return errors > 0;
1390 }
1391
1392 void
1393 Task::implicitXRef()
1394 {
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)
1399     {
1400         scenarios[sc].startCanBeDetermined = false;
1401         scenarios[sc].endCanBeDetermined = false;
1402     }
1403
1404     if (!sub->isEmpty())
1405         return;
1406
1407     for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1408     {
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. */
1412
1413         if (milestone)
1414         {
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;
1421         }
1422         bool hasDurationSpec = scenarios[sc].duration != 0 ||
1423             scenarios[sc].length != 0 ||
1424             scenarios[sc].effort != 0;
1425
1426         if (scenarios[sc].specifiedStart == 0 && depends.isEmpty() &&
1427             !(hasDurationSpec && scheduling == ALAP))
1428             for (Task* tp = getParent(); tp; tp = tp->getParent())
1429             {
1430                 if (tp->scenarios[sc].specifiedStart != 0)
1431                 {
1432                     if (DEBUGPF(11))
1433                         qDebug("Setting start of task '%s' in scenario %s to "
1434                                "%s", id.latin1(),
1435                                project->getScenarioId(sc).latin1(),
1436                                time2ISO(tp->scenarios[sc].specifiedStart)
1437                                .latin1());
1438                     scenarios[sc].specifiedStart =
1439                         tp->scenarios[sc].specifiedStart;
1440                     break;
1441                 }
1442             }
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())
1447             {
1448                 if (tp->scenarios[sc].specifiedEnd != 0)
1449                 {
1450                     if (DEBUGPF(11))
1451                         qDebug("Setting end of task '%s' in scenario %s to %s",
1452                                id.latin1(),
1453                                project->getScenarioId(sc).latin1(),
1454                                time2ISO(tp->scenarios[sc].specifiedEnd)
1455                                .latin1());
1456                     scenarios[sc].specifiedEnd = tp->scenarios[sc].specifiedEnd;
1457                     break;
1458                 }
1459             }
1460     }
1461
1462     if (!isMilestone() && isLeaf())
1463     {
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
1468          * later. */
1469         bool hasStartSpec = false;
1470         bool hasEndSpec = false;
1471         bool hasDurationSpec = false;
1472         for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
1473         {
1474             if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
1475                 hasStartSpec = true;
1476             if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
1477                 hasEndSpec = true;
1478             if (scenarios[sc].duration != 0 || scenarios[sc].length != 0 ||
1479                 scenarios[sc].effort != 0)
1480                 hasDurationSpec = true;
1481         }
1482         if  (!hasDurationSpec && (hasStartSpec ^ hasEndSpec))
1483             milestone = true;
1484     }
1485 }
1486
1487 void
1488 Task::sortAllocations()
1489 {
1490     if (allocations.isEmpty())
1491         return;
1492
1493     allocations.setAutoDelete(false);
1494     for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; )
1495     {
1496         QPtrListIterator<Allocation> tmp = ali;
1497         ++ali;
1498         if (!(*tmp)->isWorker())
1499         {
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);
1507         }
1508
1509     }
1510     allocations.setAutoDelete(true);
1511 }
1512
1513 void
1514 Task::saveSpecifiedBookedResources()
1515 {
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;
1522 }
1523
1524 bool
1525 Task::loopDetector(LDIList& chkedTaskList) const
1526 {
1527     /* Only check top-level tasks. All other tasks will be checked then as
1528      * well. */
1529     if (parent)
1530         return false;
1531     if (DEBUGPF(2))
1532         qDebug("Running loop detector for task %s", id.latin1());
1533     // Check ASAP tasks
1534     LDIList list;
1535     if (loopDetection(list, chkedTaskList, false, true))
1536         return true;
1537     // Check ALAP tasks
1538     if (loopDetection(list, chkedTaskList, true, true))
1539         return true;
1540     return false;
1541 }
1542
1543 bool
1544 Task::loopDetection(LDIList& list, LDIList& chkedTaskList, bool atEnd,
1545                     bool fromOutside) const
1546 {
1547     if (DEBUGPF(10))
1548         qDebug("%sloopDetection at %s (%s)",
1549                QString().fill(' ', list.count() + 1).latin1(), id.latin1(),
1550                atEnd ? "End" : "Start");
1551
1552     // First, check whether the task has already been checked for loops.
1553     {
1554         LoopDetectorInfo thisTask(this, atEnd);
1555         if (chkedTaskList.find(&thisTask))
1556         {
1557             // Already checked
1558             return false;
1559         }
1560     }
1561
1562     if (checkPathForLoops(list, atEnd))
1563         return true;
1564
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
1573      * relationship. */
1574     if (!atEnd)
1575     {
1576         if (fromOutside)
1577         {
1578             /*
1579                  |
1580                  v
1581                +--------
1582             -->| o--+
1583                +--- | --
1584                     |
1585                     V
1586             */
1587             /* If we were not called from a sub task we check all sub tasks.*/
1588             for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1589             {
1590                 if (DEBUGPF(15))
1591                     qDebug("%sChecking sub task %s of %s",
1592                            QString().fill(' ', list.count()).latin1(),
1593                            (*tli)->getId().latin1(),
1594                            id.latin1());
1595                 if ((*tli)->loopDetection(list, chkedTaskList, false, true))
1596                     return true;
1597             }
1598
1599             /*
1600                  |
1601                  v
1602                +--------
1603             -->| o---->
1604                +--------
1605             */
1606             if (DEBUGPF(15))
1607                 qDebug("%sChecking end of task %s",
1608                        QString().fill(' ', list.count()).latin1(),
1609                        id.latin1());
1610             if (loopDetection(list, chkedTaskList, true, false))
1611                 return true;
1612         }
1613         else
1614         {
1615             /*
1616                  ^
1617                  |
1618                + | -----
1619                | o <--
1620                +--------
1621                  ^
1622                  |
1623             */
1624             if (parent)
1625             {
1626                 if (DEBUGPF(15))
1627                     qDebug("%sChecking parent task of %s",
1628                            QString().fill(' ', list.count()).latin1(),
1629                            id.latin1());
1630                 if (getParent()->loopDetection(list, chkedTaskList, false,
1631                                                false))
1632                     return true;
1633             }
1634
1635             /*
1636                +--------
1637             <--|- o <--
1638                +--------
1639                   ^
1640                   |
1641              */
1642             // Now check all previous tasks.
1643             for (TaskListIterator tli(previous); *tli != 0; ++tli)
1644             {
1645                 if (DEBUGPF(15))
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))
1650                     return true;
1651             }
1652         }
1653     }
1654     else
1655     {
1656         if (fromOutside)
1657         {
1658             /*
1659                   |
1660                   v
1661             --------+
1662                +--o |<--
1663             -- | ---+
1664                |
1665                v
1666             */
1667             /* If we were not called from a sub task we check all sub tasks.*/
1668             for (TaskListIterator tli(*sub); *tli != 0; ++tli)
1669             {
1670                 if (DEBUGPF(15))
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))
1675                     return true;
1676             }
1677
1678             /*
1679                   |
1680                   v
1681             --------+
1682              <----o |<--
1683             --------+
1684             */
1685             if (DEBUGPF(15))
1686                 qDebug("%sChecking start of task %s",
1687                        QString().fill(' ', list.count()).latin1(),
1688                        id.latin1());
1689             if (loopDetection(list, chkedTaskList, false, false))
1690                 return true;
1691         }
1692         else
1693         {
1694             /*
1695                   ^
1696                   |
1697             ----- | +
1698               --> o |
1699             --------+
1700                   ^
1701                   |
1702             */
1703             if (parent)
1704             {
1705                 if (DEBUGPF(15))
1706                     qDebug("%sChecking parent task of %s",
1707                            QString().fill(' ', list.count()).latin1(),
1708                            id.latin1());
1709                 if (getParent()->loopDetection(list, chkedTaskList, true,
1710                                                false))
1711                     return true;
1712             }
1713
1714             /*
1715             --------+
1716               --> o-|-->
1717             --------+
1718                   ^
1719                   |
1720             */
1721             // Now check all following tasks.
1722             for (TaskListIterator tli(followers); *tli != 0; ++tli)
1723             {
1724                 if (DEBUGPF(15))
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))
1729                     return true;
1730             }
1731         }
1732     }
1733     chkedTaskList.append(list.popLast());
1734
1735     if (DEBUGPF(5))
1736         qDebug("%sNo loops found in %s (%s)",
1737                  QString().fill(' ', list.count()).latin1(),
1738                  id.latin1(), atEnd ? "End" : "Start");
1739     return false;
1740 }
1741
1742 bool
1743 Task::checkPathForLoops(LDIList& list, bool atEnd) const
1744 {
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))
1750     {
1751         QString loopChain;
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())
1756             ;
1757         /* Then copy all loop elements to the loopChain string. */
1758         for ( ; it != 0; it = it->next())
1759         {
1760             loopChain += QString("%1 (%2) -> ")
1761                 .arg(it->getTask()->getId())
1762                 .arg(it->getAtEnd() ? "End" : "Start");
1763         }
1764         loopChain += QString("%1 (%2)").arg(id)
1765             .arg(atEnd ? "End" : "Start");
1766         delete thisTask;
1767         errorMessage(i18n("Dependency loop detected: %1").arg(loopChain));
1768         return true;
1769     }
1770     list.append(thisTask);
1771
1772     return false;
1773 }
1774
1775 bool
1776 Task::checkDetermination(int sc) const
1777 {
1778     /* Check if the task and it's dependencies have enough information to
1779      * produce a fixed determined schedule. */
1780     if (DEBUGPF(10))
1781         qDebug("Checking determination of task %s",
1782                id.latin1());
1783     LDIList list;
1784
1785     if (!startCanBeDetermined(list, sc))
1786     {
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())
1790             errorMessage
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)));
1796         return false;
1797     }
1798
1799     if (!endCanBeDetermined(list, sc))
1800     {
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())
1804             errorMessage
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)));
1809         return false;
1810     }
1811
1812     return true;
1813 }
1814
1815 bool
1816 Task::startCanBeDetermined(LDIList& list, int sc) const
1817 {
1818     if (DEBUGPF(10))
1819         qDebug("Checking if start of task %s can be determined", id.latin1());
1820
1821     if (scenarios[sc].startCanBeDetermined)
1822     {
1823         if (DEBUGPF(10))
1824             qDebug("Start of task %s can be determined (cached)", id.latin1());
1825         return true;
1826     }
1827
1828     if (checkPathForLoops(list, false))
1829         return false;
1830
1831     for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
1832         if (scenarios[sc].specifiedStart != 0)
1833         {
1834             if (DEBUGPF(10))
1835                 qDebug("Start of task %s can be determined (fixed date)",
1836                        id.latin1());
1837             goto isDetermined;
1838         }
1839
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))
1844     {
1845         if (DEBUGPF(10))
1846             qDebug("Start of task %s can be determined (end + fixed length)",
1847                    id.latin1());
1848         goto isDetermined;
1849     }
1850
1851     for (TaskListIterator tli(predecessors); *tli; ++tli)
1852         if ((*tli)->endCanBeDetermined(list, sc))
1853         {
1854             if (DEBUGPF(10))
1855                 qDebug("Start of task %s can be determined (dependency)",
1856                        id.latin1());
1857             goto isDetermined;
1858         }
1859
1860     if (hasSubs())
1861     {
1862         for (TaskListIterator tli = getSubListIterator(); *tli; ++tli)
1863             if (!(*tli)->startCanBeDetermined(list, sc))
1864                 goto isNotDetermined;
1865
1866         if (DEBUGPF(10))
1867             qDebug("Start of task %s can be determined (children)",
1868                    id.latin1());
1869         goto isDetermined;
1870     }
1871
1872 isNotDetermined:
1873     if (DEBUGPF(10))
1874         qDebug("*** Start of task %s cannot be determined",
1875                id.latin1());
1876     list.removeLast();
1877     return false;
1878
1879 isDetermined:
1880     list.removeLast();
1881     scenarios[sc].startCanBeDetermined = true;
1882     return true;
1883 }
1884
1885 bool
1886 Task::endCanBeDetermined(LDIList& list, int sc) const
1887 {
1888     if (DEBUGPF(10))
1889         qDebug("Checking if end of task %s can be determined", id.latin1());
1890
1891     if (scenarios[sc].endCanBeDetermined)
1892     {
1893         if (DEBUGPF(10))
1894             qDebug("End of task %s can be determined", id.latin1());
1895         return true;
1896     }
1897
1898     if (checkPathForLoops(list, true))
1899         return false;
1900
1901     for (const Task* t = this; t; t = static_cast<const Task*>(t->parent))
1902         if (scenarios[sc].specifiedEnd != 0)
1903         {
1904             if (DEBUGPF(10))
1905                 qDebug("End of task %s can be determined (fixed date)",
1906                        id.latin1());
1907             goto isDetermined;
1908         }
1909
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))
1914     {
1915         if (DEBUGPF(10))
1916             qDebug("End of task %s can be determined (end + fixed length)",
1917                    id.latin1());
1918         goto isDetermined;
1919     }
1920
1921     for (TaskListIterator tli(successors); *tli; ++tli)
1922         if ((*tli)->startCanBeDetermined(list, sc))
1923         {
1924             if (DEBUGPF(10))
1925                 qDebug("End of task %s can be determined (dependency)",
1926                        id.latin1());
1927             goto isDetermined;
1928         }
1929
1930     if (hasSubs())
1931     {
1932         for (TaskListIterator tli = getSubListIterator(); *tli; ++tli)
1933             if (!(*tli)->endCanBeDetermined(list, sc))
1934             {
1935                 if (DEBUGPF(10))
1936                     qDebug("End of task %s cannot be determined (child %s)",
1937                            id.latin1(), (*tli)->id.latin1());
1938                 goto isNotDetermined;
1939             }
1940
1941         if (DEBUGPF(10))
1942             qDebug("End of task %s can be determined (children)",
1943                    id.latin1());
1944         goto isDetermined;
1945     }
1946
1947 isNotDetermined:
1948     if (DEBUGPF(10))
1949         qDebug("*** End of task %s cannot be determined",
1950                id.latin1());
1951     list.removeLast();
1952     return false;
1953
1954 isDetermined:
1955     list.removeLast();
1956     scenarios[sc].endCanBeDetermined = true;
1957     return true;
1958 }
1959
1960 QString
1961 Task::resolveId(QString relId)
1962 {
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] != '!')
1967         return relId;
1968
1969     Task* t = this;
1970     unsigned int i;
1971     for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
1972     {
1973         if (t == 0)
1974         {
1975             errorMessage(i18n("Illegal relative ID '%1'").arg(relId));
1976             return relId;
1977         }
1978         t = t->getParent();
1979     }
1980     if (t)
1981         return t->id + "." + relId.right(relId.length() - i);
1982     else
1983         return relId.right(relId.length() - i);
1984 }
1985
1986 bool
1987 Task::hasStartDependency(int sc) const
1988 {
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
1992      * parent task. */
1993     if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
1994         return true;
1995     for (Task* p = getParent(); p; p = p->getParent())
1996         if (p->scenarios[sc].specifiedStart != 0)
1997             return true;
1998     return false;
1999 }
2000
2001 bool
2002 Task::hasEndDependency(int sc) const
2003 {
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
2007      * parent task. */
2008     if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
2009         return true;
2010     for (Task* p = getParent(); p; p = p->getParent())
2011         if (p->scenarios[sc].specifiedEnd != 0)
2012             return true;
2013     return false;
2014 }
2015
2016 bool
2017 Task::hasStartDependency() const
2018 {
2019     /* Check whether the task or any of it's sub tasks has a start
2020      * dependency. */
2021     if (start != 0 || !previous.isEmpty() || scheduling == ALAP)
2022         return true;
2023
2024     for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2025         if ((*tli)->hasStartDependency())
2026             return true;
2027
2028     return false;
2029 }
2030
2031 bool
2032 Task::hasEndDependency() const
2033 {
2034     /* Check whether the task or any of it's sub tasks has an end
2035      * dependency. */
2036     if (end != 0 || !followers.isEmpty() || scheduling == ASAP)
2037         return true;
2038
2039     for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2040         if ((*tli)->hasEndDependency())
2041             return true;
2042
2043     return false;
2044 }
2045
2046 bool
2047 Task::preScheduleOk(int sc)
2048 {
2049     if (account && !account->isLeaf())
2050     {
2051         errorMessage(i18n
2052                      ("Task '%1' must not have an account group ('%2') "
2053                       "assigned to it.")
2054                      .arg(id).arg(account->getId()));
2055         return false;
2056     }
2057
2058     if (hasSubs() && !scenarios[sc].bookedResources.isEmpty())
2059     {
2060         errorMessage(i18n
2061                      ("Task '%1' is a container task and must not have "
2062                       "bookings assigned to it.").arg(id));
2063         return false;
2064     }
2065
2066     if (milestone && !scenarios[sc].bookedResources.isEmpty())
2067     {
2068         errorMessage(i18n
2069                      ("Task '%1' is a milestone task and must not have "
2070                       "bookings assigned to it.").arg(id));
2071         return false;
2072     }
2073
2074     if (scenarios[sc].specifiedScheduled && !sub->isEmpty() &&
2075         (scenarios[sc].specifiedStart == 0 ||
2076          scenarios[sc].specifiedEnd == 0))
2077     {
2078         errorMessage(i18n
2079                      ("Task '%1' is marked as scheduled but does not have "
2080                       "a fixed start and end date.").arg(id));
2081         return false;
2082     }
2083
2084     if (scenarios[sc].effort > 0.0 && allocations.count() == 0 &&
2085         !scenarios[sc].specifiedScheduled)
2086     {
2087         errorMessage(i18n
2088                      ("No allocations specified for effort based task '%1' "
2089                       "in '%2' scenario")
2090                      .arg(id).arg(project->getScenarioId(sc)));
2091         return false;
2092     }
2093
2094     if (scenarios[sc].startBuffer + scenarios[sc].endBuffer >= 100.0)
2095     {
2096         errorMessage(i18n
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)));
2100         return false;
2101     }
2102
2103     int durationSpec = 0;
2104     if (scenarios[sc].effort > 0.0)
2105         durationSpec++;
2106     if (scenarios[sc].length > 0.0)
2107         durationSpec++;
2108     if (scenarios[sc].duration > 0.0)
2109         durationSpec++;
2110     if (durationSpec > 1)
2111     {
2112         errorMessage(i18n("Task '%1' may only have one duration "
2113                           "criteria in '%2' scenario.").arg(id)
2114                      .arg(project->getScenarioId(sc)));
2115         return false;
2116     }
2117
2118     /*
2119     |: fixed start or end date
2120     -: no fixed start or end date
2121     M: Milestone
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
2127      */
2128     bool hasStartDep = hasStartDependency(sc);
2129     bool hasEndDep = hasEndDependency(sc);
2130     if (!sub->isEmpty())
2131     {
2132         if (durationSpec != 0)
2133         {
2134             errorMessage(i18n
2135                          ("Container task '%1' may not have a duration "
2136                           "criteria in '%2' scenario").arg(id)
2137                          .arg(project->getScenarioId(sc)));
2138             return false;
2139         }
2140         if (milestone)
2141         {
2142             errorMessage(i18n
2143                          ("The container task '%1' may not be a "
2144                           "milestone.").arg(id));
2145             return false;
2146         }
2147     }
2148     else if (milestone)
2149     {
2150         if (durationSpec != 0)
2151         {
2152             errorMessage(i18n
2153                          ("Milestone '%1' may not have a duration "
2154                           "criteria in '%2' scenario").arg(id)
2155                          .arg(project->getScenarioId(sc)));
2156             return false;
2157         }
2158         /*
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
2163          */
2164         /* err1: no start and end
2165         - M -
2166          */
2167         if (!hasStartDep && !hasEndDep)
2168         {
2169             errorMessage(i18n("Milestone '%1' must have a start or end "
2170                               "specification in '%2' scenario.")
2171                          .arg(id).arg(project->getScenarioId(sc)));
2172             return false;
2173         }
2174         /* err2: different start and end
2175         |  M |
2176         |  M |D
2177         |D M |
2178         |D M |D
2179          */
2180         if (scenarios[sc].specifiedStart != 0 &&
2181             scenarios[sc].specifiedEnd != 0 &&
2182             scenarios[sc].specifiedStart != scenarios[sc].specifiedEnd + 1)
2183         {
2184             errorMessage(i18n
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)));
2189             return false;
2190         }
2191     }
2192     else
2193     {
2194         /*
2195         Error table for non-container, non-milestone tasks:
2196
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
2213          */
2214         /*
2215         err1: Overspecified (12 cases)
2216         |  x-> |
2217         |  <-x |
2218         |  x-> |D
2219         |  <-x |D
2220         |D x-> |
2221         |D <-x |
2222         |D <-x |D
2223         |D x-> |D
2224         -D x-> |
2225         -D x-> |D
2226         |D <-x -D
2227         |  <-x -D
2228          */
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)
2236         {
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)));
2240             return false;
2241         }
2242         /*
2243         err2: Underspecified (6 cases)
2244         |  --> -
2245         |D --> -
2246         -D --> -
2247         -  <-- |
2248         -  <-- |D
2249         -  <-- -D
2250          */
2251         if ((hasStartDep ^ hasEndDep) && durationSpec == 0)
2252         {
2253             errorMessage(i18n
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)));
2257             return false;
2258         }
2259         /*
2260         err3: ASAP + Duration must have fixed start (8 cases)
2261         -  x-> -
2262         -  x-> |
2263         -  x-> -D
2264         -  x-> |D
2265         -  --> -
2266         -  --> |
2267         -  --> -D
2268         -  --> |D
2269          */
2270         if (!hasStartDep && scheduling == ASAP)
2271         {
2272             errorMessage(i18n
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)));
2276             return false;
2277         }
2278         /*
2279         err4: ALAP + Duration must have fixed end (8 cases)
2280         -  <-x -
2281         |  <-x -
2282         |D <-x -
2283         -D <-x -
2284         -  <-- -
2285         |  <-- -
2286         -D <-- -
2287         |D <-- -
2288          */
2289         if (!hasEndDep && scheduling == ALAP)
2290         {
2291             errorMessage(i18n
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)));
2295             return false;
2296         }
2297     }
2298
2299     if (!account &&
2300         (scenarios[sc].startCredit > 0.0 || scenarios[sc].endCredit > 0.0))
2301     {
2302         errorMessage(i18n
2303                      ("Task '%1' has a specified start- or endcredit "
2304                       "but no account assigned in scenario '%2'.")
2305                      .arg(id).arg(project->getScenarioId(sc)));
2306         return false;
2307     }
2308
2309     if (!scenarios[sc].bookedResources.isEmpty() && scheduling == ALAP &&
2310         !scenarios[sc].specifiedScheduled)
2311     {
2312         errorMessage
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)));
2321         return false;
2322     }
2323
2324     return true;
2325 }
2326
2327 bool
2328 Task::scheduleOk(int sc) const
2329 {
2330     const QString scenario = project->getScenarioId(sc);
2331
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())
2338     {
2339         if (DEBUGPS(2))
2340             tjDebug(QString("Scheduling errors in sub tasks of '%1'.")
2341                    .arg(id));
2342         return false;
2343     }
2344
2345     /* Runaway errors have already been reported. Since the data of this task
2346      * is very likely completely bogus, we just return false. */
2347     if (runAway)
2348         return false;
2349
2350     if (DEBUGPS(3))
2351         qDebug("Checking task %s", id.latin1());
2352
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)
2357             return false;
2358     for (QPtrListIterator<TaskDependency> tdi(precedes); *tdi; ++tdi)
2359         if ((*tdi)->getTaskRef()->runAway)
2360             return false;
2361
2362     if (start == 0)
2363     {
2364         errorMessage(i18n("Task '%1' has no start time for the '%2'"
2365                           "scenario.")
2366                      .arg(id).arg(scenario));
2367         return false;
2368     }
2369     if (start < project->getStart() || start > project->getEnd())
2370     {
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))
2374                      .arg(id)
2375                      .arg(time2tjp(project->getStart()))
2376                      .arg(time2tjp(project->getEnd()))
2377                      .arg(scenario));
2378         return false;
2379     }
2380     if (scenarios[sc].minStart != 0 && start < scenarios[sc].minStart)
2381     {
2382         warningMessage(i18n("'%1' start time of task '%2' is too early\n"
2383                             "Date is:  %3\n"
2384                             "Limit is: %4")
2385                        .arg(scenario).arg(id).arg(time2tjp(start))
2386                        .arg(time2tjp(scenarios[sc].minStart)));
2387         return false;
2388     }
2389     if (scenarios[sc].maxStart != 0 && start > scenarios[sc].maxStart)
2390     {
2391         warningMessage(i18n("'%1' start time of task '%2' is too late\n"
2392                             "Date is:  %3\n"
2393                             "Limit is: %4")
2394                        .arg(scenario).arg(id)
2395                        .arg(time2tjp(start))
2396                        .arg(time2tjp(scenarios[sc].maxStart)));
2397         return false;
2398     }
2399     if (end == 0)
2400     {
2401         errorMessage(i18n("Task '%1' has no '%2' end time.")
2402                      .arg(id).arg(scenario.lower()));
2403         return false;
2404     }
2405     if ((end + 1) < project->getStart() || (end > project->getEnd()))
2406     {
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))
2410                      .arg(id)
2411                      .arg(time2tjp(project->getStart()))
2412                      .arg(time2tjp(project->getEnd() + 1))
2413                      .arg(scenario));
2414         return false;
2415     }
2416     if (scenarios[sc].minEnd != 0 && end < scenarios[sc].minEnd)
2417     {
2418         warningMessage(i18n("'%1' end time of task '%2' is too early\n"
2419                             "Date is:  %3\n"
2420                             "Limit is: %4")
2421                        .arg(scenario).arg(id)
2422                        .arg(time2tjp(end + 1))
2423                        .arg(time2tjp(scenarios[sc].minEnd + 1)));
2424         return false;
2425     }
2426     if (scenarios[sc].maxEnd != 0 && end > scenarios[sc].maxEnd)
2427     {
2428         warningMessage(i18n("'%1' end time of task '%2' is too late\n"
2429                             "Date is:  %2\n"
2430                             "Limit is: %3")
2431                        .arg(scenario).arg(id)
2432                        .arg(time2tjp(end + 1))
2433                        .arg(time2tjp(scenarios[sc].maxEnd + 1)));
2434         return false;
2435     }
2436     if (!sub->isEmpty())
2437     {
2438         // All sub task must fit into their parent task.
2439         for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2440         {
2441             if (start > (*tli)->start)
2442             {
2443                 if (!(*tli)->runAway)
2444                 {
2445                     errorMessage(i18n("Task '%1' has earlier '%2' start than "
2446                                       "parent\n"
2447                                       "%3 start date: %4\n"
2448                                       "%5 start date: %6")
2449                                  .arg((*tli)->getId()).arg(scenario)
2450                                  .arg(id.latin1())
2451                                  .arg(time2ISO(start).latin1())
2452                                  .arg((*tli)->getId().latin1())
2453                                  .arg(time2ISO((*tli)->start).latin1()));
2454                 }
2455                 return false;
2456             }
2457             if (end < (*tli)->end)
2458             {
2459                 if (!(*tli)->runAway)
2460                 {
2461                     errorMessage(i18n("Task '%1' has later '%2' end than "
2462                                       "parent")
2463                                  .arg(id).arg(scenario));
2464                 }
2465                 return false;
2466             }
2467         }
2468     }
2469
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)
2473         {
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)));
2479             return false;
2480         }
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)
2484         {
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)));
2490             return false;
2491         }
2492
2493     if (!schedulingDone)
2494     {
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)));
2499         return false;
2500     }
2501
2502     return true;
2503 }
2504
2505 time_t
2506 Task::nextSlot(time_t slotDuration) const
2507 {
2508     if (scheduling == ASAP)
2509     {
2510         if (lastSlot == 0)
2511             return start;
2512         return lastSlot + 1;
2513     }
2514     else
2515     {
2516         if (lastSlot == 0)
2517             return end - slotDuration + 1;
2518
2519         return lastSlot - slotDuration;
2520     }
2521
2522     return 0;
2523 }
2524
2525 bool
2526 Task::isReadyForScheduling() const
2527 {
2528     /* This function returns true if the tasks has all the necessary
2529      * information to be scheduled and has not been completely scheduled yet.
2530      */
2531     if (schedulingDone)
2532         return false;
2533
2534     if (scheduling == ASAP)
2535     {
2536         if (start != 0)
2537         {
2538             if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
2539                 !milestone && end == 0)
2540                 return false;
2541
2542             return true;
2543         }
2544     }
2545     else
2546     {
2547         if (end != 0)
2548         {
2549             if (effort == 0.0 && length == 0.0 && duration == 0.0 &&
2550                 !milestone && start == 0)
2551                 return false;
2552
2553             return true;
2554         }
2555     }
2556
2557     return false;
2558 }
2559
2560 bool
2561 Task::isActive(int sc, const Interval& period) const
2562 {
2563     return period.overlaps(Interval(scenarios[sc].start,
2564                                     milestone ? scenarios[sc].start :
2565                                     scenarios[sc].end));
2566 }
2567
2568 bool
2569 Task::isSubTask(Task* tsk) const
2570 {
2571     for (TaskListIterator tli(*sub); *tli != 0; ++tli)
2572         if (*tli == tsk || (*tli)->isSubTask(tsk))
2573             return true;
2574
2575     return false;
2576 }
2577
2578 void
2579 Task::overlayScenario(int base, int sc)
2580 {
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;
2611 }
2612
2613 void
2614 Task::prepareScenario(int sc)
2615 {
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;
2620
2621     duration = scenarios[sc].duration;
2622     length = scenarios[sc].length;
2623     effort = scenarios[sc].effort;
2624     lastSlot = 0;
2625     doneEffort = 0.0;
2626     doneDuration = 0.0;
2627     doneLength = 0.0;
2628     tentativeStart = tentativeEnd = 0;
2629     workStarted = false;
2630     runAway = false;
2631     bookedResources.clear();
2632     bookedResources = scenarios[sc].specifiedBookedResources;
2633
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.
2638      */
2639     time_t firstSlot = 0;
2640     for (ResourceListIterator rli(bookedResources); *rli != 0; ++rli)
2641     {
2642         double effort = (*rli)->getEffectiveLoad
2643             (sc, Interval(project->getStart(), project->getEnd()),
2644              AllAccounts, this);
2645         if (effort > 0.0)
2646         {
2647             doneEffort += effort;
2648             if (firstSlot == 0 ||
2649                 firstSlot > (*rli)->getStartOfFirstSlot(sc, this))
2650             {
2651                 firstSlot = (*rli)->getStartOfFirstSlot(sc, this);
2652             }
2653             time_t ls = (*rli)->getEndOfLastSlot(sc, this);
2654             if (ls > lastSlot)
2655                 lastSlot = ls;
2656         }
2657     }
2658
2659     if (lastSlot > 0)
2660     {
2661         if (schedulingDone)
2662         {
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.
2666              */
2667             if (scenarios[sc].start == 0)
2668                 start = scenarios[sc].start = firstSlot;
2669             if (scenarios[sc].end == 0)
2670                 end = scenarios[sc].end = lastSlot;
2671         }
2672         else
2673         {
2674             /* Some bookings have been specified for the task, but it is not
2675              * marked completed yet. */
2676             workStarted = true;
2677             // Trim start to first booked time slot.
2678             start = firstSlot;
2679
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)
2685             {
2686                 scenarios[sc].reportedCompletion = doneEffort / effort * 100.0;
2687                 if (scenarios[sc].reportedCompletion > 100.0)
2688                     scenarios[sc].reportedCompletion = 100.0;
2689
2690                 if (doneEffort >= effort)
2691                 {
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
2695                      * scheduled. */
2696                     end = scenarios[sc].end = lastSlot;
2697                     schedulingDone = true;
2698
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))
2705                     {
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")
2712                                        .arg(id)
2713                                        .arg(project->getScenarioId(sc))
2714                                        .arg(doneEffort)
2715                                        .arg(doneEffort *
2716                                             project->getDailyWorkingHours())
2717                                        .arg(effort)
2718                                        .arg(effort *
2719                                             project->getDailyWorkingHours()));
2720                     }
2721                 }
2722                 else
2723                     lastSlot = project->getNow() - 1;
2724             }
2725         }
2726     }
2727
2728     /*
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
2733      * critical it is.
2734      *
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
2738      * first.
2739      *
2740      * TODO: We need to respect limits and shifts here!
2741      */
2742     double allocationEfficiency = 0;
2743     for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
2744     {
2745         (*ali)->init();
2746         if ((*ali)->isPersistent() && !bookedResources.isEmpty())
2747         {
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)))
2759                     {
2760                         lastSlot = (*rti)->getEndOfLastSlot(sc, this);
2761                         lastResource = *rli;
2762                     }
2763
2764             (*ali)->setLockedResource(lastResource);
2765         }
2766         if (scenarios[sc].effort > 0.0)
2767         {
2768             double maxEfficiency = 0;
2769             for (QPtrListIterator<Resource> rli =
2770                  (*ali)->getCandidatesIterator(); *rli; ++rli)
2771             {
2772                 for (ResourceTreeIterator rti(*rli); *rti; ++rti)
2773                     if ((*rti)->getEfficiency() > maxEfficiency)
2774                         maxEfficiency = (*rti)->getEfficiency();
2775             }
2776             allocationEfficiency += maxEfficiency;
2777         }
2778     }
2779     if (scenarios[sc].effort > 0.0)
2780     {
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());
2790     }
2791 }
2792
2793 void
2794 Task::computeCriticalness(int sc)
2795 {
2796     if (scenarios[sc].effort > 0.0)
2797     {
2798         double overallAllocationProbability = 0;
2799         for (QPtrListIterator<Allocation> ali(allocations); *ali != 0; ++ali)
2800         {
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)
2807             {
2808                 /* If the candidate is a resource group we use the average
2809                  * allocation probablility of all the resources of the group.
2810                  */
2811                 int resources = 0;
2812                 double averageProbability = 0.0;
2813                 for (ResourceTreeIterator rti(*rli); *rti; ++rti, ++resources)
2814                     averageProbability +=
2815                         (*rti)->getAllocationProbability(sc);
2816                 if (resources > 0)
2817                     averageProbability /= resources;
2818
2819                 if (smallestAllocationProbablity == 0 ||
2820                     averageProbability < smallestAllocationProbablity)
2821                     smallestAllocationProbablity = averageProbability;
2822             }
2823             overallAllocationProbability += smallestAllocationProbablity;
2824         }
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;
2845     }
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())
2852     {
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
2856          * milestones. */
2857         scenarios[sc].criticalness = 1.0;
2858     }
2859     else
2860         scenarios[sc].criticalness = 0;
2861
2862 }
2863
2864 void
2865 Task::computePathCriticalness(int sc)
2866 {
2867     /*
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.
2874      *
2875      * Since both the forward and backward functions include the
2876      * criticalness of this function we have to subtract it again.
2877      */
2878     scenarios[sc].pathCriticalness = computeBackwardCriticalness(sc) -
2879         scenarios[sc].criticalness + computeForwardCriticalness(sc);
2880 }
2881
2882 double
2883 Task::computeBackwardCriticalness(int sc)
2884 {
2885     double maxCriticalness = 0.0;
2886
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
2890      * there. */
2891     if (!hasSubs())
2892         for (TaskListIterator tli(previous); *tli; ++tli)
2893             if ((criticalness = (*tli)->computeBackwardCriticalness(sc)) >
2894                 maxCriticalness)
2895                 maxCriticalness = criticalness;
2896
2897     if (parent &&
2898         ((criticalness = static_cast<Task*>
2899           (parent)->computeBackwardCriticalness(sc)) > maxCriticalness))
2900         maxCriticalness = criticalness;
2901
2902     return scenarios[sc].criticalness + maxCriticalness;
2903 }
2904
2905 double
2906 Task::computeForwardCriticalness(int sc)
2907 {
2908     double maxCriticalness = 0.0;
2909
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
2913      * there. */
2914     if (!hasSubs())
2915         for (TaskListIterator tli(followers); *tli; ++tli)
2916             if ((criticalness = (*tli)->computeForwardCriticalness(sc)) >
2917                 maxCriticalness)
2918                 maxCriticalness = criticalness;
2919
2920     if (parent &&
2921         ((criticalness = static_cast<Task*>(parent)->computeForwardCriticalness(sc)) >
2922          maxCriticalness))
2923         maxCriticalness = criticalness;
2924
2925     return scenarios[sc].criticalness + maxCriticalness;
2926 }
2927
2928 void
2929 Task::checkAndMarkCriticalPath(int sc, double minSlack, time_t maxEnd)
2930 {
2931     // The algorithm has to start at a leaf task that has no predecessors.
2932     if (hasSubs() || !previous.isEmpty())
2933         return;
2934
2935     if (DEBUGPA(3))
2936         qDebug("Starting critical path search at %s", id.latin1());
2937
2938     long worstMinSlackTime = static_cast<long>((maxEnd - getStart(sc)) *
2939                                                minSlack);
2940     long checks = 0;
2941     long found = 0;
2942     analyzePath(sc, minSlack, getStart(sc), 0, worstMinSlackTime, checks,
2943                 found);
2944 }
2945
2946 bool
2947 Task::analyzePath(int sc, double minSlack, time_t pathStart, long busyTime,
2948                   long worstMinSlackTime, long& checks, long& found)
2949 {
2950     /* Saveguard to limit the runtime for this NP hard algorithm. */
2951     long maxPaths = project->getScenario(sc)->getMaxPaths();
2952     if (maxPaths > 0 && checks >= maxPaths)
2953         return false;
2954
2955     if (DEBUGPA(14))
2956         qDebug("  * Checking task %s", id.latin1());
2957
2958     bool critical = false;
2959
2960     if (hasSubs())
2961     {
2962         if (DEBUGPA(15))
2963             qDebug("  > Sub check started for %s", id.latin1());
2964
2965         for (TaskListIterator tli(*sub); *tli; ++tli)
2966             if ((*tli)->analyzePath(sc, minSlack, pathStart, busyTime,
2967                                     worstMinSlackTime, checks, found))
2968                 critical = true;
2969
2970         if (DEBUGPA(15))
2971             qDebug("  < Sub check finished for %s", id.latin1());
2972     }
2973     else
2974     {
2975         busyTime += (getEnd(sc) + 1 - getStart(sc));
2976
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)
2981         {
2982             checks++;
2983             if (DEBUGPA(6))
2984                 qDebug("Path cannot be critical. Stopping at task %s",
2985                        id.latin1());
2986             return false;
2987         }
2988
2989         /* Find out if any of the followers is a sibling of the parent of this
2990          * task. */
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())
2995                 {
2996                     hasBrotherFollower = true;
2997                     break;
2998                 }
2999
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())
3005         {
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)
3012                 break;
3013         }
3014
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);
3021
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);
3029
3030         /* Now we can check the paths through the remaining followers. */
3031         for (TaskListIterator tli(allFollowers); *tli; ++tli)
3032         {
3033             if (ignoreList.findRef(*tli) >= 0 ||
3034                 transientFollowers.findRef(*tli) >= 0)
3035                 continue;
3036
3037             if (DEBUGPA(16))
3038                 qDebug("  > Follower check started for %s",
3039                        (*tli)->id.latin1());
3040
3041             if ((*tli)->analyzePath(sc, minSlack, pathStart, busyTime,
3042                                     worstMinSlackTime, checks, found))
3043             {
3044                 if (scenarios[sc].criticalLinks.findRef(*tli) < 0)
3045                 {
3046                     if (DEBUGPA(5))
3047                         qDebug("  +++ Critical link %s -> %s",
3048                                id.latin1(), (*tli)->id.latin1());
3049                     scenarios[sc].criticalLinks.append(*tli);
3050                 }
3051
3052                 critical = true;
3053             }
3054
3055             if (DEBUGPA(16))
3056                 qDebug("  < Follower check finished for %s",
3057                        (*tli)->id.latin1());
3058         }
3059
3060         if (allFollowers.isEmpty())
3061         {
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);
3069             if (critical)
3070             {
3071                 found++;
3072                 if (DEBUGPA(5))
3073                     qDebug("Critical path with %.2lf%% slack ending at %s "
3074                            "found",
3075                            100.0 - ((double) busyTime / overallDuration) *
3076                            100.0, id.latin1());
3077             }
3078             else
3079             {
3080                 if (DEBUGPA(11))
3081                     qDebug("Path ending at %s is not critical", id.latin1());
3082             }
3083             if (++checks == maxPaths)
3084             {
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."));
3089                 return false;
3090             }
3091             if (checks % 100000 == 0 && DEBUGPA(1))
3092                 qDebug("Already check %ld paths. %ld critical found.",
3093                        checks, found);
3094         }
3095     }
3096
3097     if (critical)
3098        scenarios[sc].isOnCriticalPath = true;
3099
3100     if (DEBUGPA(14))
3101         qDebug("  - Check of task %s completed (%d)", id.latin1(), critical);
3102     return critical;
3103 }
3104
3105 void
3106 Task::collectTransientFollowers(TaskList& list)
3107 {
3108     if (hasSubs())
3109     {
3110         for (TaskListIterator tli(followers); *tli; ++tli)
3111             if (list.findRef(*tli) < 0)
3112             {
3113                 list.append(*tli);
3114                 (*tli)->collectTransientFollowers(list);
3115             }
3116     }
3117     else
3118     {
3119         for (Task* task = getParent(); task; task = task->getParent())
3120             for (TaskListIterator tli(task->followers); *tli; ++tli)
3121                 if (list.findRef(*tli) < 0)
3122                 {
3123                     list.append(*tli);
3124                     (*tli)->collectTransientFollowers(list);
3125                 }
3126     }
3127 }
3128
3129 void
3130 Task::finishScenario(int sc)
3131 {
3132     scenarios[sc].start = start;
3133     scenarios[sc].end = end;
3134     scenarios[sc].bookedResources = bookedResources;
3135     scenarios[sc].scheduled = schedulingDone;
3136 }
3137
3138 void
3139 Task::computeBuffers()
3140 {
3141     int sg = project->getScheduleGranularity();
3142     for (int sc = 0; sc < project->getMaxScenarios(); sc++)
3143     {
3144         scenarios[sc].startBufferEnd = scenarios[sc].start - 1;
3145         scenarios[sc].endBufferStart = scenarios[sc].end + 1;
3146
3147         if (scenarios[sc].start == 0 || scenarios[sc].end == 0)
3148         {
3149             scenarios[sc].startBufferEnd = scenarios[sc].endBufferStart = 0;
3150             continue;
3151         }
3152
3153         if (duration > 0.0)
3154         {
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);
3165         }
3166         else if (length > 0.0)
3167         {
3168             double l;
3169             if (scenarios[sc].startBuffer > 0.0)
3170             {
3171                 for (l = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
3172                      scenarios[sc].startBufferEnd += sg)
3173                 {
3174                     if (project->isWorkingDay(scenarios[sc].startBufferEnd))
3175                     l += (double) sg / ONEDAY;
3176                     if (l >= scenarios[sc].length *
3177                         scenarios[sc].startBuffer / 100.0)
3178                         break;
3179                 }
3180             }
3181             if (scenarios[sc].endBuffer > 0.0)
3182             {
3183                 for (l = 0.0; scenarios[sc].endBufferStart >
3184                      scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
3185                 {
3186                     if (project->isWorkingDay(scenarios[sc].endBufferStart))
3187                         l += (double) sg / ONEDAY;
3188                     if (l >= scenarios[sc].length *
3189                         scenarios[sc].endBuffer / 100.0)
3190                         break;
3191                 }
3192             }
3193         }
3194         else if (effort > 0.0)
3195         {
3196             double e;
3197             if (scenarios[sc].startBuffer > 0.0)
3198             {
3199                 for (e = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
3200                      scenarios[sc].startBufferEnd += sg)
3201                 {
3202                     e += getLoad(sc,
3203                                  Interval(scenarios[sc].startBufferEnd,
3204                                           scenarios[sc].startBufferEnd + sg));
3205                     if (e >= scenarios[sc].effort *
3206                         scenarios[sc].startBuffer / 100.0)
3207                         break;
3208                 }
3209             }
3210             if (scenarios[sc].endBuffer > 0.0)
3211             {
3212                 for (e = 0.0; scenarios[sc].endBufferStart >
3213                      scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
3214                 {
3215                     e += getLoad(sc,
3216                                  Interval(scenarios[sc].endBufferStart - sg,
3217                                           scenarios[sc].endBufferStart));
3218                     if (e >= scenarios[sc].effort *
3219                         scenarios[sc].endBuffer / 100.0)
3220                         break;
3221                 }
3222             }
3223         }
3224     }
3225 }
3226
3227 void
3228 Task::calcCompletionDegree(int sc)
3229 {
3230     time_t now = project->getNow();
3231
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);
3237     else
3238         /* Calc completion for simple tasks and determine the task state. */
3239         scenarios[sc].calcCompletionDegree(now);
3240 }
3241
3242 double
3243 Task::getCompletionDegree(int sc) const
3244 {
3245     if(scenarios[sc].reportedCompletion >= 0.0)
3246         return(scenarios[sc].reportedCompletion);
3247
3248     return isContainer() && scenarios[sc].containerCompletion >= 0.0 ?
3249         scenarios[sc].containerCompletion : scenarios[sc].completionDegree;
3250 }
3251
3252 double
3253 Task::getCalcedCompletionDegree(int sc) const
3254 {
3255     return scenarios[sc].completionDegree;
3256 }
3257
3258 void
3259 Task::calcContainerCompletionDegree(int sc, time_t now)
3260 {
3261     assert(isContainer());
3262     assert(scenarios[sc].start < now && now <= scenarios[sc].end);
3263
3264     scenarios[sc].status = InProgress;
3265
3266     int totalMilestones = 0;
3267     int completedMilestones = 0;
3268     int reportedCompletedMilestones = 0;
3269     if (countMilestones(sc, now, totalMilestones, completedMilestones,
3270                         reportedCompletedMilestones))
3271     {
3272         scenarios[sc].completionDegree = completedMilestones * 100.0 /
3273             totalMilestones;
3274         scenarios[sc].containerCompletion = reportedCompletedMilestones
3275             * 100.0 / totalMilestones;
3276         return;
3277     }
3278
3279     double totalEffort = 0.0;
3280     double completedEffort = 0.0;
3281     double reportedCompletedEffort = 0.0;
3282     if (sumUpEffort(sc, now, totalEffort, completedEffort,
3283                     reportedCompletedEffort))
3284     {
3285         scenarios[sc].completionDegree = completedEffort * 100.0 /
3286             totalEffort;
3287         scenarios[sc].containerCompletion = reportedCompletedEffort * 100.0 /
3288             totalEffort;
3289     }
3290     else
3291     {
3292         /* We can't determine the completion degree for mixed work/non-work
3293          * tasks. So we use -1.0 as "in progress" value. */
3294         double comp = -1.0;
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;
3301     }
3302 }
3303
3304 double
3305 Task::getCompletedLoad(int sc) const
3306 {
3307     return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
3308         getCompletionDegree(sc) / 100.0;
3309 }
3310
3311 double
3312 Task::getRemainingLoad(int sc) const
3313 {
3314     return getLoad(sc, Interval(project->getStart(), project->getEnd())) *
3315         (1.0 - getCompletionDegree(sc) / 100.0);
3316 }
3317
3318 bool
3319 Task::countMilestones(int sc, time_t now, int& totalMilestones,
3320                       int& completedMilestones,
3321                       int& reportedCompletedMilestones)
3322 {
3323     if (isContainer())
3324     {
3325         for (TaskListIterator tli(*sub); *tli; ++tli)
3326             if (!(*tli)->countMilestones(sc, now, totalMilestones,
3327                                          completedMilestones,
3328                                          reportedCompletedMilestones))
3329                 return false;
3330
3331         /* A reported completion for a container always overrides the computed
3332          * completion. */
3333         if (scenarios[sc].reportedCompletion >= 0.0)
3334             reportedCompletedMilestones = static_cast<int>(totalMilestones *
3335                 scenarios[sc].reportedCompletion / 100.0);
3336
3337         return true;
3338     }
3339     else if (isMilestone())
3340     {
3341         totalMilestones++;
3342         if (scenarios[sc].start <= now)
3343             completedMilestones++;
3344
3345         if (scenarios[sc].reportedCompletion >= 100.0)
3346             reportedCompletedMilestones++;
3347         else
3348             if (scenarios[sc].start <= now)
3349                 reportedCompletedMilestones++;
3350
3351         return true;
3352     }
3353
3354     return false;
3355 }
3356
3357 bool
3358 Task::sumUpEffort(int sc, time_t now, double& totalEffort,
3359                   double& completedEffort, double& reportedCompletedEffort)
3360 {
3361     if (isContainer())
3362     {
3363         for (TaskListIterator tli(*sub); *tli; ++tli)
3364             if (!(*tli)->sumUpEffort(sc, now, totalEffort, completedEffort,
3365                                      reportedCompletedEffort))
3366                 return false;
3367
3368         /* A reported completion for a container always overrides the computed
3369          * completion. */
3370         if (scenarios[sc].reportedCompletion >= 0.0)
3371             reportedCompletedEffort = totalEffort *
3372                 scenarios[sc].reportedCompletion / 100.0;
3373
3374         return true;
3375     }
3376     if (scenarios[sc].effort > 0.0)
3377     {
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;
3384
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;
3391         else
3392             reportedCompletedEffort += load;
3393
3394         return true;
3395     }
3396     if (!allocations.isEmpty())
3397     {
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;
3407
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;
3413         else
3414             reportedCompletedEffort += load;
3415
3416         return true;
3417     }
3418     if (isMilestone())
3419     {
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. */
3425         return true;
3426     }
3427
3428     return false;
3429 }
3430
3431 QDomElement Task::xmlElement( QDomDocument& doc, bool /* absId */ )
3432 {
3433    QDomElement taskElem = doc.createElement( "Task" );
3434    QDomElement tempElem;
3435
3436    QString idStr = getId();
3437 /*   if( !absId )
3438       idStr = idStr.section( '.', -1 ); */
3439
3440    taskElem.setAttribute( "Id", idStr );
3441
3442    QDomText t;
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())));
3447
3448    double cmplt = getCompletionDegree(0);
3449    taskElem.appendChild( ReportXML::createXMLElem( doc, "complete", QString::number(cmplt, 'f', 1) ));
3450
3451    QString tType = "Milestone";
3452    if( !isMilestone() )
3453    {
3454       if( isContainer() )
3455      tType = "Container";
3456       else
3457      tType = "Task";
3458
3459    }
3460    taskElem.appendChild( ReportXML::createXMLElem( doc, "Type", tType  ));
3461
3462    CoreAttributes *parent = getParent();
3463    if( parent )
3464       taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
3465
3466    if( !note.isEmpty())
3467       taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
3468    if(!ref.isEmpty())
3469        taskElem.appendChild(ReportXML::createXMLElem(doc, "Reference",
3470                                                      ref));
3471    if(!refLabel.isEmpty())
3472        taskElem.appendChild(ReportXML::createXMLElem(doc, "ReferenceLabel",
3473                                                      refLabel));
3474
3475     if (scenarios[0].minStart != 0)
3476     {
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 );
3482     }
3483
3484     if (scenarios[0].maxStart != 0)
3485     {
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 );
3491     }
3492
3493     if (scenarios[0].minEnd != 0)
3494     {
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 );
3500     }
3501
3502     if (scenarios[0].maxEnd != 0)
3503     {
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 );
3509     }
3510     if (project->getMaxScenarios() > 1)
3511     {
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 );
3517
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 );
3523     }
3524
3525    tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( scenarios[0].start ));
3526    tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].start ));
3527    taskElem.appendChild( tempElem );
3528
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 );
3533
3534    /* Start- and Endbuffer */
3535    if(getStartBuffer(0) > 0.01)
3536    {
3537        /* startbuffer exists */
3538        tempElem = ReportXML::createXMLElem
3539            (doc, "startBufferSize",
3540             QString::number(getStartBuffer(0)));
3541        taskElem.appendChild( tempElem );
3542
3543        tempElem = ReportXML::createXMLElem
3544            (doc, "PlanStartBufferEnd",
3545             QString::number(getStartBufferEnd(0)));
3546        tempElem.setAttribute("humanReadable",
3547                              time2ISO(getStartBufferEnd(0)));
3548        taskElem.appendChild(tempElem);
3549
3550        tempElem = ReportXML::createXMLElem
3551            (doc, "PlanStartBufferEnd",
3552             QString::number(getStartBufferEnd(0)));
3553        tempElem.setAttribute("humanReadable",
3554                              time2ISO(getStartBufferEnd(0)));
3555        taskElem.appendChild(tempElem);
3556    }
3557
3558    if(getEndBuffer(0) > 0.01)
3559    {
3560        /* startbuffer exists */
3561        tempElem = ReportXML::createXMLElem
3562            (doc, "EndBufferSize", QString::number(getEndBuffer(0)));
3563        taskElem.appendChild(tempElem);
3564
3565        tempElem = ReportXML::createXMLElem
3566            (doc, "PlanEndBufferStart",
3567             QString::number(getEndBufferStart(0)));
3568        tempElem.setAttribute("humanReadable",
3569                              time2ISO(getEndBufferStart(0)));
3570        taskElem.appendChild(tempElem);
3571
3572        tempElem = ReportXML::createXMLElem
3573            (doc, "PlanEndBufferStart",
3574             QString::number(getEndBufferStart(0)));
3575        tempElem.setAttribute("humanReadable",
3576                              time2ISO(getStartBufferEnd(0)));
3577        taskElem.appendChild(tempElem);
3578    }
3579
3580    /* Responsible persons */
3581    if( getResponsible() )
3582       taskElem.appendChild( getResponsible()->xmlIDElement( doc ));
3583
3584    /* Now start the subtasks */
3585    int cnt = 0;
3586    QDomElement subTaskElem = doc.createElement( "SubTasks" );
3587    for (Task* t = subFirst(); t != 0; t = subNext())
3588    {
3589       if( t != this )
3590       {
3591      QDomElement sTask = t->xmlElement( doc, false );
3592      subTaskElem.appendChild( sTask );
3593      cnt++;
3594       }
3595    }
3596    if( cnt > 0 )
3597       taskElem.appendChild( subTaskElem);
3598
3599    /* list of tasks by id which are previous */
3600    if( previous.count() > 0 )
3601    {
3602       for (TaskListIterator tli(previous); *tli != 0; ++tli)
3603       {
3604      if( *tli != this )
3605      {
3606         taskElem.appendChild( ReportXML::createXMLElem( doc, "Previous",
3607                                                         (*tli)->getId()));
3608      }
3609       }
3610    }
3611
3612    /* list of tasks by id which follow */
3613    if( followers.count() > 0 )
3614    {
3615       for (TaskListIterator tli(followers); *tli != 0; ++tli)
3616       {
3617      if( *tli != this )
3618      {
3619         taskElem.appendChild( ReportXML::createXMLElem( doc, "Follower",
3620                                                         (*tli)->getId()));
3621      }
3622       }
3623    }
3624
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>
3632     *       <Allocation>
3633     *          <Load>100</Load>
3634     *          <Persistent>Yes</Persistent>
3635     *       </Allocation>
3636     *  </Resource>
3637     *
3638     *  But we do not ;-) to have full flexibility.
3639     *
3640     */
3641    /* Allocations */
3642    if( allocations.count() > 0 )
3643    {
3644       QPtrList<Allocation> al(allocations);
3645       for (QPtrListIterator<Allocation> ali(al); *ali != 0; ++ali)
3646       {
3647      taskElem.appendChild( (*ali)->xmlElement( doc ));
3648       }
3649    }
3650
3651    /* booked Ressources */
3652    if( bookedResources.count() > 0 )
3653    {
3654        for (ResourceListIterator rli(bookedResources); *rli != 0; ++rli)
3655       {
3656      taskElem.appendChild( (*rli)->xmlIDElement( doc ));
3657       }
3658    }
3659
3660    return( taskElem );
3661 }